afb379b28517b86fc7f649917c8bc9b74f6829db
[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 #define SLASH '\\'
63
64 #else
65
66 #include <sys/file.h>
67 #define DoSleep( n ) if( (n) >= 0) sleep(n)
68 #define SLASH '/'
69
70 #endif
71
72 #include "config.h"
73
74 #include <assert.h>
75 #include <stdio.h>
76 #include <ctype.h>
77 #include <errno.h>
78 #include <sys/types.h>
79 #include <sys/stat.h>
80 #include <math.h>
81 #include <ctype.h>
82
83 #if STDC_HEADERS
84 # include <stdlib.h>
85 # include <string.h>
86 # include <stdarg.h>
87 #else /* not STDC_HEADERS */
88 # if HAVE_STRING_H
89 #  include <string.h>
90 # else /* not HAVE_STRING_H */
91 #  include <strings.h>
92 # endif /* not HAVE_STRING_H */
93 #endif /* not STDC_HEADERS */
94
95 #if HAVE_SYS_FCNTL_H
96 # include <sys/fcntl.h>
97 #else /* not HAVE_SYS_FCNTL_H */
98 # if HAVE_FCNTL_H
99 #  include <fcntl.h>
100 # endif /* HAVE_FCNTL_H */
101 #endif /* not HAVE_SYS_FCNTL_H */
102
103 #if TIME_WITH_SYS_TIME
104 # include <sys/time.h>
105 # include <time.h>
106 #else
107 # if HAVE_SYS_TIME_H
108 #  include <sys/time.h>
109 # else
110 #  include <time.h>
111 # endif
112 #endif
113
114 #if defined(_amigados) && !defined(__GNUC__)
115 struct timezone {
116     int tz_minuteswest;
117     int tz_dsttime;
118 };
119 extern int gettimeofday(struct timeval *, struct timezone *);
120 #endif
121
122 #if HAVE_UNISTD_H
123 # include <unistd.h>
124 #endif
125
126 #include "common.h"
127 #include "frontend.h"
128 #include "backend.h"
129 #include "parser.h"
130 #include "moves.h"
131 #if ZIPPY
132 # include "zippy.h"
133 #endif
134 #include "backendz.h"
135 #include "gettext.h"
136
137 #ifdef ENABLE_NLS
138 # define _(s) gettext (s)
139 # define N_(s) gettext_noop (s)
140 # define T_(s) gettext(s)
141 #else
142 # ifdef WIN32
143 #   define _(s) T_(s)
144 #   define N_(s) s
145 # else
146 #   define _(s) (s)
147 #   define N_(s) s
148 #   define T_(s) s
149 # endif
150 #endif
151
152
153 /* A point in time */
154 typedef struct {
155     long sec;  /* Assuming this is >= 32 bits */
156     int ms;    /* Assuming this is >= 16 bits */
157 } TimeMark;
158
159 int establish P((void));
160 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
161                          char *buf, int count, int error));
162 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
163                       char *buf, int count, int error));
164 void ics_printf P((char *format, ...));
165 void SendToICS P((char *s));
166 void SendToICSDelayed P((char *s, long msdelay));
167 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
168 void HandleMachineMove P((char *message, ChessProgramState *cps));
169 int AutoPlayOneMove P((void));
170 int LoadGameOneMove P((ChessMove readAhead));
171 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
172 int LoadPositionFromFile P((char *filename, int n, char *title));
173 int SavePositionToFile P((char *filename));
174 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
175                                                                                 Board board));
176 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
177 void ShowMove P((int fromX, int fromY, int toX, int toY));
178 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
179                    /*char*/int promoChar));
180 void BackwardInner P((int target));
181 void ForwardInner P((int target));
182 int Adjudicate P((ChessProgramState *cps));
183 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
184 void EditPositionDone P((Boolean fakeRights));
185 void PrintOpponents P((FILE *fp));
186 void PrintPosition P((FILE *fp, int move));
187 void StartChessProgram P((ChessProgramState *cps));
188 void SendToProgram P((char *message, ChessProgramState *cps));
189 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
190 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
191                            char *buf, int count, int error));
192 void SendTimeControl P((ChessProgramState *cps,
193                         int mps, long tc, int inc, int sd, int st));
194 char *TimeControlTagValue P((void));
195 void Attention P((ChessProgramState *cps));
196 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
197 int ResurrectChessProgram P((void));
198 void DisplayComment P((int moveNumber, char *text));
199 void DisplayMove P((int moveNumber));
200
201 void ParseGameHistory P((char *game));
202 void ParseBoard12 P((char *string));
203 void KeepAlive P((void));
204 void StartClocks P((void));
205 void SwitchClocks P((int nr));
206 void StopClocks P((void));
207 void ResetClocks P((void));
208 char *PGNDate P((void));
209 void SetGameInfo P((void));
210 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
211 int RegisterMove P((void));
212 void MakeRegisteredMove P((void));
213 void TruncateGame P((void));
214 int looking_at P((char *, int *, char *));
215 void CopyPlayerNameIntoFileName P((char **, char *));
216 char *SavePart P((char *));
217 int SaveGameOldStyle P((FILE *));
218 int SaveGamePGN P((FILE *));
219 void GetTimeMark P((TimeMark *));
220 long SubtractTimeMarks P((TimeMark *, TimeMark *));
221 int CheckFlags P((void));
222 long NextTickLength P((long));
223 void CheckTimeControl P((void));
224 void show_bytes P((FILE *, char *, int));
225 int string_to_rating P((char *str));
226 void ParseFeatures P((char* args, ChessProgramState *cps));
227 void InitBackEnd3 P((void));
228 void FeatureDone P((ChessProgramState* cps, int val));
229 void InitChessProgram P((ChessProgramState *cps, int setup));
230 void OutputKibitz(int window, char *text);
231 int PerpetualChase(int first, int last);
232 int EngineOutputIsUp();
233 void InitDrawingSizes(int x, int y);
234 void NextMatchGame P((void));
235 int NextTourneyGame P((int nr, int *swap));
236 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
237 FILE *WriteTourneyFile P((char *results));
238 void DisplayTwoMachinesTitle P(());
239
240 #ifdef WIN32
241        extern void ConsoleCreate();
242 #endif
243
244 ChessProgramState *WhitePlayer();
245 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
246 int VerifyDisplayMode P(());
247
248 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
249 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
250 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
251 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
252 void ics_update_width P((int new_width));
253 extern char installDir[MSG_SIZ];
254 VariantClass startVariant; /* [HGM] nicks: initial variant */
255 Boolean abortMatch;
256
257 extern int tinyLayout, smallLayout;
258 ChessProgramStats programStats;
259 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
260 int endPV = -1;
261 static int exiting = 0; /* [HGM] moved to top */
262 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
263 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
264 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
265 int partnerHighlight[2];
266 Boolean partnerBoardValid = 0;
267 char partnerStatus[MSG_SIZ];
268 Boolean partnerUp;
269 Boolean originalFlip;
270 Boolean twoBoards = 0;
271 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
272 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
273 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
274 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
275 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
276 int opponentKibitzes;
277 int lastSavedGame; /* [HGM] save: ID of game */
278 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
279 extern int chatCount;
280 int chattingPartner;
281 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
282 char lastMsg[MSG_SIZ];
283 ChessSquare pieceSweep = EmptySquare;
284 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
285 int promoDefaultAltered;
286
287 /* States for ics_getting_history */
288 #define H_FALSE 0
289 #define H_REQUESTED 1
290 #define H_GOT_REQ_HEADER 2
291 #define H_GOT_UNREQ_HEADER 3
292 #define H_GETTING_MOVES 4
293 #define H_GOT_UNWANTED_HEADER 5
294
295 /* whosays values for GameEnds */
296 #define GE_ICS 0
297 #define GE_ENGINE 1
298 #define GE_PLAYER 2
299 #define GE_FILE 3
300 #define GE_XBOARD 4
301 #define GE_ENGINE1 5
302 #define GE_ENGINE2 6
303
304 /* Maximum number of games in a cmail message */
305 #define CMAIL_MAX_GAMES 20
306
307 /* Different types of move when calling RegisterMove */
308 #define CMAIL_MOVE   0
309 #define CMAIL_RESIGN 1
310 #define CMAIL_DRAW   2
311 #define CMAIL_ACCEPT 3
312
313 /* Different types of result to remember for each game */
314 #define CMAIL_NOT_RESULT 0
315 #define CMAIL_OLD_RESULT 1
316 #define CMAIL_NEW_RESULT 2
317
318 /* Telnet protocol constants */
319 #define TN_WILL 0373
320 #define TN_WONT 0374
321 #define TN_DO   0375
322 #define TN_DONT 0376
323 #define TN_IAC  0377
324 #define TN_ECHO 0001
325 #define TN_SGA  0003
326 #define TN_PORT 23
327
328 char*
329 safeStrCpy( char *dst, const char *src, size_t count )
330 { // [HGM] made safe
331   int i;
332   assert( dst != NULL );
333   assert( src != NULL );
334   assert( count > 0 );
335
336   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
337   if(  i == count && dst[count-1] != NULLCHAR)
338     {
339       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
340       if(appData.debugMode)
341       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
342     }
343
344   return dst;
345 }
346
347 /* Some compiler can't cast u64 to double
348  * This function do the job for us:
349
350  * We use the highest bit for cast, this only
351  * works if the highest bit is not
352  * in use (This should not happen)
353  *
354  * We used this for all compiler
355  */
356 double
357 u64ToDouble(u64 value)
358 {
359   double r;
360   u64 tmp = value & u64Const(0x7fffffffffffffff);
361   r = (double)(s64)tmp;
362   if (value & u64Const(0x8000000000000000))
363        r +=  9.2233720368547758080e18; /* 2^63 */
364  return r;
365 }
366
367 /* Fake up flags for now, as we aren't keeping track of castling
368    availability yet. [HGM] Change of logic: the flag now only
369    indicates the type of castlings allowed by the rule of the game.
370    The actual rights themselves are maintained in the array
371    castlingRights, as part of the game history, and are not probed
372    by this function.
373  */
374 int
375 PosFlags(index)
376 {
377   int flags = F_ALL_CASTLE_OK;
378   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
379   switch (gameInfo.variant) {
380   case VariantSuicide:
381     flags &= ~F_ALL_CASTLE_OK;
382   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
383     flags |= F_IGNORE_CHECK;
384   case VariantLosers:
385     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
386     break;
387   case VariantAtomic:
388     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
389     break;
390   case VariantKriegspiel:
391     flags |= F_KRIEGSPIEL_CAPTURE;
392     break;
393   case VariantCapaRandom:
394   case VariantFischeRandom:
395     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
396   case VariantNoCastle:
397   case VariantShatranj:
398   case VariantCourier:
399   case VariantMakruk:
400   case VariantGrand:
401     flags &= ~F_ALL_CASTLE_OK;
402     break;
403   default:
404     break;
405   }
406   return flags;
407 }
408
409 FILE *gameFileFP, *debugFP;
410
411 /*
412     [AS] Note: sometimes, the sscanf() function is used to parse the input
413     into a fixed-size buffer. Because of this, we must be prepared to
414     receive strings as long as the size of the input buffer, which is currently
415     set to 4K for Windows and 8K for the rest.
416     So, we must either allocate sufficiently large buffers here, or
417     reduce the size of the input buffer in the input reading part.
418 */
419
420 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
421 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
422 char thinkOutput1[MSG_SIZ*10];
423
424 ChessProgramState first, second, pairing;
425
426 /* premove variables */
427 int premoveToX = 0;
428 int premoveToY = 0;
429 int premoveFromX = 0;
430 int premoveFromY = 0;
431 int premovePromoChar = 0;
432 int gotPremove = 0;
433 Boolean alarmSounded;
434 /* end premove variables */
435
436 char *ics_prefix = "$";
437 int ics_type = ICS_GENERIC;
438
439 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
440 int pauseExamForwardMostMove = 0;
441 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
442 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
443 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
444 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
445 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
446 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
447 int whiteFlag = FALSE, blackFlag = FALSE;
448 int userOfferedDraw = FALSE;
449 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
450 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
451 int cmailMoveType[CMAIL_MAX_GAMES];
452 long ics_clock_paused = 0;
453 ProcRef icsPR = NoProc, cmailPR = NoProc;
454 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
455 GameMode gameMode = BeginningOfGame;
456 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
457 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
458 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
459 int hiddenThinkOutputState = 0; /* [AS] */
460 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
461 int adjudicateLossPlies = 6;
462 char white_holding[64], black_holding[64];
463 TimeMark lastNodeCountTime;
464 long lastNodeCount=0;
465 int shiftKey; // [HGM] set by mouse handler
466
467 int have_sent_ICS_logon = 0;
468 int movesPerSession;
469 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
470 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
471 long timeControl_2; /* [AS] Allow separate time controls */
472 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
473 long timeRemaining[2][MAX_MOVES];
474 int matchGame = 0, nextGame = 0, roundNr = 0;
475 Boolean waitingForGame = FALSE;
476 TimeMark programStartTime, pauseStart;
477 char ics_handle[MSG_SIZ];
478 int have_set_title = 0;
479
480 /* animateTraining preserves the state of appData.animate
481  * when Training mode is activated. This allows the
482  * response to be animated when appData.animate == TRUE and
483  * appData.animateDragging == TRUE.
484  */
485 Boolean animateTraining;
486
487 GameInfo gameInfo;
488
489 AppData appData;
490
491 Board boards[MAX_MOVES];
492 /* [HGM] Following 7 needed for accurate legality tests: */
493 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
494 signed char  initialRights[BOARD_FILES];
495 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
496 int   initialRulePlies, FENrulePlies;
497 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
498 int loadFlag = 0;
499 Boolean shuffleOpenings;
500 int mute; // mute all sounds
501
502 // [HGM] vari: next 12 to save and restore variations
503 #define MAX_VARIATIONS 10
504 int framePtr = MAX_MOVES-1; // points to free stack entry
505 int storedGames = 0;
506 int savedFirst[MAX_VARIATIONS];
507 int savedLast[MAX_VARIATIONS];
508 int savedFramePtr[MAX_VARIATIONS];
509 char *savedDetails[MAX_VARIATIONS];
510 ChessMove savedResult[MAX_VARIATIONS];
511
512 void PushTail P((int firstMove, int lastMove));
513 Boolean PopTail P((Boolean annotate));
514 void PushInner P((int firstMove, int lastMove));
515 void PopInner P((Boolean annotate));
516 void CleanupTail P((void));
517
518 ChessSquare  FIDEArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
520         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
522         BlackKing, BlackBishop, BlackKnight, BlackRook }
523 };
524
525 ChessSquare twoKingsArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
527         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
528     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
529         BlackKing, BlackKing, BlackKnight, BlackRook }
530 };
531
532 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
534         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
535     { BlackRook, BlackMan, BlackBishop, BlackQueen,
536         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
537 };
538
539 ChessSquare SpartanArray[2][BOARD_FILES] = {
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
542     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
543         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
544 };
545
546 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
547     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
548         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
549     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
550         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
551 };
552
553 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
554     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
555         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
556     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
557         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
558 };
559
560 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
561     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
562         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
563     { BlackRook, BlackKnight, BlackMan, BlackFerz,
564         BlackKing, BlackMan, BlackKnight, BlackRook }
565 };
566
567
568 #if (BOARD_FILES>=10)
569 ChessSquare ShogiArray[2][BOARD_FILES] = {
570     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
571         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
572     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
573         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
574 };
575
576 ChessSquare XiangqiArray[2][BOARD_FILES] = {
577     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
578         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
580         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
581 };
582
583 ChessSquare CapablancaArray[2][BOARD_FILES] = {
584     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
585         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
587         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
588 };
589
590 ChessSquare GreatArray[2][BOARD_FILES] = {
591     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
592         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
593     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
594         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
595 };
596
597 ChessSquare JanusArray[2][BOARD_FILES] = {
598     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
599         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
600     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
601         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
602 };
603
604 ChessSquare GrandArray[2][BOARD_FILES] = {
605     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
606         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
607     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
608         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
609 };
610
611 #ifdef GOTHIC
612 ChessSquare GothicArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
614         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
616         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
617 };
618 #else // !GOTHIC
619 #define GothicArray CapablancaArray
620 #endif // !GOTHIC
621
622 #ifdef FALCON
623 ChessSquare FalconArray[2][BOARD_FILES] = {
624     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
625         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
626     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
627         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
628 };
629 #else // !FALCON
630 #define FalconArray CapablancaArray
631 #endif // !FALCON
632
633 #else // !(BOARD_FILES>=10)
634 #define XiangqiPosition FIDEArray
635 #define CapablancaArray FIDEArray
636 #define GothicArray FIDEArray
637 #define GreatArray FIDEArray
638 #endif // !(BOARD_FILES>=10)
639
640 #if (BOARD_FILES>=12)
641 ChessSquare CourierArray[2][BOARD_FILES] = {
642     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
643         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
644     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
645         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
646 };
647 #else // !(BOARD_FILES>=12)
648 #define CourierArray CapablancaArray
649 #endif // !(BOARD_FILES>=12)
650
651
652 Board initialPosition;
653
654
655 /* Convert str to a rating. Checks for special cases of "----",
656
657    "++++", etc. Also strips ()'s */
658 int
659 string_to_rating(str)
660   char *str;
661 {
662   while(*str && !isdigit(*str)) ++str;
663   if (!*str)
664     return 0;   /* One of the special "no rating" cases */
665   else
666     return atoi(str);
667 }
668
669 void
670 ClearProgramStats()
671 {
672     /* Init programStats */
673     programStats.movelist[0] = 0;
674     programStats.depth = 0;
675     programStats.nr_moves = 0;
676     programStats.moves_left = 0;
677     programStats.nodes = 0;
678     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
679     programStats.score = 0;
680     programStats.got_only_move = 0;
681     programStats.got_fail = 0;
682     programStats.line_is_book = 0;
683 }
684
685 void
686 CommonEngineInit()
687 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
688     if (appData.firstPlaysBlack) {
689         first.twoMachinesColor = "black\n";
690         second.twoMachinesColor = "white\n";
691     } else {
692         first.twoMachinesColor = "white\n";
693         second.twoMachinesColor = "black\n";
694     }
695
696     first.other = &second;
697     second.other = &first;
698
699     { float norm = 1;
700         if(appData.timeOddsMode) {
701             norm = appData.timeOdds[0];
702             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
703         }
704         first.timeOdds  = appData.timeOdds[0]/norm;
705         second.timeOdds = appData.timeOdds[1]/norm;
706     }
707
708     if(programVersion) free(programVersion);
709     if (appData.noChessProgram) {
710         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
711         sprintf(programVersion, "%s", PACKAGE_STRING);
712     } else {
713       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
714       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
715       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
716     }
717 }
718
719 void
720 UnloadEngine(ChessProgramState *cps)
721 {
722         /* Kill off first chess program */
723         if (cps->isr != NULL)
724           RemoveInputSource(cps->isr);
725         cps->isr = NULL;
726
727         if (cps->pr != NoProc) {
728             ExitAnalyzeMode();
729             DoSleep( appData.delayBeforeQuit );
730             SendToProgram("quit\n", cps);
731             DoSleep( appData.delayAfterQuit );
732             DestroyChildProcess(cps->pr, cps->useSigterm);
733         }
734         cps->pr = NoProc;
735         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
736 }
737
738 void
739 ClearOptions(ChessProgramState *cps)
740 {
741     int i;
742     cps->nrOptions = cps->comboCnt = 0;
743     for(i=0; i<MAX_OPTIONS; i++) {
744         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
745         cps->option[i].textValue = 0;
746     }
747 }
748
749 char *engineNames[] = {
750 "first",
751 "second"
752 };
753
754 void
755 InitEngine(ChessProgramState *cps, int n)
756 {   // [HGM] all engine initialiation put in a function that does one engine
757
758     ClearOptions(cps);
759
760     cps->which = engineNames[n];
761     cps->maybeThinking = FALSE;
762     cps->pr = NoProc;
763     cps->isr = NULL;
764     cps->sendTime = 2;
765     cps->sendDrawOffers = 1;
766
767     cps->program = appData.chessProgram[n];
768     cps->host = appData.host[n];
769     cps->dir = appData.directory[n];
770     cps->initString = appData.engInitString[n];
771     cps->computerString = appData.computerString[n];
772     cps->useSigint  = TRUE;
773     cps->useSigterm = TRUE;
774     cps->reuse = appData.reuse[n];
775     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
776     cps->useSetboard = FALSE;
777     cps->useSAN = FALSE;
778     cps->usePing = FALSE;
779     cps->lastPing = 0;
780     cps->lastPong = 0;
781     cps->usePlayother = FALSE;
782     cps->useColors = TRUE;
783     cps->useUsermove = FALSE;
784     cps->sendICS = FALSE;
785     cps->sendName = appData.icsActive;
786     cps->sdKludge = FALSE;
787     cps->stKludge = FALSE;
788     TidyProgramName(cps->program, cps->host, cps->tidy);
789     cps->matchWins = 0;
790     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
791     cps->analysisSupport = 2; /* detect */
792     cps->analyzing = FALSE;
793     cps->initDone = FALSE;
794
795     /* New features added by Tord: */
796     cps->useFEN960 = FALSE;
797     cps->useOOCastle = TRUE;
798     /* End of new features added by Tord. */
799     cps->fenOverride  = appData.fenOverride[n];
800
801     /* [HGM] time odds: set factor for each machine */
802     cps->timeOdds  = appData.timeOdds[n];
803
804     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
805     cps->accumulateTC = appData.accumulateTC[n];
806     cps->maxNrOfSessions = 1;
807
808     /* [HGM] debug */
809     cps->debug = FALSE;
810
811     cps->supportsNPS = UNKNOWN;
812     cps->memSize = FALSE;
813     cps->maxCores = FALSE;
814     cps->egtFormats[0] = NULLCHAR;
815
816     /* [HGM] options */
817     cps->optionSettings  = appData.engOptions[n];
818
819     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
820     cps->isUCI = appData.isUCI[n]; /* [AS] */
821     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
822
823     if (appData.protocolVersion[n] > PROTOVER
824         || appData.protocolVersion[n] < 1)
825       {
826         char buf[MSG_SIZ];
827         int len;
828
829         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
830                        appData.protocolVersion[n]);
831         if( (len > MSG_SIZ) && appData.debugMode )
832           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
833
834         DisplayFatalError(buf, 0, 2);
835       }
836     else
837       {
838         cps->protocolVersion = appData.protocolVersion[n];
839       }
840
841     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
842 }
843
844 ChessProgramState *savCps;
845
846 void
847 LoadEngine()
848 {
849     int i;
850     if(WaitForEngine(savCps, LoadEngine)) return;
851     CommonEngineInit(); // recalculate time odds
852     if(gameInfo.variant != StringToVariant(appData.variant)) {
853         // we changed variant when loading the engine; this forces us to reset
854         Reset(TRUE, savCps != &first);
855         EditGameEvent(); // for consistency with other path, as Reset changes mode
856     }
857     InitChessProgram(savCps, FALSE);
858     SendToProgram("force\n", savCps);
859     DisplayMessage("", "");
860     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
861     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
862     ThawUI();
863     SetGNUMode();
864 }
865
866 void
867 ReplaceEngine(ChessProgramState *cps, int n)
868 {
869     EditGameEvent();
870     UnloadEngine(cps);
871     appData.noChessProgram = FALSE;
872     appData.clockMode = TRUE;
873     InitEngine(cps, n);
874     UpdateLogos(TRUE);
875     if(n) return; // only startup first engine immediately; second can wait
876     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
877     LoadEngine();
878 }
879
880 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
881 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
882
883 static char resetOptions[] = 
884         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
885         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
886
887 void
888 Load(ChessProgramState *cps, int i)
889 {
890     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
891     if(engineLine[0]) { // an engine was selected from the combo box
892         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
893         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
894         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
895         ParseArgsFromString(buf);
896         SwapEngines(i);
897         ReplaceEngine(cps, i);
898         return;
899     }
900     p = engineName;
901     while(q = strchr(p, SLASH)) p = q+1;
902     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
903     if(engineDir[0] != NULLCHAR)
904         appData.directory[i] = engineDir;
905     else if(p != engineName) { // derive directory from engine path, when not given
906         p[-1] = 0;
907         appData.directory[i] = strdup(engineName);
908         p[-1] = SLASH;
909     } else appData.directory[i] = ".";
910     if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
911     if(params[0]) {
912         snprintf(command, MSG_SIZ, "%s %s", p, params);
913         p = command;
914     }
915     appData.chessProgram[i] = strdup(p);
916     appData.isUCI[i] = isUCI;
917     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
918     appData.hasOwnBookUCI[i] = hasBook;
919     if(!nickName[0]) useNick = FALSE;
920     if(useNick) ASSIGN(appData.pgnName[i], nickName);
921     if(addToList) {
922         int len;
923         char quote;
924         q = firstChessProgramNames;
925         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
926         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
927         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
928                         quote, p, quote, appData.directory[i], 
929                         useNick ? " -fn \"" : "",
930                         useNick ? nickName : "",
931                         useNick ? "\"" : "",
932                         v1 ? " -firstProtocolVersion 1" : "",
933                         hasBook ? "" : " -fNoOwnBookUCI",
934                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
935                         storeVariant ? " -variant " : "",
936                         storeVariant ? VariantName(gameInfo.variant) : "");
937         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
938         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
939         if(q)   free(q);
940     }
941     ReplaceEngine(cps, i);
942 }
943
944 void
945 InitTimeControls()
946 {
947     int matched, min, sec;
948     /*
949      * Parse timeControl resource
950      */
951     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
952                           appData.movesPerSession)) {
953         char buf[MSG_SIZ];
954         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
955         DisplayFatalError(buf, 0, 2);
956     }
957
958     /*
959      * Parse searchTime resource
960      */
961     if (*appData.searchTime != NULLCHAR) {
962         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
963         if (matched == 1) {
964             searchTime = min * 60;
965         } else if (matched == 2) {
966             searchTime = min * 60 + sec;
967         } else {
968             char buf[MSG_SIZ];
969             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
970             DisplayFatalError(buf, 0, 2);
971         }
972     }
973 }
974
975 void
976 InitBackEnd1()
977 {
978
979     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
980     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
981
982     GetTimeMark(&programStartTime);
983     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
984     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
985
986     ClearProgramStats();
987     programStats.ok_to_send = 1;
988     programStats.seen_stat = 0;
989
990     /*
991      * Initialize game list
992      */
993     ListNew(&gameList);
994
995
996     /*
997      * Internet chess server status
998      */
999     if (appData.icsActive) {
1000         appData.matchMode = FALSE;
1001         appData.matchGames = 0;
1002 #if ZIPPY
1003         appData.noChessProgram = !appData.zippyPlay;
1004 #else
1005         appData.zippyPlay = FALSE;
1006         appData.zippyTalk = FALSE;
1007         appData.noChessProgram = TRUE;
1008 #endif
1009         if (*appData.icsHelper != NULLCHAR) {
1010             appData.useTelnet = TRUE;
1011             appData.telnetProgram = appData.icsHelper;
1012         }
1013     } else {
1014         appData.zippyTalk = appData.zippyPlay = FALSE;
1015     }
1016
1017     /* [AS] Initialize pv info list [HGM] and game state */
1018     {
1019         int i, j;
1020
1021         for( i=0; i<=framePtr; i++ ) {
1022             pvInfoList[i].depth = -1;
1023             boards[i][EP_STATUS] = EP_NONE;
1024             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1025         }
1026     }
1027
1028     InitTimeControls();
1029
1030     /* [AS] Adjudication threshold */
1031     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1032
1033     InitEngine(&first, 0);
1034     InitEngine(&second, 1);
1035     CommonEngineInit();
1036
1037     pairing.which = "pairing"; // pairing engine
1038     pairing.pr = NoProc;
1039     pairing.isr = NULL;
1040     pairing.program = appData.pairingEngine;
1041     pairing.host = "localhost";
1042     pairing.dir = ".";
1043
1044     if (appData.icsActive) {
1045         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1046     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1047         appData.clockMode = FALSE;
1048         first.sendTime = second.sendTime = 0;
1049     }
1050
1051 #if ZIPPY
1052     /* Override some settings from environment variables, for backward
1053        compatibility.  Unfortunately it's not feasible to have the env
1054        vars just set defaults, at least in xboard.  Ugh.
1055     */
1056     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1057       ZippyInit();
1058     }
1059 #endif
1060
1061     if (!appData.icsActive) {
1062       char buf[MSG_SIZ];
1063       int len;
1064
1065       /* Check for variants that are supported only in ICS mode,
1066          or not at all.  Some that are accepted here nevertheless
1067          have bugs; see comments below.
1068       */
1069       VariantClass variant = StringToVariant(appData.variant);
1070       switch (variant) {
1071       case VariantBughouse:     /* need four players and two boards */
1072       case VariantKriegspiel:   /* need to hide pieces and move details */
1073         /* case VariantFischeRandom: (Fabien: moved below) */
1074         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1075         if( (len > MSG_SIZ) && appData.debugMode )
1076           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1077
1078         DisplayFatalError(buf, 0, 2);
1079         return;
1080
1081       case VariantUnknown:
1082       case VariantLoadable:
1083       case Variant29:
1084       case Variant30:
1085       case Variant31:
1086       case Variant32:
1087       case Variant33:
1088       case Variant34:
1089       case Variant35:
1090       case Variant36:
1091       default:
1092         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1093         if( (len > MSG_SIZ) && appData.debugMode )
1094           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1095
1096         DisplayFatalError(buf, 0, 2);
1097         return;
1098
1099       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1100       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1101       case VariantGothic:     /* [HGM] should work */
1102       case VariantCapablanca: /* [HGM] should work */
1103       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1104       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1105       case VariantKnightmate: /* [HGM] should work */
1106       case VariantCylinder:   /* [HGM] untested */
1107       case VariantFalcon:     /* [HGM] untested */
1108       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1109                                  offboard interposition not understood */
1110       case VariantNormal:     /* definitely works! */
1111       case VariantWildCastle: /* pieces not automatically shuffled */
1112       case VariantNoCastle:   /* pieces not automatically shuffled */
1113       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1114       case VariantLosers:     /* should work except for win condition,
1115                                  and doesn't know captures are mandatory */
1116       case VariantSuicide:    /* should work except for win condition,
1117                                  and doesn't know captures are mandatory */
1118       case VariantGiveaway:   /* should work except for win condition,
1119                                  and doesn't know captures are mandatory */
1120       case VariantTwoKings:   /* should work */
1121       case VariantAtomic:     /* should work except for win condition */
1122       case Variant3Check:     /* should work except for win condition */
1123       case VariantShatranj:   /* should work except for all win conditions */
1124       case VariantMakruk:     /* should work except for draw countdown */
1125       case VariantBerolina:   /* might work if TestLegality is off */
1126       case VariantCapaRandom: /* should work */
1127       case VariantJanus:      /* should work */
1128       case VariantSuper:      /* experimental */
1129       case VariantGreat:      /* experimental, requires legality testing to be off */
1130       case VariantSChess:     /* S-Chess, should work */
1131       case VariantGrand:      /* should work */
1132       case VariantSpartan:    /* should work */
1133         break;
1134       }
1135     }
1136
1137 }
1138
1139 int NextIntegerFromString( char ** str, long * value )
1140 {
1141     int result = -1;
1142     char * s = *str;
1143
1144     while( *s == ' ' || *s == '\t' ) {
1145         s++;
1146     }
1147
1148     *value = 0;
1149
1150     if( *s >= '0' && *s <= '9' ) {
1151         while( *s >= '0' && *s <= '9' ) {
1152             *value = *value * 10 + (*s - '0');
1153             s++;
1154         }
1155
1156         result = 0;
1157     }
1158
1159     *str = s;
1160
1161     return result;
1162 }
1163
1164 int NextTimeControlFromString( char ** str, long * value )
1165 {
1166     long temp;
1167     int result = NextIntegerFromString( str, &temp );
1168
1169     if( result == 0 ) {
1170         *value = temp * 60; /* Minutes */
1171         if( **str == ':' ) {
1172             (*str)++;
1173             result = NextIntegerFromString( str, &temp );
1174             *value += temp; /* Seconds */
1175         }
1176     }
1177
1178     return result;
1179 }
1180
1181 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1182 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1183     int result = -1, type = 0; long temp, temp2;
1184
1185     if(**str != ':') return -1; // old params remain in force!
1186     (*str)++;
1187     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1188     if( NextIntegerFromString( str, &temp ) ) return -1;
1189     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1190
1191     if(**str != '/') {
1192         /* time only: incremental or sudden-death time control */
1193         if(**str == '+') { /* increment follows; read it */
1194             (*str)++;
1195             if(**str == '!') type = *(*str)++; // Bronstein TC
1196             if(result = NextIntegerFromString( str, &temp2)) return -1;
1197             *inc = temp2 * 1000;
1198             if(**str == '.') { // read fraction of increment
1199                 char *start = ++(*str);
1200                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1201                 temp2 *= 1000;
1202                 while(start++ < *str) temp2 /= 10;
1203                 *inc += temp2;
1204             }
1205         } else *inc = 0;
1206         *moves = 0; *tc = temp * 1000; *incType = type;
1207         return 0;
1208     }
1209
1210     (*str)++; /* classical time control */
1211     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1212
1213     if(result == 0) {
1214         *moves = temp;
1215         *tc    = temp2 * 1000;
1216         *inc   = 0;
1217         *incType = type;
1218     }
1219     return result;
1220 }
1221
1222 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1223 {   /* [HGM] get time to add from the multi-session time-control string */
1224     int incType, moves=1; /* kludge to force reading of first session */
1225     long time, increment;
1226     char *s = tcString;
1227
1228     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1229     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1230     do {
1231         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1232         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1233         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1234         if(movenr == -1) return time;    /* last move before new session     */
1235         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1236         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1237         if(!moves) return increment;     /* current session is incremental   */
1238         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1239     } while(movenr >= -1);               /* try again for next session       */
1240
1241     return 0; // no new time quota on this move
1242 }
1243
1244 int
1245 ParseTimeControl(tc, ti, mps)
1246      char *tc;
1247      float ti;
1248      int mps;
1249 {
1250   long tc1;
1251   long tc2;
1252   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1253   int min, sec=0;
1254
1255   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1256   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1257       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1258   if(ti > 0) {
1259
1260     if(mps)
1261       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1262     else 
1263       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1264   } else {
1265     if(mps)
1266       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1267     else 
1268       snprintf(buf, MSG_SIZ, ":%s", mytc);
1269   }
1270   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1271   
1272   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1273     return FALSE;
1274   }
1275
1276   if( *tc == '/' ) {
1277     /* Parse second time control */
1278     tc++;
1279
1280     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1281       return FALSE;
1282     }
1283
1284     if( tc2 == 0 ) {
1285       return FALSE;
1286     }
1287
1288     timeControl_2 = tc2 * 1000;
1289   }
1290   else {
1291     timeControl_2 = 0;
1292   }
1293
1294   if( tc1 == 0 ) {
1295     return FALSE;
1296   }
1297
1298   timeControl = tc1 * 1000;
1299
1300   if (ti >= 0) {
1301     timeIncrement = ti * 1000;  /* convert to ms */
1302     movesPerSession = 0;
1303   } else {
1304     timeIncrement = 0;
1305     movesPerSession = mps;
1306   }
1307   return TRUE;
1308 }
1309
1310 void
1311 InitBackEnd2()
1312 {
1313     if (appData.debugMode) {
1314         fprintf(debugFP, "%s\n", programVersion);
1315     }
1316
1317     set_cont_sequence(appData.wrapContSeq);
1318     if (appData.matchGames > 0) {
1319         appData.matchMode = TRUE;
1320     } else if (appData.matchMode) {
1321         appData.matchGames = 1;
1322     }
1323     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1324         appData.matchGames = appData.sameColorGames;
1325     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1326         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1327         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1328     }
1329     Reset(TRUE, FALSE);
1330     if (appData.noChessProgram || first.protocolVersion == 1) {
1331       InitBackEnd3();
1332     } else {
1333       /* kludge: allow timeout for initial "feature" commands */
1334       FreezeUI();
1335       DisplayMessage("", _("Starting chess program"));
1336       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1337     }
1338 }
1339
1340 int
1341 CalculateIndex(int index, int gameNr)
1342 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1343     int res;
1344     if(index > 0) return index; // fixed nmber
1345     if(index == 0) return 1;
1346     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1347     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1348     return res;
1349 }
1350
1351 int
1352 LoadGameOrPosition(int gameNr)
1353 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1354     if (*appData.loadGameFile != NULLCHAR) {
1355         if (!LoadGameFromFile(appData.loadGameFile,
1356                 CalculateIndex(appData.loadGameIndex, gameNr),
1357                               appData.loadGameFile, FALSE)) {
1358             DisplayFatalError(_("Bad game file"), 0, 1);
1359             return 0;
1360         }
1361     } else if (*appData.loadPositionFile != NULLCHAR) {
1362         if (!LoadPositionFromFile(appData.loadPositionFile,
1363                 CalculateIndex(appData.loadPositionIndex, gameNr),
1364                                   appData.loadPositionFile)) {
1365             DisplayFatalError(_("Bad position file"), 0, 1);
1366             return 0;
1367         }
1368     }
1369     return 1;
1370 }
1371
1372 void
1373 ReserveGame(int gameNr, char resChar)
1374 {
1375     FILE *tf = fopen(appData.tourneyFile, "r+");
1376     char *p, *q, c, buf[MSG_SIZ];
1377     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1378     safeStrCpy(buf, lastMsg, MSG_SIZ);
1379     DisplayMessage(_("Pick new game"), "");
1380     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1381     ParseArgsFromFile(tf);
1382     p = q = appData.results;
1383     if(appData.debugMode) {
1384       char *r = appData.participants;
1385       fprintf(debugFP, "results = '%s'\n", p);
1386       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1387       fprintf(debugFP, "\n");
1388     }
1389     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1390     nextGame = q - p;
1391     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1392     safeStrCpy(q, p, strlen(p) + 2);
1393     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1394     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1395     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1396         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1397         q[nextGame] = '*';
1398     }
1399     fseek(tf, -(strlen(p)+4), SEEK_END);
1400     c = fgetc(tf);
1401     if(c != '"') // depending on DOS or Unix line endings we can be one off
1402          fseek(tf, -(strlen(p)+2), SEEK_END);
1403     else fseek(tf, -(strlen(p)+3), SEEK_END);
1404     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1405     DisplayMessage(buf, "");
1406     free(p); appData.results = q;
1407     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1408        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1409         UnloadEngine(&first);  // next game belongs to other pairing;
1410         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1411     }
1412 }
1413
1414 void
1415 MatchEvent(int mode)
1416 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1417         int dummy;
1418         if(matchMode) { // already in match mode: switch it off
1419             abortMatch = TRUE;
1420             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1421             return;
1422         }
1423 //      if(gameMode != BeginningOfGame) {
1424 //          DisplayError(_("You can only start a match from the initial position."), 0);
1425 //          return;
1426 //      }
1427         abortMatch = FALSE;
1428         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1429         /* Set up machine vs. machine match */
1430         nextGame = 0;
1431         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1432         if(appData.tourneyFile[0]) {
1433             ReserveGame(-1, 0);
1434             if(nextGame > appData.matchGames) {
1435                 char buf[MSG_SIZ];
1436                 if(strchr(appData.results, '*') == NULL) {
1437                     FILE *f;
1438                     appData.tourneyCycles++;
1439                     if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1440                         fclose(f);
1441                         NextTourneyGame(-1, &dummy);
1442                         ReserveGame(-1, 0);
1443                         if(nextGame <= appData.matchGames) {
1444                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1445                             matchMode = mode;
1446                             ScheduleDelayedEvent(NextMatchGame, 10000);
1447                             return;
1448                         }
1449                     }
1450                 }
1451                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1452                 DisplayError(buf, 0);
1453                 appData.tourneyFile[0] = 0;
1454                 return;
1455             }
1456         } else
1457         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1458             DisplayFatalError(_("Can't have a match with no chess programs"),
1459                               0, 2);
1460             return;
1461         }
1462         matchMode = mode;
1463         matchGame = roundNr = 1;
1464         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1465         NextMatchGame();
1466 }
1467
1468 void
1469 InitBackEnd3 P((void))
1470 {
1471     GameMode initialMode;
1472     char buf[MSG_SIZ];
1473     int err, len;
1474
1475     InitChessProgram(&first, startedFromSetupPosition);
1476
1477     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1478         free(programVersion);
1479         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1480         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1481     }
1482
1483     if (appData.icsActive) {
1484 #ifdef WIN32
1485         /* [DM] Make a console window if needed [HGM] merged ifs */
1486         ConsoleCreate();
1487 #endif
1488         err = establish();
1489         if (err != 0)
1490           {
1491             if (*appData.icsCommPort != NULLCHAR)
1492               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1493                              appData.icsCommPort);
1494             else
1495               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1496                         appData.icsHost, appData.icsPort);
1497
1498             if( (len > MSG_SIZ) && appData.debugMode )
1499               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1500
1501             DisplayFatalError(buf, err, 1);
1502             return;
1503         }
1504         SetICSMode();
1505         telnetISR =
1506           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1507         fromUserISR =
1508           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1509         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1510             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1511     } else if (appData.noChessProgram) {
1512         SetNCPMode();
1513     } else {
1514         SetGNUMode();
1515     }
1516
1517     if (*appData.cmailGameName != NULLCHAR) {
1518         SetCmailMode();
1519         OpenLoopback(&cmailPR);
1520         cmailISR =
1521           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1522     }
1523
1524     ThawUI();
1525     DisplayMessage("", "");
1526     if (StrCaseCmp(appData.initialMode, "") == 0) {
1527       initialMode = BeginningOfGame;
1528       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1529         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1530         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1531         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1532         ModeHighlight();
1533       }
1534     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1535       initialMode = TwoMachinesPlay;
1536     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1537       initialMode = AnalyzeFile;
1538     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1539       initialMode = AnalyzeMode;
1540     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1541       initialMode = MachinePlaysWhite;
1542     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1543       initialMode = MachinePlaysBlack;
1544     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1545       initialMode = EditGame;
1546     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1547       initialMode = EditPosition;
1548     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1549       initialMode = Training;
1550     } else {
1551       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1552       if( (len > MSG_SIZ) && appData.debugMode )
1553         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1554
1555       DisplayFatalError(buf, 0, 2);
1556       return;
1557     }
1558
1559     if (appData.matchMode) {
1560         if(appData.tourneyFile[0]) { // start tourney from command line
1561             FILE *f;
1562             if(f = fopen(appData.tourneyFile, "r")) {
1563                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1564                 fclose(f);
1565                 appData.clockMode = TRUE;
1566                 SetGNUMode();
1567             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1568         }
1569         MatchEvent(TRUE);
1570     } else if (*appData.cmailGameName != NULLCHAR) {
1571         /* Set up cmail mode */
1572         ReloadCmailMsgEvent(TRUE);
1573     } else {
1574         /* Set up other modes */
1575         if (initialMode == AnalyzeFile) {
1576           if (*appData.loadGameFile == NULLCHAR) {
1577             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1578             return;
1579           }
1580         }
1581         if (*appData.loadGameFile != NULLCHAR) {
1582             (void) LoadGameFromFile(appData.loadGameFile,
1583                                     appData.loadGameIndex,
1584                                     appData.loadGameFile, TRUE);
1585         } else if (*appData.loadPositionFile != NULLCHAR) {
1586             (void) LoadPositionFromFile(appData.loadPositionFile,
1587                                         appData.loadPositionIndex,
1588                                         appData.loadPositionFile);
1589             /* [HGM] try to make self-starting even after FEN load */
1590             /* to allow automatic setup of fairy variants with wtm */
1591             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1592                 gameMode = BeginningOfGame;
1593                 setboardSpoiledMachineBlack = 1;
1594             }
1595             /* [HGM] loadPos: make that every new game uses the setup */
1596             /* from file as long as we do not switch variant          */
1597             if(!blackPlaysFirst) {
1598                 startedFromPositionFile = TRUE;
1599                 CopyBoard(filePosition, boards[0]);
1600             }
1601         }
1602         if (initialMode == AnalyzeMode) {
1603           if (appData.noChessProgram) {
1604             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1605             return;
1606           }
1607           if (appData.icsActive) {
1608             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1609             return;
1610           }
1611           AnalyzeModeEvent();
1612         } else if (initialMode == AnalyzeFile) {
1613           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1614           ShowThinkingEvent();
1615           AnalyzeFileEvent();
1616           AnalysisPeriodicEvent(1);
1617         } else if (initialMode == MachinePlaysWhite) {
1618           if (appData.noChessProgram) {
1619             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1620                               0, 2);
1621             return;
1622           }
1623           if (appData.icsActive) {
1624             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1625                               0, 2);
1626             return;
1627           }
1628           MachineWhiteEvent();
1629         } else if (initialMode == MachinePlaysBlack) {
1630           if (appData.noChessProgram) {
1631             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1632                               0, 2);
1633             return;
1634           }
1635           if (appData.icsActive) {
1636             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1637                               0, 2);
1638             return;
1639           }
1640           MachineBlackEvent();
1641         } else if (initialMode == TwoMachinesPlay) {
1642           if (appData.noChessProgram) {
1643             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1644                               0, 2);
1645             return;
1646           }
1647           if (appData.icsActive) {
1648             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1649                               0, 2);
1650             return;
1651           }
1652           TwoMachinesEvent();
1653         } else if (initialMode == EditGame) {
1654           EditGameEvent();
1655         } else if (initialMode == EditPosition) {
1656           EditPositionEvent();
1657         } else if (initialMode == Training) {
1658           if (*appData.loadGameFile == NULLCHAR) {
1659             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1660             return;
1661           }
1662           TrainingEvent();
1663         }
1664     }
1665 }
1666
1667 /*
1668  * Establish will establish a contact to a remote host.port.
1669  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1670  *  used to talk to the host.
1671  * Returns 0 if okay, error code if not.
1672  */
1673 int
1674 establish()
1675 {
1676     char buf[MSG_SIZ];
1677
1678     if (*appData.icsCommPort != NULLCHAR) {
1679         /* Talk to the host through a serial comm port */
1680         return OpenCommPort(appData.icsCommPort, &icsPR);
1681
1682     } else if (*appData.gateway != NULLCHAR) {
1683         if (*appData.remoteShell == NULLCHAR) {
1684             /* Use the rcmd protocol to run telnet program on a gateway host */
1685             snprintf(buf, sizeof(buf), "%s %s %s",
1686                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1687             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1688
1689         } else {
1690             /* Use the rsh program to run telnet program on a gateway host */
1691             if (*appData.remoteUser == NULLCHAR) {
1692                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1693                         appData.gateway, appData.telnetProgram,
1694                         appData.icsHost, appData.icsPort);
1695             } else {
1696                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1697                         appData.remoteShell, appData.gateway,
1698                         appData.remoteUser, appData.telnetProgram,
1699                         appData.icsHost, appData.icsPort);
1700             }
1701             return StartChildProcess(buf, "", &icsPR);
1702
1703         }
1704     } else if (appData.useTelnet) {
1705         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1706
1707     } else {
1708         /* TCP socket interface differs somewhat between
1709            Unix and NT; handle details in the front end.
1710            */
1711         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1712     }
1713 }
1714
1715 void EscapeExpand(char *p, char *q)
1716 {       // [HGM] initstring: routine to shape up string arguments
1717         while(*p++ = *q++) if(p[-1] == '\\')
1718             switch(*q++) {
1719                 case 'n': p[-1] = '\n'; break;
1720                 case 'r': p[-1] = '\r'; break;
1721                 case 't': p[-1] = '\t'; break;
1722                 case '\\': p[-1] = '\\'; break;
1723                 case 0: *p = 0; return;
1724                 default: p[-1] = q[-1]; break;
1725             }
1726 }
1727
1728 void
1729 show_bytes(fp, buf, count)
1730      FILE *fp;
1731      char *buf;
1732      int count;
1733 {
1734     while (count--) {
1735         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1736             fprintf(fp, "\\%03o", *buf & 0xff);
1737         } else {
1738             putc(*buf, fp);
1739         }
1740         buf++;
1741     }
1742     fflush(fp);
1743 }
1744
1745 /* Returns an errno value */
1746 int
1747 OutputMaybeTelnet(pr, message, count, outError)
1748      ProcRef pr;
1749      char *message;
1750      int count;
1751      int *outError;
1752 {
1753     char buf[8192], *p, *q, *buflim;
1754     int left, newcount, outcount;
1755
1756     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1757         *appData.gateway != NULLCHAR) {
1758         if (appData.debugMode) {
1759             fprintf(debugFP, ">ICS: ");
1760             show_bytes(debugFP, message, count);
1761             fprintf(debugFP, "\n");
1762         }
1763         return OutputToProcess(pr, message, count, outError);
1764     }
1765
1766     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1767     p = message;
1768     q = buf;
1769     left = count;
1770     newcount = 0;
1771     while (left) {
1772         if (q >= buflim) {
1773             if (appData.debugMode) {
1774                 fprintf(debugFP, ">ICS: ");
1775                 show_bytes(debugFP, buf, newcount);
1776                 fprintf(debugFP, "\n");
1777             }
1778             outcount = OutputToProcess(pr, buf, newcount, outError);
1779             if (outcount < newcount) return -1; /* to be sure */
1780             q = buf;
1781             newcount = 0;
1782         }
1783         if (*p == '\n') {
1784             *q++ = '\r';
1785             newcount++;
1786         } else if (((unsigned char) *p) == TN_IAC) {
1787             *q++ = (char) TN_IAC;
1788             newcount ++;
1789         }
1790         *q++ = *p++;
1791         newcount++;
1792         left--;
1793     }
1794     if (appData.debugMode) {
1795         fprintf(debugFP, ">ICS: ");
1796         show_bytes(debugFP, buf, newcount);
1797         fprintf(debugFP, "\n");
1798     }
1799     outcount = OutputToProcess(pr, buf, newcount, outError);
1800     if (outcount < newcount) return -1; /* to be sure */
1801     return count;
1802 }
1803
1804 void
1805 read_from_player(isr, closure, message, count, error)
1806      InputSourceRef isr;
1807      VOIDSTAR closure;
1808      char *message;
1809      int count;
1810      int error;
1811 {
1812     int outError, outCount;
1813     static int gotEof = 0;
1814
1815     /* Pass data read from player on to ICS */
1816     if (count > 0) {
1817         gotEof = 0;
1818         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1819         if (outCount < count) {
1820             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1821         }
1822     } else if (count < 0) {
1823         RemoveInputSource(isr);
1824         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1825     } else if (gotEof++ > 0) {
1826         RemoveInputSource(isr);
1827         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1828     }
1829 }
1830
1831 void
1832 KeepAlive()
1833 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1834     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1835     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1836     SendToICS("date\n");
1837     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1838 }
1839
1840 /* added routine for printf style output to ics */
1841 void ics_printf(char *format, ...)
1842 {
1843     char buffer[MSG_SIZ];
1844     va_list args;
1845
1846     va_start(args, format);
1847     vsnprintf(buffer, sizeof(buffer), format, args);
1848     buffer[sizeof(buffer)-1] = '\0';
1849     SendToICS(buffer);
1850     va_end(args);
1851 }
1852
1853 void
1854 SendToICS(s)
1855      char *s;
1856 {
1857     int count, outCount, outError;
1858
1859     if (icsPR == NULL) return;
1860
1861     count = strlen(s);
1862     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1863     if (outCount < count) {
1864         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1865     }
1866 }
1867
1868 /* This is used for sending logon scripts to the ICS. Sending
1869    without a delay causes problems when using timestamp on ICC
1870    (at least on my machine). */
1871 void
1872 SendToICSDelayed(s,msdelay)
1873      char *s;
1874      long msdelay;
1875 {
1876     int count, outCount, outError;
1877
1878     if (icsPR == NULL) return;
1879
1880     count = strlen(s);
1881     if (appData.debugMode) {
1882         fprintf(debugFP, ">ICS: ");
1883         show_bytes(debugFP, s, count);
1884         fprintf(debugFP, "\n");
1885     }
1886     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1887                                       msdelay);
1888     if (outCount < count) {
1889         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1890     }
1891 }
1892
1893
1894 /* Remove all highlighting escape sequences in s
1895    Also deletes any suffix starting with '('
1896    */
1897 char *
1898 StripHighlightAndTitle(s)
1899      char *s;
1900 {
1901     static char retbuf[MSG_SIZ];
1902     char *p = retbuf;
1903
1904     while (*s != NULLCHAR) {
1905         while (*s == '\033') {
1906             while (*s != NULLCHAR && !isalpha(*s)) s++;
1907             if (*s != NULLCHAR) s++;
1908         }
1909         while (*s != NULLCHAR && *s != '\033') {
1910             if (*s == '(' || *s == '[') {
1911                 *p = NULLCHAR;
1912                 return retbuf;
1913             }
1914             *p++ = *s++;
1915         }
1916     }
1917     *p = NULLCHAR;
1918     return retbuf;
1919 }
1920
1921 /* Remove all highlighting escape sequences in s */
1922 char *
1923 StripHighlight(s)
1924      char *s;
1925 {
1926     static char retbuf[MSG_SIZ];
1927     char *p = retbuf;
1928
1929     while (*s != NULLCHAR) {
1930         while (*s == '\033') {
1931             while (*s != NULLCHAR && !isalpha(*s)) s++;
1932             if (*s != NULLCHAR) s++;
1933         }
1934         while (*s != NULLCHAR && *s != '\033') {
1935             *p++ = *s++;
1936         }
1937     }
1938     *p = NULLCHAR;
1939     return retbuf;
1940 }
1941
1942 char *variantNames[] = VARIANT_NAMES;
1943 char *
1944 VariantName(v)
1945      VariantClass v;
1946 {
1947     return variantNames[v];
1948 }
1949
1950
1951 /* Identify a variant from the strings the chess servers use or the
1952    PGN Variant tag names we use. */
1953 VariantClass
1954 StringToVariant(e)
1955      char *e;
1956 {
1957     char *p;
1958     int wnum = -1;
1959     VariantClass v = VariantNormal;
1960     int i, found = FALSE;
1961     char buf[MSG_SIZ];
1962     int len;
1963
1964     if (!e) return v;
1965
1966     /* [HGM] skip over optional board-size prefixes */
1967     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1968         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1969         while( *e++ != '_');
1970     }
1971
1972     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1973         v = VariantNormal;
1974         found = TRUE;
1975     } else
1976     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1977       if (StrCaseStr(e, variantNames[i])) {
1978         v = (VariantClass) i;
1979         found = TRUE;
1980         break;
1981       }
1982     }
1983
1984     if (!found) {
1985       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1986           || StrCaseStr(e, "wild/fr")
1987           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1988         v = VariantFischeRandom;
1989       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1990                  (i = 1, p = StrCaseStr(e, "w"))) {
1991         p += i;
1992         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1993         if (isdigit(*p)) {
1994           wnum = atoi(p);
1995         } else {
1996           wnum = -1;
1997         }
1998         switch (wnum) {
1999         case 0: /* FICS only, actually */
2000         case 1:
2001           /* Castling legal even if K starts on d-file */
2002           v = VariantWildCastle;
2003           break;
2004         case 2:
2005         case 3:
2006         case 4:
2007           /* Castling illegal even if K & R happen to start in
2008              normal positions. */
2009           v = VariantNoCastle;
2010           break;
2011         case 5:
2012         case 7:
2013         case 8:
2014         case 10:
2015         case 11:
2016         case 12:
2017         case 13:
2018         case 14:
2019         case 15:
2020         case 18:
2021         case 19:
2022           /* Castling legal iff K & R start in normal positions */
2023           v = VariantNormal;
2024           break;
2025         case 6:
2026         case 20:
2027         case 21:
2028           /* Special wilds for position setup; unclear what to do here */
2029           v = VariantLoadable;
2030           break;
2031         case 9:
2032           /* Bizarre ICC game */
2033           v = VariantTwoKings;
2034           break;
2035         case 16:
2036           v = VariantKriegspiel;
2037           break;
2038         case 17:
2039           v = VariantLosers;
2040           break;
2041         case 22:
2042           v = VariantFischeRandom;
2043           break;
2044         case 23:
2045           v = VariantCrazyhouse;
2046           break;
2047         case 24:
2048           v = VariantBughouse;
2049           break;
2050         case 25:
2051           v = Variant3Check;
2052           break;
2053         case 26:
2054           /* Not quite the same as FICS suicide! */
2055           v = VariantGiveaway;
2056           break;
2057         case 27:
2058           v = VariantAtomic;
2059           break;
2060         case 28:
2061           v = VariantShatranj;
2062           break;
2063
2064         /* Temporary names for future ICC types.  The name *will* change in
2065            the next xboard/WinBoard release after ICC defines it. */
2066         case 29:
2067           v = Variant29;
2068           break;
2069         case 30:
2070           v = Variant30;
2071           break;
2072         case 31:
2073           v = Variant31;
2074           break;
2075         case 32:
2076           v = Variant32;
2077           break;
2078         case 33:
2079           v = Variant33;
2080           break;
2081         case 34:
2082           v = Variant34;
2083           break;
2084         case 35:
2085           v = Variant35;
2086           break;
2087         case 36:
2088           v = Variant36;
2089           break;
2090         case 37:
2091           v = VariantShogi;
2092           break;
2093         case 38:
2094           v = VariantXiangqi;
2095           break;
2096         case 39:
2097           v = VariantCourier;
2098           break;
2099         case 40:
2100           v = VariantGothic;
2101           break;
2102         case 41:
2103           v = VariantCapablanca;
2104           break;
2105         case 42:
2106           v = VariantKnightmate;
2107           break;
2108         case 43:
2109           v = VariantFairy;
2110           break;
2111         case 44:
2112           v = VariantCylinder;
2113           break;
2114         case 45:
2115           v = VariantFalcon;
2116           break;
2117         case 46:
2118           v = VariantCapaRandom;
2119           break;
2120         case 47:
2121           v = VariantBerolina;
2122           break;
2123         case 48:
2124           v = VariantJanus;
2125           break;
2126         case 49:
2127           v = VariantSuper;
2128           break;
2129         case 50:
2130           v = VariantGreat;
2131           break;
2132         case -1:
2133           /* Found "wild" or "w" in the string but no number;
2134              must assume it's normal chess. */
2135           v = VariantNormal;
2136           break;
2137         default:
2138           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2139           if( (len > MSG_SIZ) && appData.debugMode )
2140             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2141
2142           DisplayError(buf, 0);
2143           v = VariantUnknown;
2144           break;
2145         }
2146       }
2147     }
2148     if (appData.debugMode) {
2149       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2150               e, wnum, VariantName(v));
2151     }
2152     return v;
2153 }
2154
2155 static int leftover_start = 0, leftover_len = 0;
2156 char star_match[STAR_MATCH_N][MSG_SIZ];
2157
2158 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2159    advance *index beyond it, and set leftover_start to the new value of
2160    *index; else return FALSE.  If pattern contains the character '*', it
2161    matches any sequence of characters not containing '\r', '\n', or the
2162    character following the '*' (if any), and the matched sequence(s) are
2163    copied into star_match.
2164    */
2165 int
2166 looking_at(buf, index, pattern)
2167      char *buf;
2168      int *index;
2169      char *pattern;
2170 {
2171     char *bufp = &buf[*index], *patternp = pattern;
2172     int star_count = 0;
2173     char *matchp = star_match[0];
2174
2175     for (;;) {
2176         if (*patternp == NULLCHAR) {
2177             *index = leftover_start = bufp - buf;
2178             *matchp = NULLCHAR;
2179             return TRUE;
2180         }
2181         if (*bufp == NULLCHAR) return FALSE;
2182         if (*patternp == '*') {
2183             if (*bufp == *(patternp + 1)) {
2184                 *matchp = NULLCHAR;
2185                 matchp = star_match[++star_count];
2186                 patternp += 2;
2187                 bufp++;
2188                 continue;
2189             } else if (*bufp == '\n' || *bufp == '\r') {
2190                 patternp++;
2191                 if (*patternp == NULLCHAR)
2192                   continue;
2193                 else
2194                   return FALSE;
2195             } else {
2196                 *matchp++ = *bufp++;
2197                 continue;
2198             }
2199         }
2200         if (*patternp != *bufp) return FALSE;
2201         patternp++;
2202         bufp++;
2203     }
2204 }
2205
2206 void
2207 SendToPlayer(data, length)
2208      char *data;
2209      int length;
2210 {
2211     int error, outCount;
2212     outCount = OutputToProcess(NoProc, data, length, &error);
2213     if (outCount < length) {
2214         DisplayFatalError(_("Error writing to display"), error, 1);
2215     }
2216 }
2217
2218 void
2219 PackHolding(packed, holding)
2220      char packed[];
2221      char *holding;
2222 {
2223     char *p = holding;
2224     char *q = packed;
2225     int runlength = 0;
2226     int curr = 9999;
2227     do {
2228         if (*p == curr) {
2229             runlength++;
2230         } else {
2231             switch (runlength) {
2232               case 0:
2233                 break;
2234               case 1:
2235                 *q++ = curr;
2236                 break;
2237               case 2:
2238                 *q++ = curr;
2239                 *q++ = curr;
2240                 break;
2241               default:
2242                 sprintf(q, "%d", runlength);
2243                 while (*q) q++;
2244                 *q++ = curr;
2245                 break;
2246             }
2247             runlength = 1;
2248             curr = *p;
2249         }
2250     } while (*p++);
2251     *q = NULLCHAR;
2252 }
2253
2254 /* Telnet protocol requests from the front end */
2255 void
2256 TelnetRequest(ddww, option)
2257      unsigned char ddww, option;
2258 {
2259     unsigned char msg[3];
2260     int outCount, outError;
2261
2262     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2263
2264     if (appData.debugMode) {
2265         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2266         switch (ddww) {
2267           case TN_DO:
2268             ddwwStr = "DO";
2269             break;
2270           case TN_DONT:
2271             ddwwStr = "DONT";
2272             break;
2273           case TN_WILL:
2274             ddwwStr = "WILL";
2275             break;
2276           case TN_WONT:
2277             ddwwStr = "WONT";
2278             break;
2279           default:
2280             ddwwStr = buf1;
2281             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2282             break;
2283         }
2284         switch (option) {
2285           case TN_ECHO:
2286             optionStr = "ECHO";
2287             break;
2288           default:
2289             optionStr = buf2;
2290             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2291             break;
2292         }
2293         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2294     }
2295     msg[0] = TN_IAC;
2296     msg[1] = ddww;
2297     msg[2] = option;
2298     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2299     if (outCount < 3) {
2300         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2301     }
2302 }
2303
2304 void
2305 DoEcho()
2306 {
2307     if (!appData.icsActive) return;
2308     TelnetRequest(TN_DO, TN_ECHO);
2309 }
2310
2311 void
2312 DontEcho()
2313 {
2314     if (!appData.icsActive) return;
2315     TelnetRequest(TN_DONT, TN_ECHO);
2316 }
2317
2318 void
2319 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2320 {
2321     /* put the holdings sent to us by the server on the board holdings area */
2322     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2323     char p;
2324     ChessSquare piece;
2325
2326     if(gameInfo.holdingsWidth < 2)  return;
2327     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2328         return; // prevent overwriting by pre-board holdings
2329
2330     if( (int)lowestPiece >= BlackPawn ) {
2331         holdingsColumn = 0;
2332         countsColumn = 1;
2333         holdingsStartRow = BOARD_HEIGHT-1;
2334         direction = -1;
2335     } else {
2336         holdingsColumn = BOARD_WIDTH-1;
2337         countsColumn = BOARD_WIDTH-2;
2338         holdingsStartRow = 0;
2339         direction = 1;
2340     }
2341
2342     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2343         board[i][holdingsColumn] = EmptySquare;
2344         board[i][countsColumn]   = (ChessSquare) 0;
2345     }
2346     while( (p=*holdings++) != NULLCHAR ) {
2347         piece = CharToPiece( ToUpper(p) );
2348         if(piece == EmptySquare) continue;
2349         /*j = (int) piece - (int) WhitePawn;*/
2350         j = PieceToNumber(piece);
2351         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2352         if(j < 0) continue;               /* should not happen */
2353         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2354         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2355         board[holdingsStartRow+j*direction][countsColumn]++;
2356     }
2357 }
2358
2359
2360 void
2361 VariantSwitch(Board board, VariantClass newVariant)
2362 {
2363    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2364    static Board oldBoard;
2365
2366    startedFromPositionFile = FALSE;
2367    if(gameInfo.variant == newVariant) return;
2368
2369    /* [HGM] This routine is called each time an assignment is made to
2370     * gameInfo.variant during a game, to make sure the board sizes
2371     * are set to match the new variant. If that means adding or deleting
2372     * holdings, we shift the playing board accordingly
2373     * This kludge is needed because in ICS observe mode, we get boards
2374     * of an ongoing game without knowing the variant, and learn about the
2375     * latter only later. This can be because of the move list we requested,
2376     * in which case the game history is refilled from the beginning anyway,
2377     * but also when receiving holdings of a crazyhouse game. In the latter
2378     * case we want to add those holdings to the already received position.
2379     */
2380
2381
2382    if (appData.debugMode) {
2383      fprintf(debugFP, "Switch board from %s to %s\n",
2384              VariantName(gameInfo.variant), VariantName(newVariant));
2385      setbuf(debugFP, NULL);
2386    }
2387    shuffleOpenings = 0;       /* [HGM] shuffle */
2388    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2389    switch(newVariant)
2390      {
2391      case VariantShogi:
2392        newWidth = 9;  newHeight = 9;
2393        gameInfo.holdingsSize = 7;
2394      case VariantBughouse:
2395      case VariantCrazyhouse:
2396        newHoldingsWidth = 2; break;
2397      case VariantGreat:
2398        newWidth = 10;
2399      case VariantSuper:
2400        newHoldingsWidth = 2;
2401        gameInfo.holdingsSize = 8;
2402        break;
2403      case VariantGothic:
2404      case VariantCapablanca:
2405      case VariantCapaRandom:
2406        newWidth = 10;
2407      default:
2408        newHoldingsWidth = gameInfo.holdingsSize = 0;
2409      };
2410
2411    if(newWidth  != gameInfo.boardWidth  ||
2412       newHeight != gameInfo.boardHeight ||
2413       newHoldingsWidth != gameInfo.holdingsWidth ) {
2414
2415      /* shift position to new playing area, if needed */
2416      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2417        for(i=0; i<BOARD_HEIGHT; i++)
2418          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2419            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2420              board[i][j];
2421        for(i=0; i<newHeight; i++) {
2422          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2423          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2424        }
2425      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2426        for(i=0; i<BOARD_HEIGHT; i++)
2427          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2428            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2429              board[i][j];
2430      }
2431      gameInfo.boardWidth  = newWidth;
2432      gameInfo.boardHeight = newHeight;
2433      gameInfo.holdingsWidth = newHoldingsWidth;
2434      gameInfo.variant = newVariant;
2435      InitDrawingSizes(-2, 0);
2436    } else gameInfo.variant = newVariant;
2437    CopyBoard(oldBoard, board);   // remember correctly formatted board
2438      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2439    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2440 }
2441
2442 static int loggedOn = FALSE;
2443
2444 /*-- Game start info cache: --*/
2445 int gs_gamenum;
2446 char gs_kind[MSG_SIZ];
2447 static char player1Name[128] = "";
2448 static char player2Name[128] = "";
2449 static char cont_seq[] = "\n\\   ";
2450 static int player1Rating = -1;
2451 static int player2Rating = -1;
2452 /*----------------------------*/
2453
2454 ColorClass curColor = ColorNormal;
2455 int suppressKibitz = 0;
2456
2457 // [HGM] seekgraph
2458 Boolean soughtPending = FALSE;
2459 Boolean seekGraphUp;
2460 #define MAX_SEEK_ADS 200
2461 #define SQUARE 0x80
2462 char *seekAdList[MAX_SEEK_ADS];
2463 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2464 float tcList[MAX_SEEK_ADS];
2465 char colorList[MAX_SEEK_ADS];
2466 int nrOfSeekAds = 0;
2467 int minRating = 1010, maxRating = 2800;
2468 int hMargin = 10, vMargin = 20, h, w;
2469 extern int squareSize, lineGap;
2470
2471 void
2472 PlotSeekAd(int i)
2473 {
2474         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2475         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2476         if(r < minRating+100 && r >=0 ) r = minRating+100;
2477         if(r > maxRating) r = maxRating;
2478         if(tc < 1.) tc = 1.;
2479         if(tc > 95.) tc = 95.;
2480         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2481         y = ((double)r - minRating)/(maxRating - minRating)
2482             * (h-vMargin-squareSize/8-1) + vMargin;
2483         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2484         if(strstr(seekAdList[i], " u ")) color = 1;
2485         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2486            !strstr(seekAdList[i], "bullet") &&
2487            !strstr(seekAdList[i], "blitz") &&
2488            !strstr(seekAdList[i], "standard") ) color = 2;
2489         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2490         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2491 }
2492
2493 void
2494 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2495 {
2496         char buf[MSG_SIZ], *ext = "";
2497         VariantClass v = StringToVariant(type);
2498         if(strstr(type, "wild")) {
2499             ext = type + 4; // append wild number
2500             if(v == VariantFischeRandom) type = "chess960"; else
2501             if(v == VariantLoadable) type = "setup"; else
2502             type = VariantName(v);
2503         }
2504         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2505         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2506             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2507             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2508             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2509             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2510             seekNrList[nrOfSeekAds] = nr;
2511             zList[nrOfSeekAds] = 0;
2512             seekAdList[nrOfSeekAds++] = StrSave(buf);
2513             if(plot) PlotSeekAd(nrOfSeekAds-1);
2514         }
2515 }
2516
2517 void
2518 EraseSeekDot(int i)
2519 {
2520     int x = xList[i], y = yList[i], d=squareSize/4, k;
2521     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2522     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2523     // now replot every dot that overlapped
2524     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2525         int xx = xList[k], yy = yList[k];
2526         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2527             DrawSeekDot(xx, yy, colorList[k]);
2528     }
2529 }
2530
2531 void
2532 RemoveSeekAd(int nr)
2533 {
2534         int i;
2535         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2536             EraseSeekDot(i);
2537             if(seekAdList[i]) free(seekAdList[i]);
2538             seekAdList[i] = seekAdList[--nrOfSeekAds];
2539             seekNrList[i] = seekNrList[nrOfSeekAds];
2540             ratingList[i] = ratingList[nrOfSeekAds];
2541             colorList[i]  = colorList[nrOfSeekAds];
2542             tcList[i] = tcList[nrOfSeekAds];
2543             xList[i]  = xList[nrOfSeekAds];
2544             yList[i]  = yList[nrOfSeekAds];
2545             zList[i]  = zList[nrOfSeekAds];
2546             seekAdList[nrOfSeekAds] = NULL;
2547             break;
2548         }
2549 }
2550
2551 Boolean
2552 MatchSoughtLine(char *line)
2553 {
2554     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2555     int nr, base, inc, u=0; char dummy;
2556
2557     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2558        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2559        (u=1) &&
2560        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2561         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2562         // match: compact and save the line
2563         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2564         return TRUE;
2565     }
2566     return FALSE;
2567 }
2568
2569 int
2570 DrawSeekGraph()
2571 {
2572     int i;
2573     if(!seekGraphUp) return FALSE;
2574     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2575     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2576
2577     DrawSeekBackground(0, 0, w, h);
2578     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2579     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2580     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2581         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2582         yy = h-1-yy;
2583         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2584         if(i%500 == 0) {
2585             char buf[MSG_SIZ];
2586             snprintf(buf, MSG_SIZ, "%d", i);
2587             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2588         }
2589     }
2590     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2591     for(i=1; i<100; i+=(i<10?1:5)) {
2592         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2593         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2594         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2595             char buf[MSG_SIZ];
2596             snprintf(buf, MSG_SIZ, "%d", i);
2597             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2598         }
2599     }
2600     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2601     return TRUE;
2602 }
2603
2604 int SeekGraphClick(ClickType click, int x, int y, int moving)
2605 {
2606     static int lastDown = 0, displayed = 0, lastSecond;
2607     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2608         if(click == Release || moving) return FALSE;
2609         nrOfSeekAds = 0;
2610         soughtPending = TRUE;
2611         SendToICS(ics_prefix);
2612         SendToICS("sought\n"); // should this be "sought all"?
2613     } else { // issue challenge based on clicked ad
2614         int dist = 10000; int i, closest = 0, second = 0;
2615         for(i=0; i<nrOfSeekAds; i++) {
2616             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2617             if(d < dist) { dist = d; closest = i; }
2618             second += (d - zList[i] < 120); // count in-range ads
2619             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2620         }
2621         if(dist < 120) {
2622             char buf[MSG_SIZ];
2623             second = (second > 1);
2624             if(displayed != closest || second != lastSecond) {
2625                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2626                 lastSecond = second; displayed = closest;
2627             }
2628             if(click == Press) {
2629                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2630                 lastDown = closest;
2631                 return TRUE;
2632             } // on press 'hit', only show info
2633             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2634             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2635             SendToICS(ics_prefix);
2636             SendToICS(buf);
2637             return TRUE; // let incoming board of started game pop down the graph
2638         } else if(click == Release) { // release 'miss' is ignored
2639             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2640             if(moving == 2) { // right up-click
2641                 nrOfSeekAds = 0; // refresh graph
2642                 soughtPending = TRUE;
2643                 SendToICS(ics_prefix);
2644                 SendToICS("sought\n"); // should this be "sought all"?
2645             }
2646             return TRUE;
2647         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2648         // press miss or release hit 'pop down' seek graph
2649         seekGraphUp = FALSE;
2650         DrawPosition(TRUE, NULL);
2651     }
2652     return TRUE;
2653 }
2654
2655 void
2656 read_from_ics(isr, closure, data, count, error)
2657      InputSourceRef isr;
2658      VOIDSTAR closure;
2659      char *data;
2660      int count;
2661      int error;
2662 {
2663 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2664 #define STARTED_NONE 0
2665 #define STARTED_MOVES 1
2666 #define STARTED_BOARD 2
2667 #define STARTED_OBSERVE 3
2668 #define STARTED_HOLDINGS 4
2669 #define STARTED_CHATTER 5
2670 #define STARTED_COMMENT 6
2671 #define STARTED_MOVES_NOHIDE 7
2672
2673     static int started = STARTED_NONE;
2674     static char parse[20000];
2675     static int parse_pos = 0;
2676     static char buf[BUF_SIZE + 1];
2677     static int firstTime = TRUE, intfSet = FALSE;
2678     static ColorClass prevColor = ColorNormal;
2679     static int savingComment = FALSE;
2680     static int cmatch = 0; // continuation sequence match
2681     char *bp;
2682     char str[MSG_SIZ];
2683     int i, oldi;
2684     int buf_len;
2685     int next_out;
2686     int tkind;
2687     int backup;    /* [DM] For zippy color lines */
2688     char *p;
2689     char talker[MSG_SIZ]; // [HGM] chat
2690     int channel;
2691
2692     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2693
2694     if (appData.debugMode) {
2695       if (!error) {
2696         fprintf(debugFP, "<ICS: ");
2697         show_bytes(debugFP, data, count);
2698         fprintf(debugFP, "\n");
2699       }
2700     }
2701
2702     if (appData.debugMode) { int f = forwardMostMove;
2703         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2704                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2705                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2706     }
2707     if (count > 0) {
2708         /* If last read ended with a partial line that we couldn't parse,
2709            prepend it to the new read and try again. */
2710         if (leftover_len > 0) {
2711             for (i=0; i<leftover_len; i++)
2712               buf[i] = buf[leftover_start + i];
2713         }
2714
2715     /* copy new characters into the buffer */
2716     bp = buf + leftover_len;
2717     buf_len=leftover_len;
2718     for (i=0; i<count; i++)
2719     {
2720         // ignore these
2721         if (data[i] == '\r')
2722             continue;
2723
2724         // join lines split by ICS?
2725         if (!appData.noJoin)
2726         {
2727             /*
2728                 Joining just consists of finding matches against the
2729                 continuation sequence, and discarding that sequence
2730                 if found instead of copying it.  So, until a match
2731                 fails, there's nothing to do since it might be the
2732                 complete sequence, and thus, something we don't want
2733                 copied.
2734             */
2735             if (data[i] == cont_seq[cmatch])
2736             {
2737                 cmatch++;
2738                 if (cmatch == strlen(cont_seq))
2739                 {
2740                     cmatch = 0; // complete match.  just reset the counter
2741
2742                     /*
2743                         it's possible for the ICS to not include the space
2744                         at the end of the last word, making our [correct]
2745                         join operation fuse two separate words.  the server
2746                         does this when the space occurs at the width setting.
2747                     */
2748                     if (!buf_len || buf[buf_len-1] != ' ')
2749                     {
2750                         *bp++ = ' ';
2751                         buf_len++;
2752                     }
2753                 }
2754                 continue;
2755             }
2756             else if (cmatch)
2757             {
2758                 /*
2759                     match failed, so we have to copy what matched before
2760                     falling through and copying this character.  In reality,
2761                     this will only ever be just the newline character, but
2762                     it doesn't hurt to be precise.
2763                 */
2764                 strncpy(bp, cont_seq, cmatch);
2765                 bp += cmatch;
2766                 buf_len += cmatch;
2767                 cmatch = 0;
2768             }
2769         }
2770
2771         // copy this char
2772         *bp++ = data[i];
2773         buf_len++;
2774     }
2775
2776         buf[buf_len] = NULLCHAR;
2777 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2778         next_out = 0;
2779         leftover_start = 0;
2780
2781         i = 0;
2782         while (i < buf_len) {
2783             /* Deal with part of the TELNET option negotiation
2784                protocol.  We refuse to do anything beyond the
2785                defaults, except that we allow the WILL ECHO option,
2786                which ICS uses to turn off password echoing when we are
2787                directly connected to it.  We reject this option
2788                if localLineEditing mode is on (always on in xboard)
2789                and we are talking to port 23, which might be a real
2790                telnet server that will try to keep WILL ECHO on permanently.
2791              */
2792             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2793                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2794                 unsigned char option;
2795                 oldi = i;
2796                 switch ((unsigned char) buf[++i]) {
2797                   case TN_WILL:
2798                     if (appData.debugMode)
2799                       fprintf(debugFP, "\n<WILL ");
2800                     switch (option = (unsigned char) buf[++i]) {
2801                       case TN_ECHO:
2802                         if (appData.debugMode)
2803                           fprintf(debugFP, "ECHO ");
2804                         /* Reply only if this is a change, according
2805                            to the protocol rules. */
2806                         if (remoteEchoOption) break;
2807                         if (appData.localLineEditing &&
2808                             atoi(appData.icsPort) == TN_PORT) {
2809                             TelnetRequest(TN_DONT, TN_ECHO);
2810                         } else {
2811                             EchoOff();
2812                             TelnetRequest(TN_DO, TN_ECHO);
2813                             remoteEchoOption = TRUE;
2814                         }
2815                         break;
2816                       default:
2817                         if (appData.debugMode)
2818                           fprintf(debugFP, "%d ", option);
2819                         /* Whatever this is, we don't want it. */
2820                         TelnetRequest(TN_DONT, option);
2821                         break;
2822                     }
2823                     break;
2824                   case TN_WONT:
2825                     if (appData.debugMode)
2826                       fprintf(debugFP, "\n<WONT ");
2827                     switch (option = (unsigned char) buf[++i]) {
2828                       case TN_ECHO:
2829                         if (appData.debugMode)
2830                           fprintf(debugFP, "ECHO ");
2831                         /* Reply only if this is a change, according
2832                            to the protocol rules. */
2833                         if (!remoteEchoOption) break;
2834                         EchoOn();
2835                         TelnetRequest(TN_DONT, TN_ECHO);
2836                         remoteEchoOption = FALSE;
2837                         break;
2838                       default:
2839                         if (appData.debugMode)
2840                           fprintf(debugFP, "%d ", (unsigned char) option);
2841                         /* Whatever this is, it must already be turned
2842                            off, because we never agree to turn on
2843                            anything non-default, so according to the
2844                            protocol rules, we don't reply. */
2845                         break;
2846                     }
2847                     break;
2848                   case TN_DO:
2849                     if (appData.debugMode)
2850                       fprintf(debugFP, "\n<DO ");
2851                     switch (option = (unsigned char) buf[++i]) {
2852                       default:
2853                         /* Whatever this is, we refuse to do it. */
2854                         if (appData.debugMode)
2855                           fprintf(debugFP, "%d ", option);
2856                         TelnetRequest(TN_WONT, option);
2857                         break;
2858                     }
2859                     break;
2860                   case TN_DONT:
2861                     if (appData.debugMode)
2862                       fprintf(debugFP, "\n<DONT ");
2863                     switch (option = (unsigned char) buf[++i]) {
2864                       default:
2865                         if (appData.debugMode)
2866                           fprintf(debugFP, "%d ", option);
2867                         /* Whatever this is, we are already not doing
2868                            it, because we never agree to do anything
2869                            non-default, so according to the protocol
2870                            rules, we don't reply. */
2871                         break;
2872                     }
2873                     break;
2874                   case TN_IAC:
2875                     if (appData.debugMode)
2876                       fprintf(debugFP, "\n<IAC ");
2877                     /* Doubled IAC; pass it through */
2878                     i--;
2879                     break;
2880                   default:
2881                     if (appData.debugMode)
2882                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2883                     /* Drop all other telnet commands on the floor */
2884                     break;
2885                 }
2886                 if (oldi > next_out)
2887                   SendToPlayer(&buf[next_out], oldi - next_out);
2888                 if (++i > next_out)
2889                   next_out = i;
2890                 continue;
2891             }
2892
2893             /* OK, this at least will *usually* work */
2894             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2895                 loggedOn = TRUE;
2896             }
2897
2898             if (loggedOn && !intfSet) {
2899                 if (ics_type == ICS_ICC) {
2900                   snprintf(str, MSG_SIZ,
2901                           "/set-quietly interface %s\n/set-quietly style 12\n",
2902                           programVersion);
2903                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2904                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2905                 } else if (ics_type == ICS_CHESSNET) {
2906                   snprintf(str, MSG_SIZ, "/style 12\n");
2907                 } else {
2908                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2909                   strcat(str, programVersion);
2910                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2911                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2912                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2913 #ifdef WIN32
2914                   strcat(str, "$iset nohighlight 1\n");
2915 #endif
2916                   strcat(str, "$iset lock 1\n$style 12\n");
2917                 }
2918                 SendToICS(str);
2919                 NotifyFrontendLogin();
2920                 intfSet = TRUE;
2921             }
2922
2923             if (started == STARTED_COMMENT) {
2924                 /* Accumulate characters in comment */
2925                 parse[parse_pos++] = buf[i];
2926                 if (buf[i] == '\n') {
2927                     parse[parse_pos] = NULLCHAR;
2928                     if(chattingPartner>=0) {
2929                         char mess[MSG_SIZ];
2930                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2931                         OutputChatMessage(chattingPartner, mess);
2932                         chattingPartner = -1;
2933                         next_out = i+1; // [HGM] suppress printing in ICS window
2934                     } else
2935                     if(!suppressKibitz) // [HGM] kibitz
2936                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2937                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2938                         int nrDigit = 0, nrAlph = 0, j;
2939                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2940                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2941                         parse[parse_pos] = NULLCHAR;
2942                         // try to be smart: if it does not look like search info, it should go to
2943                         // ICS interaction window after all, not to engine-output window.
2944                         for(j=0; j<parse_pos; j++) { // count letters and digits
2945                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2946                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2947                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2948                         }
2949                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2950                             int depth=0; float score;
2951                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2952                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2953                                 pvInfoList[forwardMostMove-1].depth = depth;
2954                                 pvInfoList[forwardMostMove-1].score = 100*score;
2955                             }
2956                             OutputKibitz(suppressKibitz, parse);
2957                         } else {
2958                             char tmp[MSG_SIZ];
2959                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2960                             SendToPlayer(tmp, strlen(tmp));
2961                         }
2962                         next_out = i+1; // [HGM] suppress printing in ICS window
2963                     }
2964                     started = STARTED_NONE;
2965                 } else {
2966                     /* Don't match patterns against characters in comment */
2967                     i++;
2968                     continue;
2969                 }
2970             }
2971             if (started == STARTED_CHATTER) {
2972                 if (buf[i] != '\n') {
2973                     /* Don't match patterns against characters in chatter */
2974                     i++;
2975                     continue;
2976                 }
2977                 started = STARTED_NONE;
2978                 if(suppressKibitz) next_out = i+1;
2979             }
2980
2981             /* Kludge to deal with rcmd protocol */
2982             if (firstTime && looking_at(buf, &i, "\001*")) {
2983                 DisplayFatalError(&buf[1], 0, 1);
2984                 continue;
2985             } else {
2986                 firstTime = FALSE;
2987             }
2988
2989             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2990                 ics_type = ICS_ICC;
2991                 ics_prefix = "/";
2992                 if (appData.debugMode)
2993                   fprintf(debugFP, "ics_type %d\n", ics_type);
2994                 continue;
2995             }
2996             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2997                 ics_type = ICS_FICS;
2998                 ics_prefix = "$";
2999                 if (appData.debugMode)
3000                   fprintf(debugFP, "ics_type %d\n", ics_type);
3001                 continue;
3002             }
3003             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3004                 ics_type = ICS_CHESSNET;
3005                 ics_prefix = "/";
3006                 if (appData.debugMode)
3007                   fprintf(debugFP, "ics_type %d\n", ics_type);
3008                 continue;
3009             }
3010
3011             if (!loggedOn &&
3012                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3013                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3014                  looking_at(buf, &i, "will be \"*\""))) {
3015               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3016               continue;
3017             }
3018
3019             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3020               char buf[MSG_SIZ];
3021               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3022               DisplayIcsInteractionTitle(buf);
3023               have_set_title = TRUE;
3024             }
3025
3026             /* skip finger notes */
3027             if (started == STARTED_NONE &&
3028                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3029                  (buf[i] == '1' && buf[i+1] == '0')) &&
3030                 buf[i+2] == ':' && buf[i+3] == ' ') {
3031               started = STARTED_CHATTER;
3032               i += 3;
3033               continue;
3034             }
3035
3036             oldi = i;
3037             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3038             if(appData.seekGraph) {
3039                 if(soughtPending && MatchSoughtLine(buf+i)) {
3040                     i = strstr(buf+i, "rated") - buf;
3041                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3042                     next_out = leftover_start = i;
3043                     started = STARTED_CHATTER;
3044                     suppressKibitz = TRUE;
3045                     continue;
3046                 }
3047                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3048                         && looking_at(buf, &i, "* ads displayed")) {
3049                     soughtPending = FALSE;
3050                     seekGraphUp = TRUE;
3051                     DrawSeekGraph();
3052                     continue;
3053                 }
3054                 if(appData.autoRefresh) {
3055                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3056                         int s = (ics_type == ICS_ICC); // ICC format differs
3057                         if(seekGraphUp)
3058                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3059                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3060                         looking_at(buf, &i, "*% "); // eat prompt
3061                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3062                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3063                         next_out = i; // suppress
3064                         continue;
3065                     }
3066                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3067                         char *p = star_match[0];
3068                         while(*p) {
3069                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3070                             while(*p && *p++ != ' '); // next
3071                         }
3072                         looking_at(buf, &i, "*% "); // eat prompt
3073                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3074                         next_out = i;
3075                         continue;
3076                     }
3077                 }
3078             }
3079
3080             /* skip formula vars */
3081             if (started == STARTED_NONE &&
3082                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3083               started = STARTED_CHATTER;
3084               i += 3;
3085               continue;
3086             }
3087
3088             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3089             if (appData.autoKibitz && started == STARTED_NONE &&
3090                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3091                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3092                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3093                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3094                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3095                         suppressKibitz = TRUE;
3096                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3097                         next_out = i;
3098                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3099                                 && (gameMode == IcsPlayingWhite)) ||
3100                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3101                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3102                             started = STARTED_CHATTER; // own kibitz we simply discard
3103                         else {
3104                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3105                             parse_pos = 0; parse[0] = NULLCHAR;
3106                             savingComment = TRUE;
3107                             suppressKibitz = gameMode != IcsObserving ? 2 :
3108                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3109                         }
3110                         continue;
3111                 } else
3112                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3113                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3114                          && atoi(star_match[0])) {
3115                     // suppress the acknowledgements of our own autoKibitz
3116                     char *p;
3117                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3118                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3119                     SendToPlayer(star_match[0], strlen(star_match[0]));
3120                     if(looking_at(buf, &i, "*% ")) // eat prompt
3121                         suppressKibitz = FALSE;
3122                     next_out = i;
3123                     continue;
3124                 }
3125             } // [HGM] kibitz: end of patch
3126
3127             // [HGM] chat: intercept tells by users for which we have an open chat window
3128             channel = -1;
3129             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3130                                            looking_at(buf, &i, "* whispers:") ||
3131                                            looking_at(buf, &i, "* kibitzes:") ||
3132                                            looking_at(buf, &i, "* shouts:") ||
3133                                            looking_at(buf, &i, "* c-shouts:") ||
3134                                            looking_at(buf, &i, "--> * ") ||
3135                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3136                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3137                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3138                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3139                 int p;
3140                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3141                 chattingPartner = -1;
3142
3143                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3144                 for(p=0; p<MAX_CHAT; p++) {
3145                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3146                     talker[0] = '['; strcat(talker, "] ");
3147                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3148                     chattingPartner = p; break;
3149                     }
3150                 } else
3151                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3152                 for(p=0; p<MAX_CHAT; p++) {
3153                     if(!strcmp("kibitzes", chatPartner[p])) {
3154                         talker[0] = '['; strcat(talker, "] ");
3155                         chattingPartner = p; break;
3156                     }
3157                 } else
3158                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3159                 for(p=0; p<MAX_CHAT; p++) {
3160                     if(!strcmp("whispers", chatPartner[p])) {
3161                         talker[0] = '['; strcat(talker, "] ");
3162                         chattingPartner = p; break;
3163                     }
3164                 } else
3165                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3166                   if(buf[i-8] == '-' && buf[i-3] == 't')
3167                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3168                     if(!strcmp("c-shouts", chatPartner[p])) {
3169                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3170                         chattingPartner = p; break;
3171                     }
3172                   }
3173                   if(chattingPartner < 0)
3174                   for(p=0; p<MAX_CHAT; p++) {
3175                     if(!strcmp("shouts", chatPartner[p])) {
3176                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3177                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3178                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3179                         chattingPartner = p; break;
3180                     }
3181                   }
3182                 }
3183                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3184                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3185                     talker[0] = 0; Colorize(ColorTell, FALSE);
3186                     chattingPartner = p; break;
3187                 }
3188                 if(chattingPartner<0) i = oldi; else {
3189                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3190                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3191                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3192                     started = STARTED_COMMENT;
3193                     parse_pos = 0; parse[0] = NULLCHAR;
3194                     savingComment = 3 + chattingPartner; // counts as TRUE
3195                     suppressKibitz = TRUE;
3196                     continue;
3197                 }
3198             } // [HGM] chat: end of patch
3199
3200           backup = i;
3201             if (appData.zippyTalk || appData.zippyPlay) {
3202                 /* [DM] Backup address for color zippy lines */
3203 #if ZIPPY
3204                if (loggedOn == TRUE)
3205                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3206                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3207 #endif
3208             } // [DM] 'else { ' deleted
3209                 if (
3210                     /* Regular tells and says */
3211                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3212                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3213                     looking_at(buf, &i, "* says: ") ||
3214                     /* Don't color "message" or "messages" output */
3215                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3216                     looking_at(buf, &i, "*. * at *:*: ") ||
3217                     looking_at(buf, &i, "--* (*:*): ") ||
3218                     /* Message notifications (same color as tells) */
3219                     looking_at(buf, &i, "* has left a message ") ||
3220                     looking_at(buf, &i, "* just sent you a message:\n") ||
3221                     /* Whispers and kibitzes */
3222                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3223                     looking_at(buf, &i, "* kibitzes: ") ||
3224                     /* Channel tells */
3225                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3226
3227                   if (tkind == 1 && strchr(star_match[0], ':')) {
3228                       /* Avoid "tells you:" spoofs in channels */
3229                      tkind = 3;
3230                   }
3231                   if (star_match[0][0] == NULLCHAR ||
3232                       strchr(star_match[0], ' ') ||
3233                       (tkind == 3 && strchr(star_match[1], ' '))) {
3234                     /* Reject bogus matches */
3235                     i = oldi;
3236                   } else {
3237                     if (appData.colorize) {
3238                       if (oldi > next_out) {
3239                         SendToPlayer(&buf[next_out], oldi - next_out);
3240                         next_out = oldi;
3241                       }
3242                       switch (tkind) {
3243                       case 1:
3244                         Colorize(ColorTell, FALSE);
3245                         curColor = ColorTell;
3246                         break;
3247                       case 2:
3248                         Colorize(ColorKibitz, FALSE);
3249                         curColor = ColorKibitz;
3250                         break;
3251                       case 3:
3252                         p = strrchr(star_match[1], '(');
3253                         if (p == NULL) {
3254                           p = star_match[1];
3255                         } else {
3256                           p++;
3257                         }
3258                         if (atoi(p) == 1) {
3259                           Colorize(ColorChannel1, FALSE);
3260                           curColor = ColorChannel1;
3261                         } else {
3262                           Colorize(ColorChannel, FALSE);
3263                           curColor = ColorChannel;
3264                         }
3265                         break;
3266                       case 5:
3267                         curColor = ColorNormal;
3268                         break;
3269                       }
3270                     }
3271                     if (started == STARTED_NONE && appData.autoComment &&
3272                         (gameMode == IcsObserving ||
3273                          gameMode == IcsPlayingWhite ||
3274                          gameMode == IcsPlayingBlack)) {
3275                       parse_pos = i - oldi;
3276                       memcpy(parse, &buf[oldi], parse_pos);
3277                       parse[parse_pos] = NULLCHAR;
3278                       started = STARTED_COMMENT;
3279                       savingComment = TRUE;
3280                     } else {
3281                       started = STARTED_CHATTER;
3282                       savingComment = FALSE;
3283                     }
3284                     loggedOn = TRUE;
3285                     continue;
3286                   }
3287                 }
3288
3289                 if (looking_at(buf, &i, "* s-shouts: ") ||
3290                     looking_at(buf, &i, "* c-shouts: ")) {
3291                     if (appData.colorize) {
3292                         if (oldi > next_out) {
3293                             SendToPlayer(&buf[next_out], oldi - next_out);
3294                             next_out = oldi;
3295                         }
3296                         Colorize(ColorSShout, FALSE);
3297                         curColor = ColorSShout;
3298                     }
3299                     loggedOn = TRUE;
3300                     started = STARTED_CHATTER;
3301                     continue;
3302                 }
3303
3304                 if (looking_at(buf, &i, "--->")) {
3305                     loggedOn = TRUE;
3306                     continue;
3307                 }
3308
3309                 if (looking_at(buf, &i, "* shouts: ") ||
3310                     looking_at(buf, &i, "--> ")) {
3311                     if (appData.colorize) {
3312                         if (oldi > next_out) {
3313                             SendToPlayer(&buf[next_out], oldi - next_out);
3314                             next_out = oldi;
3315                         }
3316                         Colorize(ColorShout, FALSE);
3317                         curColor = ColorShout;
3318                     }
3319                     loggedOn = TRUE;
3320                     started = STARTED_CHATTER;
3321                     continue;
3322                 }
3323
3324                 if (looking_at( buf, &i, "Challenge:")) {
3325                     if (appData.colorize) {
3326                         if (oldi > next_out) {
3327                             SendToPlayer(&buf[next_out], oldi - next_out);
3328                             next_out = oldi;
3329                         }
3330                         Colorize(ColorChallenge, FALSE);
3331                         curColor = ColorChallenge;
3332                     }
3333                     loggedOn = TRUE;
3334                     continue;
3335                 }
3336
3337                 if (looking_at(buf, &i, "* offers you") ||
3338                     looking_at(buf, &i, "* offers to be") ||
3339                     looking_at(buf, &i, "* would like to") ||
3340                     looking_at(buf, &i, "* requests to") ||
3341                     looking_at(buf, &i, "Your opponent offers") ||
3342                     looking_at(buf, &i, "Your opponent requests")) {
3343
3344                     if (appData.colorize) {
3345                         if (oldi > next_out) {
3346                             SendToPlayer(&buf[next_out], oldi - next_out);
3347                             next_out = oldi;
3348                         }
3349                         Colorize(ColorRequest, FALSE);
3350                         curColor = ColorRequest;
3351                     }
3352                     continue;
3353                 }
3354
3355                 if (looking_at(buf, &i, "* (*) seeking")) {
3356                     if (appData.colorize) {
3357                         if (oldi > next_out) {
3358                             SendToPlayer(&buf[next_out], oldi - next_out);
3359                             next_out = oldi;
3360                         }
3361                         Colorize(ColorSeek, FALSE);
3362                         curColor = ColorSeek;
3363                     }
3364                     continue;
3365             }
3366
3367           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3368
3369             if (looking_at(buf, &i, "\\   ")) {
3370                 if (prevColor != ColorNormal) {
3371                     if (oldi > next_out) {
3372                         SendToPlayer(&buf[next_out], oldi - next_out);
3373                         next_out = oldi;
3374                     }
3375                     Colorize(prevColor, TRUE);
3376                     curColor = prevColor;
3377                 }
3378                 if (savingComment) {
3379                     parse_pos = i - oldi;
3380                     memcpy(parse, &buf[oldi], parse_pos);
3381                     parse[parse_pos] = NULLCHAR;
3382                     started = STARTED_COMMENT;
3383                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3384                         chattingPartner = savingComment - 3; // kludge to remember the box
3385                 } else {
3386                     started = STARTED_CHATTER;
3387                 }
3388                 continue;
3389             }
3390
3391             if (looking_at(buf, &i, "Black Strength :") ||
3392                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3393                 looking_at(buf, &i, "<10>") ||
3394                 looking_at(buf, &i, "#@#")) {
3395                 /* Wrong board style */
3396                 loggedOn = TRUE;
3397                 SendToICS(ics_prefix);
3398                 SendToICS("set style 12\n");
3399                 SendToICS(ics_prefix);
3400                 SendToICS("refresh\n");
3401                 continue;
3402             }
3403
3404             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3405                 ICSInitScript();
3406                 have_sent_ICS_logon = 1;
3407                 continue;
3408             }
3409
3410             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3411                 (looking_at(buf, &i, "\n<12> ") ||
3412                  looking_at(buf, &i, "<12> "))) {
3413                 loggedOn = TRUE;
3414                 if (oldi > next_out) {
3415                     SendToPlayer(&buf[next_out], oldi - next_out);
3416                 }
3417                 next_out = i;
3418                 started = STARTED_BOARD;
3419                 parse_pos = 0;
3420                 continue;
3421             }
3422
3423             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3424                 looking_at(buf, &i, "<b1> ")) {
3425                 if (oldi > next_out) {
3426                     SendToPlayer(&buf[next_out], oldi - next_out);
3427                 }
3428                 next_out = i;
3429                 started = STARTED_HOLDINGS;
3430                 parse_pos = 0;
3431                 continue;
3432             }
3433
3434             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3435                 loggedOn = TRUE;
3436                 /* Header for a move list -- first line */
3437
3438                 switch (ics_getting_history) {
3439                   case H_FALSE:
3440                     switch (gameMode) {
3441                       case IcsIdle:
3442                       case BeginningOfGame:
3443                         /* User typed "moves" or "oldmoves" while we
3444                            were idle.  Pretend we asked for these
3445                            moves and soak them up so user can step
3446                            through them and/or save them.
3447                            */
3448                         Reset(FALSE, TRUE);
3449                         gameMode = IcsObserving;
3450                         ModeHighlight();
3451                         ics_gamenum = -1;
3452                         ics_getting_history = H_GOT_UNREQ_HEADER;
3453                         break;
3454                       case EditGame: /*?*/
3455                       case EditPosition: /*?*/
3456                         /* Should above feature work in these modes too? */
3457                         /* For now it doesn't */
3458                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3459                         break;
3460                       default:
3461                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3462                         break;
3463                     }
3464                     break;
3465                   case H_REQUESTED:
3466                     /* Is this the right one? */
3467                     if (gameInfo.white && gameInfo.black &&
3468                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3469                         strcmp(gameInfo.black, star_match[2]) == 0) {
3470                         /* All is well */
3471                         ics_getting_history = H_GOT_REQ_HEADER;
3472                     }
3473                     break;
3474                   case H_GOT_REQ_HEADER:
3475                   case H_GOT_UNREQ_HEADER:
3476                   case H_GOT_UNWANTED_HEADER:
3477                   case H_GETTING_MOVES:
3478                     /* Should not happen */
3479                     DisplayError(_("Error gathering move list: two headers"), 0);
3480                     ics_getting_history = H_FALSE;
3481                     break;
3482                 }
3483
3484                 /* Save player ratings into gameInfo if needed */
3485                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3486                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3487                     (gameInfo.whiteRating == -1 ||
3488                      gameInfo.blackRating == -1)) {
3489
3490                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3491                     gameInfo.blackRating = string_to_rating(star_match[3]);
3492                     if (appData.debugMode)
3493                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3494                               gameInfo.whiteRating, gameInfo.blackRating);
3495                 }
3496                 continue;
3497             }
3498
3499             if (looking_at(buf, &i,
3500               "* * match, initial time: * minute*, increment: * second")) {
3501                 /* Header for a move list -- second line */
3502                 /* Initial board will follow if this is a wild game */
3503                 if (gameInfo.event != NULL) free(gameInfo.event);
3504                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3505                 gameInfo.event = StrSave(str);
3506                 /* [HGM] we switched variant. Translate boards if needed. */
3507                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3508                 continue;
3509             }
3510
3511             if (looking_at(buf, &i, "Move  ")) {
3512                 /* Beginning of a move list */
3513                 switch (ics_getting_history) {
3514                   case H_FALSE:
3515                     /* Normally should not happen */
3516                     /* Maybe user hit reset while we were parsing */
3517                     break;
3518                   case H_REQUESTED:
3519                     /* Happens if we are ignoring a move list that is not
3520                      * the one we just requested.  Common if the user
3521                      * tries to observe two games without turning off
3522                      * getMoveList */
3523                     break;
3524                   case H_GETTING_MOVES:
3525                     /* Should not happen */
3526                     DisplayError(_("Error gathering move list: nested"), 0);
3527                     ics_getting_history = H_FALSE;
3528                     break;
3529                   case H_GOT_REQ_HEADER:
3530                     ics_getting_history = H_GETTING_MOVES;
3531                     started = STARTED_MOVES;
3532                     parse_pos = 0;
3533                     if (oldi > next_out) {
3534                         SendToPlayer(&buf[next_out], oldi - next_out);
3535                     }
3536                     break;
3537                   case H_GOT_UNREQ_HEADER:
3538                     ics_getting_history = H_GETTING_MOVES;
3539                     started = STARTED_MOVES_NOHIDE;
3540                     parse_pos = 0;
3541                     break;
3542                   case H_GOT_UNWANTED_HEADER:
3543                     ics_getting_history = H_FALSE;
3544                     break;
3545                 }
3546                 continue;
3547             }
3548
3549             if (looking_at(buf, &i, "% ") ||
3550                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3551                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3552                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3553                     soughtPending = FALSE;
3554                     seekGraphUp = TRUE;
3555                     DrawSeekGraph();
3556                 }
3557                 if(suppressKibitz) next_out = i;
3558                 savingComment = FALSE;
3559                 suppressKibitz = 0;
3560                 switch (started) {
3561                   case STARTED_MOVES:
3562                   case STARTED_MOVES_NOHIDE:
3563                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3564                     parse[parse_pos + i - oldi] = NULLCHAR;
3565                     ParseGameHistory(parse);
3566 #if ZIPPY
3567                     if (appData.zippyPlay && first.initDone) {
3568                         FeedMovesToProgram(&first, forwardMostMove);
3569                         if (gameMode == IcsPlayingWhite) {
3570                             if (WhiteOnMove(forwardMostMove)) {
3571                                 if (first.sendTime) {
3572                                   if (first.useColors) {
3573                                     SendToProgram("black\n", &first);
3574                                   }
3575                                   SendTimeRemaining(&first, TRUE);
3576                                 }
3577                                 if (first.useColors) {
3578                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3579                                 }
3580                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3581                                 first.maybeThinking = TRUE;
3582                             } else {
3583                                 if (first.usePlayother) {
3584                                   if (first.sendTime) {
3585                                     SendTimeRemaining(&first, TRUE);
3586                                   }
3587                                   SendToProgram("playother\n", &first);
3588                                   firstMove = FALSE;
3589                                 } else {
3590                                   firstMove = TRUE;
3591                                 }
3592                             }
3593                         } else if (gameMode == IcsPlayingBlack) {
3594                             if (!WhiteOnMove(forwardMostMove)) {
3595                                 if (first.sendTime) {
3596                                   if (first.useColors) {
3597                                     SendToProgram("white\n", &first);
3598                                   }
3599                                   SendTimeRemaining(&first, FALSE);
3600                                 }
3601                                 if (first.useColors) {
3602                                   SendToProgram("black\n", &first);
3603                                 }
3604                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3605                                 first.maybeThinking = TRUE;
3606                             } else {
3607                                 if (first.usePlayother) {
3608                                   if (first.sendTime) {
3609                                     SendTimeRemaining(&first, FALSE);
3610                                   }
3611                                   SendToProgram("playother\n", &first);
3612                                   firstMove = FALSE;
3613                                 } else {
3614                                   firstMove = TRUE;
3615                                 }
3616                             }
3617                         }
3618                     }
3619 #endif
3620                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3621                         /* Moves came from oldmoves or moves command
3622                            while we weren't doing anything else.
3623                            */
3624                         currentMove = forwardMostMove;
3625                         ClearHighlights();/*!!could figure this out*/
3626                         flipView = appData.flipView;
3627                         DrawPosition(TRUE, boards[currentMove]);
3628                         DisplayBothClocks();
3629                         snprintf(str, MSG_SIZ, "%s vs. %s",
3630                                 gameInfo.white, gameInfo.black);
3631                         DisplayTitle(str);
3632                         gameMode = IcsIdle;
3633                     } else {
3634                         /* Moves were history of an active game */
3635                         if (gameInfo.resultDetails != NULL) {
3636                             free(gameInfo.resultDetails);
3637                             gameInfo.resultDetails = NULL;
3638                         }
3639                     }
3640                     HistorySet(parseList, backwardMostMove,
3641                                forwardMostMove, currentMove-1);
3642                     DisplayMove(currentMove - 1);
3643                     if (started == STARTED_MOVES) next_out = i;
3644                     started = STARTED_NONE;
3645                     ics_getting_history = H_FALSE;
3646                     break;
3647
3648                   case STARTED_OBSERVE:
3649                     started = STARTED_NONE;
3650                     SendToICS(ics_prefix);
3651                     SendToICS("refresh\n");
3652                     break;
3653
3654                   default:
3655                     break;
3656                 }
3657                 if(bookHit) { // [HGM] book: simulate book reply
3658                     static char bookMove[MSG_SIZ]; // a bit generous?
3659
3660                     programStats.nodes = programStats.depth = programStats.time =
3661                     programStats.score = programStats.got_only_move = 0;
3662                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3663
3664                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3665                     strcat(bookMove, bookHit);
3666                     HandleMachineMove(bookMove, &first);
3667                 }
3668                 continue;
3669             }
3670
3671             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3672                  started == STARTED_HOLDINGS ||
3673                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3674                 /* Accumulate characters in move list or board */
3675                 parse[parse_pos++] = buf[i];
3676             }
3677
3678             /* Start of game messages.  Mostly we detect start of game
3679                when the first board image arrives.  On some versions
3680                of the ICS, though, we need to do a "refresh" after starting
3681                to observe in order to get the current board right away. */
3682             if (looking_at(buf, &i, "Adding game * to observation list")) {
3683                 started = STARTED_OBSERVE;
3684                 continue;
3685             }
3686
3687             /* Handle auto-observe */
3688             if (appData.autoObserve &&
3689                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3690                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3691                 char *player;
3692                 /* Choose the player that was highlighted, if any. */
3693                 if (star_match[0][0] == '\033' ||
3694                     star_match[1][0] != '\033') {
3695                     player = star_match[0];
3696                 } else {
3697                     player = star_match[2];
3698                 }
3699                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3700                         ics_prefix, StripHighlightAndTitle(player));
3701                 SendToICS(str);
3702
3703                 /* Save ratings from notify string */
3704                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3705                 player1Rating = string_to_rating(star_match[1]);
3706                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3707                 player2Rating = string_to_rating(star_match[3]);
3708
3709                 if (appData.debugMode)
3710                   fprintf(debugFP,
3711                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3712                           player1Name, player1Rating,
3713                           player2Name, player2Rating);
3714
3715                 continue;
3716             }
3717
3718             /* Deal with automatic examine mode after a game,
3719                and with IcsObserving -> IcsExamining transition */
3720             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3721                 looking_at(buf, &i, "has made you an examiner of game *")) {
3722
3723                 int gamenum = atoi(star_match[0]);
3724                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3725                     gamenum == ics_gamenum) {
3726                     /* We were already playing or observing this game;
3727                        no need to refetch history */
3728                     gameMode = IcsExamining;
3729                     if (pausing) {
3730                         pauseExamForwardMostMove = forwardMostMove;
3731                     } else if (currentMove < forwardMostMove) {
3732                         ForwardInner(forwardMostMove);
3733                     }
3734                 } else {
3735                     /* I don't think this case really can happen */
3736                     SendToICS(ics_prefix);
3737                     SendToICS("refresh\n");
3738                 }
3739                 continue;
3740             }
3741
3742             /* Error messages */
3743 //          if (ics_user_moved) {
3744             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3745                 if (looking_at(buf, &i, "Illegal move") ||
3746                     looking_at(buf, &i, "Not a legal move") ||
3747                     looking_at(buf, &i, "Your king is in check") ||
3748                     looking_at(buf, &i, "It isn't your turn") ||
3749                     looking_at(buf, &i, "It is not your move")) {
3750                     /* Illegal move */
3751                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3752                         currentMove = forwardMostMove-1;
3753                         DisplayMove(currentMove - 1); /* before DMError */
3754                         DrawPosition(FALSE, boards[currentMove]);
3755                         SwitchClocks(forwardMostMove-1); // [HGM] race
3756                         DisplayBothClocks();
3757                     }
3758                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3759                     ics_user_moved = 0;
3760                     continue;
3761                 }
3762             }
3763
3764             if (looking_at(buf, &i, "still have time") ||
3765                 looking_at(buf, &i, "not out of time") ||
3766                 looking_at(buf, &i, "either player is out of time") ||
3767                 looking_at(buf, &i, "has timeseal; checking")) {
3768                 /* We must have called his flag a little too soon */
3769                 whiteFlag = blackFlag = FALSE;
3770                 continue;
3771             }
3772
3773             if (looking_at(buf, &i, "added * seconds to") ||
3774                 looking_at(buf, &i, "seconds were added to")) {
3775                 /* Update the clocks */
3776                 SendToICS(ics_prefix);
3777                 SendToICS("refresh\n");
3778                 continue;
3779             }
3780
3781             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3782                 ics_clock_paused = TRUE;
3783                 StopClocks();
3784                 continue;
3785             }
3786
3787             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3788                 ics_clock_paused = FALSE;
3789                 StartClocks();
3790                 continue;
3791             }
3792
3793             /* Grab player ratings from the Creating: message.
3794                Note we have to check for the special case when
3795                the ICS inserts things like [white] or [black]. */
3796             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3797                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3798                 /* star_matches:
3799                    0    player 1 name (not necessarily white)
3800                    1    player 1 rating
3801                    2    empty, white, or black (IGNORED)
3802                    3    player 2 name (not necessarily black)
3803                    4    player 2 rating
3804
3805                    The names/ratings are sorted out when the game
3806                    actually starts (below).
3807                 */
3808                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3809                 player1Rating = string_to_rating(star_match[1]);
3810                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3811                 player2Rating = string_to_rating(star_match[4]);
3812
3813                 if (appData.debugMode)
3814                   fprintf(debugFP,
3815                           "Ratings from 'Creating:' %s %d, %s %d\n",
3816                           player1Name, player1Rating,
3817                           player2Name, player2Rating);
3818
3819                 continue;
3820             }
3821
3822             /* Improved generic start/end-of-game messages */
3823             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3824                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3825                 /* If tkind == 0: */
3826                 /* star_match[0] is the game number */
3827                 /*           [1] is the white player's name */
3828                 /*           [2] is the black player's name */
3829                 /* For end-of-game: */
3830                 /*           [3] is the reason for the game end */
3831                 /*           [4] is a PGN end game-token, preceded by " " */
3832                 /* For start-of-game: */
3833                 /*           [3] begins with "Creating" or "Continuing" */
3834                 /*           [4] is " *" or empty (don't care). */
3835                 int gamenum = atoi(star_match[0]);
3836                 char *whitename, *blackname, *why, *endtoken;
3837                 ChessMove endtype = EndOfFile;
3838
3839                 if (tkind == 0) {
3840                   whitename = star_match[1];
3841                   blackname = star_match[2];
3842                   why = star_match[3];
3843                   endtoken = star_match[4];
3844                 } else {
3845                   whitename = star_match[1];
3846                   blackname = star_match[3];
3847                   why = star_match[5];
3848                   endtoken = star_match[6];
3849                 }
3850
3851                 /* Game start messages */
3852                 if (strncmp(why, "Creating ", 9) == 0 ||
3853                     strncmp(why, "Continuing ", 11) == 0) {
3854                     gs_gamenum = gamenum;
3855                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3856                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3857 #if ZIPPY
3858                     if (appData.zippyPlay) {
3859                         ZippyGameStart(whitename, blackname);
3860                     }
3861 #endif /*ZIPPY*/
3862                     partnerBoardValid = FALSE; // [HGM] bughouse
3863                     continue;
3864                 }
3865
3866                 /* Game end messages */
3867                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3868                     ics_gamenum != gamenum) {
3869                     continue;
3870                 }
3871                 while (endtoken[0] == ' ') endtoken++;
3872                 switch (endtoken[0]) {
3873                   case '*':
3874                   default:
3875                     endtype = GameUnfinished;
3876                     break;
3877                   case '0':
3878                     endtype = BlackWins;
3879                     break;
3880                   case '1':
3881                     if (endtoken[1] == '/')
3882                       endtype = GameIsDrawn;
3883                     else
3884                       endtype = WhiteWins;
3885                     break;
3886                 }
3887                 GameEnds(endtype, why, GE_ICS);
3888 #if ZIPPY
3889                 if (appData.zippyPlay && first.initDone) {
3890                     ZippyGameEnd(endtype, why);
3891                     if (first.pr == NULL) {
3892                       /* Start the next process early so that we'll
3893                          be ready for the next challenge */
3894                       StartChessProgram(&first);
3895                     }
3896                     /* Send "new" early, in case this command takes
3897                        a long time to finish, so that we'll be ready
3898                        for the next challenge. */
3899                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3900                     Reset(TRUE, TRUE);
3901                 }
3902 #endif /*ZIPPY*/
3903                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3904                 continue;
3905             }
3906
3907             if (looking_at(buf, &i, "Removing game * from observation") ||
3908                 looking_at(buf, &i, "no longer observing game *") ||
3909                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3910                 if (gameMode == IcsObserving &&
3911                     atoi(star_match[0]) == ics_gamenum)
3912                   {
3913                       /* icsEngineAnalyze */
3914                       if (appData.icsEngineAnalyze) {
3915                             ExitAnalyzeMode();
3916                             ModeHighlight();
3917                       }
3918                       StopClocks();
3919                       gameMode = IcsIdle;
3920                       ics_gamenum = -1;
3921                       ics_user_moved = FALSE;
3922                   }
3923                 continue;
3924             }
3925
3926             if (looking_at(buf, &i, "no longer examining game *")) {
3927                 if (gameMode == IcsExamining &&
3928                     atoi(star_match[0]) == ics_gamenum)
3929                   {
3930                       gameMode = IcsIdle;
3931                       ics_gamenum = -1;
3932                       ics_user_moved = FALSE;
3933                   }
3934                 continue;
3935             }
3936
3937             /* Advance leftover_start past any newlines we find,
3938                so only partial lines can get reparsed */
3939             if (looking_at(buf, &i, "\n")) {
3940                 prevColor = curColor;
3941                 if (curColor != ColorNormal) {
3942                     if (oldi > next_out) {
3943                         SendToPlayer(&buf[next_out], oldi - next_out);
3944                         next_out = oldi;
3945                     }
3946                     Colorize(ColorNormal, FALSE);
3947                     curColor = ColorNormal;
3948                 }
3949                 if (started == STARTED_BOARD) {
3950                     started = STARTED_NONE;
3951                     parse[parse_pos] = NULLCHAR;
3952                     ParseBoard12(parse);
3953                     ics_user_moved = 0;
3954
3955                     /* Send premove here */
3956                     if (appData.premove) {
3957                       char str[MSG_SIZ];
3958                       if (currentMove == 0 &&
3959                           gameMode == IcsPlayingWhite &&
3960                           appData.premoveWhite) {
3961                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3962                         if (appData.debugMode)
3963                           fprintf(debugFP, "Sending premove:\n");
3964                         SendToICS(str);
3965                       } else if (currentMove == 1 &&
3966                                  gameMode == IcsPlayingBlack &&
3967                                  appData.premoveBlack) {
3968                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3969                         if (appData.debugMode)
3970                           fprintf(debugFP, "Sending premove:\n");
3971                         SendToICS(str);
3972                       } else if (gotPremove) {
3973                         gotPremove = 0;
3974                         ClearPremoveHighlights();
3975                         if (appData.debugMode)
3976                           fprintf(debugFP, "Sending premove:\n");
3977                           UserMoveEvent(premoveFromX, premoveFromY,
3978                                         premoveToX, premoveToY,
3979                                         premovePromoChar);
3980                       }
3981                     }
3982
3983                     /* Usually suppress following prompt */
3984                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3985                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3986                         if (looking_at(buf, &i, "*% ")) {
3987                             savingComment = FALSE;
3988                             suppressKibitz = 0;
3989                         }
3990                     }
3991                     next_out = i;
3992                 } else if (started == STARTED_HOLDINGS) {
3993                     int gamenum;
3994                     char new_piece[MSG_SIZ];
3995                     started = STARTED_NONE;
3996                     parse[parse_pos] = NULLCHAR;
3997                     if (appData.debugMode)
3998                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3999                                                         parse, currentMove);
4000                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4001                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4002                         if (gameInfo.variant == VariantNormal) {
4003                           /* [HGM] We seem to switch variant during a game!
4004                            * Presumably no holdings were displayed, so we have
4005                            * to move the position two files to the right to
4006                            * create room for them!
4007                            */
4008                           VariantClass newVariant;
4009                           switch(gameInfo.boardWidth) { // base guess on board width
4010                                 case 9:  newVariant = VariantShogi; break;
4011                                 case 10: newVariant = VariantGreat; break;
4012                                 default: newVariant = VariantCrazyhouse; break;
4013                           }
4014                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4015                           /* Get a move list just to see the header, which
4016                              will tell us whether this is really bug or zh */
4017                           if (ics_getting_history == H_FALSE) {
4018                             ics_getting_history = H_REQUESTED;
4019                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4020                             SendToICS(str);
4021                           }
4022                         }
4023                         new_piece[0] = NULLCHAR;
4024                         sscanf(parse, "game %d white [%s black [%s <- %s",
4025                                &gamenum, white_holding, black_holding,
4026                                new_piece);
4027                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4028                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4029                         /* [HGM] copy holdings to board holdings area */
4030                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4031                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4032                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4033 #if ZIPPY
4034                         if (appData.zippyPlay && first.initDone) {
4035                             ZippyHoldings(white_holding, black_holding,
4036                                           new_piece);
4037                         }
4038 #endif /*ZIPPY*/
4039                         if (tinyLayout || smallLayout) {
4040                             char wh[16], bh[16];
4041                             PackHolding(wh, white_holding);
4042                             PackHolding(bh, black_holding);
4043                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4044                                     gameInfo.white, gameInfo.black);
4045                         } else {
4046                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4047                                     gameInfo.white, white_holding,
4048                                     gameInfo.black, black_holding);
4049                         }
4050                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4051                         DrawPosition(FALSE, boards[currentMove]);
4052                         DisplayTitle(str);
4053                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4054                         sscanf(parse, "game %d white [%s black [%s <- %s",
4055                                &gamenum, white_holding, black_holding,
4056                                new_piece);
4057                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4058                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4059                         /* [HGM] copy holdings to partner-board holdings area */
4060                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4061                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4062                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4063                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4064                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4065                       }
4066                     }
4067                     /* Suppress following prompt */
4068                     if (looking_at(buf, &i, "*% ")) {
4069                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4070                         savingComment = FALSE;
4071                         suppressKibitz = 0;
4072                     }
4073                     next_out = i;
4074                 }
4075                 continue;
4076             }
4077
4078             i++;                /* skip unparsed character and loop back */
4079         }
4080
4081         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4082 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4083 //          SendToPlayer(&buf[next_out], i - next_out);
4084             started != STARTED_HOLDINGS && leftover_start > next_out) {
4085             SendToPlayer(&buf[next_out], leftover_start - next_out);
4086             next_out = i;
4087         }
4088
4089         leftover_len = buf_len - leftover_start;
4090         /* if buffer ends with something we couldn't parse,
4091            reparse it after appending the next read */
4092
4093     } else if (count == 0) {
4094         RemoveInputSource(isr);
4095         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4096     } else {
4097         DisplayFatalError(_("Error reading from ICS"), error, 1);
4098     }
4099 }
4100
4101
4102 /* Board style 12 looks like this:
4103
4104    <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
4105
4106  * The "<12> " is stripped before it gets to this routine.  The two
4107  * trailing 0's (flip state and clock ticking) are later addition, and
4108  * some chess servers may not have them, or may have only the first.
4109  * Additional trailing fields may be added in the future.
4110  */
4111
4112 #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"
4113
4114 #define RELATION_OBSERVING_PLAYED    0
4115 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4116 #define RELATION_PLAYING_MYMOVE      1
4117 #define RELATION_PLAYING_NOTMYMOVE  -1
4118 #define RELATION_EXAMINING           2
4119 #define RELATION_ISOLATED_BOARD     -3
4120 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4121
4122 void
4123 ParseBoard12(string)
4124      char *string;
4125 {
4126     GameMode newGameMode;
4127     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4128     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4129     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4130     char to_play, board_chars[200];
4131     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4132     char black[32], white[32];
4133     Board board;
4134     int prevMove = currentMove;
4135     int ticking = 2;
4136     ChessMove moveType;
4137     int fromX, fromY, toX, toY;
4138     char promoChar;
4139     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4140     char *bookHit = NULL; // [HGM] book
4141     Boolean weird = FALSE, reqFlag = FALSE;
4142
4143     fromX = fromY = toX = toY = -1;
4144
4145     newGame = FALSE;
4146
4147     if (appData.debugMode)
4148       fprintf(debugFP, _("Parsing board: %s\n"), string);
4149
4150     move_str[0] = NULLCHAR;
4151     elapsed_time[0] = NULLCHAR;
4152     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4153         int  i = 0, j;
4154         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4155             if(string[i] == ' ') { ranks++; files = 0; }
4156             else files++;
4157             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4158             i++;
4159         }
4160         for(j = 0; j <i; j++) board_chars[j] = string[j];
4161         board_chars[i] = '\0';
4162         string += i + 1;
4163     }
4164     n = sscanf(string, PATTERN, &to_play, &double_push,
4165                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4166                &gamenum, white, black, &relation, &basetime, &increment,
4167                &white_stren, &black_stren, &white_time, &black_time,
4168                &moveNum, str, elapsed_time, move_str, &ics_flip,
4169                &ticking);
4170
4171     if (n < 21) {
4172         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4173         DisplayError(str, 0);
4174         return;
4175     }
4176
4177     /* Convert the move number to internal form */
4178     moveNum = (moveNum - 1) * 2;
4179     if (to_play == 'B') moveNum++;
4180     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4181       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4182                         0, 1);
4183       return;
4184     }
4185
4186     switch (relation) {
4187       case RELATION_OBSERVING_PLAYED:
4188       case RELATION_OBSERVING_STATIC:
4189         if (gamenum == -1) {
4190             /* Old ICC buglet */
4191             relation = RELATION_OBSERVING_STATIC;
4192         }
4193         newGameMode = IcsObserving;
4194         break;
4195       case RELATION_PLAYING_MYMOVE:
4196       case RELATION_PLAYING_NOTMYMOVE:
4197         newGameMode =
4198           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4199             IcsPlayingWhite : IcsPlayingBlack;
4200         break;
4201       case RELATION_EXAMINING:
4202         newGameMode = IcsExamining;
4203         break;
4204       case RELATION_ISOLATED_BOARD:
4205       default:
4206         /* Just display this board.  If user was doing something else,
4207            we will forget about it until the next board comes. */
4208         newGameMode = IcsIdle;
4209         break;
4210       case RELATION_STARTING_POSITION:
4211         newGameMode = gameMode;
4212         break;
4213     }
4214
4215     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4216          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4217       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4218       char *toSqr;
4219       for (k = 0; k < ranks; k++) {
4220         for (j = 0; j < files; j++)
4221           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4222         if(gameInfo.holdingsWidth > 1) {
4223              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4224              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4225         }
4226       }
4227       CopyBoard(partnerBoard, board);
4228       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4229         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4230         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4231       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4232       if(toSqr = strchr(str, '-')) {
4233         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4234         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4235       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4236       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4237       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4238       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4239       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4240       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4241                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4242       DisplayMessage(partnerStatus, "");
4243         partnerBoardValid = TRUE;
4244       return;
4245     }
4246
4247     /* Modify behavior for initial board display on move listing
4248        of wild games.
4249        */
4250     switch (ics_getting_history) {
4251       case H_FALSE:
4252       case H_REQUESTED:
4253         break;
4254       case H_GOT_REQ_HEADER:
4255       case H_GOT_UNREQ_HEADER:
4256         /* This is the initial position of the current game */
4257         gamenum = ics_gamenum;
4258         moveNum = 0;            /* old ICS bug workaround */
4259         if (to_play == 'B') {
4260           startedFromSetupPosition = TRUE;
4261           blackPlaysFirst = TRUE;
4262           moveNum = 1;
4263           if (forwardMostMove == 0) forwardMostMove = 1;
4264           if (backwardMostMove == 0) backwardMostMove = 1;
4265           if (currentMove == 0) currentMove = 1;
4266         }
4267         newGameMode = gameMode;
4268         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4269         break;
4270       case H_GOT_UNWANTED_HEADER:
4271         /* This is an initial board that we don't want */
4272         return;
4273       case H_GETTING_MOVES:
4274         /* Should not happen */
4275         DisplayError(_("Error gathering move list: extra board"), 0);
4276         ics_getting_history = H_FALSE;
4277         return;
4278     }
4279
4280    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4281                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4282      /* [HGM] We seem to have switched variant unexpectedly
4283       * Try to guess new variant from board size
4284       */
4285           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4286           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4287           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4288           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4289           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4290           if(!weird) newVariant = VariantNormal;
4291           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4292           /* Get a move list just to see the header, which
4293              will tell us whether this is really bug or zh */
4294           if (ics_getting_history == H_FALSE) {
4295             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4296             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4297             SendToICS(str);
4298           }
4299     }
4300
4301     /* Take action if this is the first board of a new game, or of a
4302        different game than is currently being displayed.  */
4303     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4304         relation == RELATION_ISOLATED_BOARD) {
4305
4306         /* Forget the old game and get the history (if any) of the new one */
4307         if (gameMode != BeginningOfGame) {
4308           Reset(TRUE, TRUE);
4309         }
4310         newGame = TRUE;
4311         if (appData.autoRaiseBoard) BoardToTop();
4312         prevMove = -3;
4313         if (gamenum == -1) {
4314             newGameMode = IcsIdle;
4315         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4316                    appData.getMoveList && !reqFlag) {
4317             /* Need to get game history */
4318             ics_getting_history = H_REQUESTED;
4319             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4320             SendToICS(str);
4321         }
4322
4323         /* Initially flip the board to have black on the bottom if playing
4324            black or if the ICS flip flag is set, but let the user change
4325            it with the Flip View button. */
4326         flipView = appData.autoFlipView ?
4327           (newGameMode == IcsPlayingBlack) || ics_flip :
4328           appData.flipView;
4329
4330         /* Done with values from previous mode; copy in new ones */
4331         gameMode = newGameMode;
4332         ModeHighlight();
4333         ics_gamenum = gamenum;
4334         if (gamenum == gs_gamenum) {
4335             int klen = strlen(gs_kind);
4336             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4337             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4338             gameInfo.event = StrSave(str);
4339         } else {
4340             gameInfo.event = StrSave("ICS game");
4341         }
4342         gameInfo.site = StrSave(appData.icsHost);
4343         gameInfo.date = PGNDate();
4344         gameInfo.round = StrSave("-");
4345         gameInfo.white = StrSave(white);
4346         gameInfo.black = StrSave(black);
4347         timeControl = basetime * 60 * 1000;
4348         timeControl_2 = 0;
4349         timeIncrement = increment * 1000;
4350         movesPerSession = 0;
4351         gameInfo.timeControl = TimeControlTagValue();
4352         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4353   if (appData.debugMode) {
4354     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4355     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4356     setbuf(debugFP, NULL);
4357   }
4358
4359         gameInfo.outOfBook = NULL;
4360
4361         /* Do we have the ratings? */
4362         if (strcmp(player1Name, white) == 0 &&
4363             strcmp(player2Name, black) == 0) {
4364             if (appData.debugMode)
4365               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4366                       player1Rating, player2Rating);
4367             gameInfo.whiteRating = player1Rating;
4368             gameInfo.blackRating = player2Rating;
4369         } else if (strcmp(player2Name, white) == 0 &&
4370                    strcmp(player1Name, black) == 0) {
4371             if (appData.debugMode)
4372               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4373                       player2Rating, player1Rating);
4374             gameInfo.whiteRating = player2Rating;
4375             gameInfo.blackRating = player1Rating;
4376         }
4377         player1Name[0] = player2Name[0] = NULLCHAR;
4378
4379         /* Silence shouts if requested */
4380         if (appData.quietPlay &&
4381             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4382             SendToICS(ics_prefix);
4383             SendToICS("set shout 0\n");
4384         }
4385     }
4386
4387     /* Deal with midgame name changes */
4388     if (!newGame) {
4389         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4390             if (gameInfo.white) free(gameInfo.white);
4391             gameInfo.white = StrSave(white);
4392         }
4393         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4394             if (gameInfo.black) free(gameInfo.black);
4395             gameInfo.black = StrSave(black);
4396         }
4397     }
4398
4399     /* Throw away game result if anything actually changes in examine mode */
4400     if (gameMode == IcsExamining && !newGame) {
4401         gameInfo.result = GameUnfinished;
4402         if (gameInfo.resultDetails != NULL) {
4403             free(gameInfo.resultDetails);
4404             gameInfo.resultDetails = NULL;
4405         }
4406     }
4407
4408     /* In pausing && IcsExamining mode, we ignore boards coming
4409        in if they are in a different variation than we are. */
4410     if (pauseExamInvalid) return;
4411     if (pausing && gameMode == IcsExamining) {
4412         if (moveNum <= pauseExamForwardMostMove) {
4413             pauseExamInvalid = TRUE;
4414             forwardMostMove = pauseExamForwardMostMove;
4415             return;
4416         }
4417     }
4418
4419   if (appData.debugMode) {
4420     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4421   }
4422     /* Parse the board */
4423     for (k = 0; k < ranks; k++) {
4424       for (j = 0; j < files; j++)
4425         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4426       if(gameInfo.holdingsWidth > 1) {
4427            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4428            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4429       }
4430     }
4431     CopyBoard(boards[moveNum], board);
4432     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4433     if (moveNum == 0) {
4434         startedFromSetupPosition =
4435           !CompareBoards(board, initialPosition);
4436         if(startedFromSetupPosition)
4437             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4438     }
4439
4440     /* [HGM] Set castling rights. Take the outermost Rooks,
4441        to make it also work for FRC opening positions. Note that board12
4442        is really defective for later FRC positions, as it has no way to
4443        indicate which Rook can castle if they are on the same side of King.
4444        For the initial position we grant rights to the outermost Rooks,
4445        and remember thos rights, and we then copy them on positions
4446        later in an FRC game. This means WB might not recognize castlings with
4447        Rooks that have moved back to their original position as illegal,
4448        but in ICS mode that is not its job anyway.
4449     */
4450     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4451     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4452
4453         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4454             if(board[0][i] == WhiteRook) j = i;
4455         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4456         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4457             if(board[0][i] == WhiteRook) j = i;
4458         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4459         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4460             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4461         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4462         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4463             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4464         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4465
4466         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4467         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4468             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4469         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4470             if(board[BOARD_HEIGHT-1][k] == bKing)
4471                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4472         if(gameInfo.variant == VariantTwoKings) {
4473             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4474             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4475             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4476         }
4477     } else { int r;
4478         r = boards[moveNum][CASTLING][0] = initialRights[0];
4479         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4480         r = boards[moveNum][CASTLING][1] = initialRights[1];
4481         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4482         r = boards[moveNum][CASTLING][3] = initialRights[3];
4483         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4484         r = boards[moveNum][CASTLING][4] = initialRights[4];
4485         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4486         /* wildcastle kludge: always assume King has rights */
4487         r = boards[moveNum][CASTLING][2] = initialRights[2];
4488         r = boards[moveNum][CASTLING][5] = initialRights[5];
4489     }
4490     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4491     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4492
4493
4494     if (ics_getting_history == H_GOT_REQ_HEADER ||
4495         ics_getting_history == H_GOT_UNREQ_HEADER) {
4496         /* This was an initial position from a move list, not
4497            the current position */
4498         return;
4499     }
4500
4501     /* Update currentMove and known move number limits */
4502     newMove = newGame || moveNum > forwardMostMove;
4503
4504     if (newGame) {
4505         forwardMostMove = backwardMostMove = currentMove = moveNum;
4506         if (gameMode == IcsExamining && moveNum == 0) {
4507           /* Workaround for ICS limitation: we are not told the wild
4508              type when starting to examine a game.  But if we ask for
4509              the move list, the move list header will tell us */
4510             ics_getting_history = H_REQUESTED;
4511             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4512             SendToICS(str);
4513         }
4514     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4515                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4516 #if ZIPPY
4517         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4518         /* [HGM] applied this also to an engine that is silently watching        */
4519         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4520             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4521             gameInfo.variant == currentlyInitializedVariant) {
4522           takeback = forwardMostMove - moveNum;
4523           for (i = 0; i < takeback; i++) {
4524             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4525             SendToProgram("undo\n", &first);
4526           }
4527         }
4528 #endif
4529
4530         forwardMostMove = moveNum;
4531         if (!pausing || currentMove > forwardMostMove)
4532           currentMove = forwardMostMove;
4533     } else {
4534         /* New part of history that is not contiguous with old part */
4535         if (pausing && gameMode == IcsExamining) {
4536             pauseExamInvalid = TRUE;
4537             forwardMostMove = pauseExamForwardMostMove;
4538             return;
4539         }
4540         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4541 #if ZIPPY
4542             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4543                 // [HGM] when we will receive the move list we now request, it will be
4544                 // fed to the engine from the first move on. So if the engine is not
4545                 // in the initial position now, bring it there.
4546                 InitChessProgram(&first, 0);
4547             }
4548 #endif
4549             ics_getting_history = H_REQUESTED;
4550             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4551             SendToICS(str);
4552         }
4553         forwardMostMove = backwardMostMove = currentMove = moveNum;
4554     }
4555
4556     /* Update the clocks */
4557     if (strchr(elapsed_time, '.')) {
4558       /* Time is in ms */
4559       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4560       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4561     } else {
4562       /* Time is in seconds */
4563       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4564       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4565     }
4566
4567
4568 #if ZIPPY
4569     if (appData.zippyPlay && newGame &&
4570         gameMode != IcsObserving && gameMode != IcsIdle &&
4571         gameMode != IcsExamining)
4572       ZippyFirstBoard(moveNum, basetime, increment);
4573 #endif
4574
4575     /* Put the move on the move list, first converting
4576        to canonical algebraic form. */
4577     if (moveNum > 0) {
4578   if (appData.debugMode) {
4579     if (appData.debugMode) { int f = forwardMostMove;
4580         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4581                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4582                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4583     }
4584     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4585     fprintf(debugFP, "moveNum = %d\n", moveNum);
4586     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4587     setbuf(debugFP, NULL);
4588   }
4589         if (moveNum <= backwardMostMove) {
4590             /* We don't know what the board looked like before
4591                this move.  Punt. */
4592           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4593             strcat(parseList[moveNum - 1], " ");
4594             strcat(parseList[moveNum - 1], elapsed_time);
4595             moveList[moveNum - 1][0] = NULLCHAR;
4596         } else if (strcmp(move_str, "none") == 0) {
4597             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4598             /* Again, we don't know what the board looked like;
4599                this is really the start of the game. */
4600             parseList[moveNum - 1][0] = NULLCHAR;
4601             moveList[moveNum - 1][0] = NULLCHAR;
4602             backwardMostMove = moveNum;
4603             startedFromSetupPosition = TRUE;
4604             fromX = fromY = toX = toY = -1;
4605         } else {
4606           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4607           //                 So we parse the long-algebraic move string in stead of the SAN move
4608           int valid; char buf[MSG_SIZ], *prom;
4609
4610           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4611                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4612           // str looks something like "Q/a1-a2"; kill the slash
4613           if(str[1] == '/')
4614             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4615           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4616           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4617                 strcat(buf, prom); // long move lacks promo specification!
4618           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4619                 if(appData.debugMode)
4620                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4621                 safeStrCpy(move_str, buf, MSG_SIZ);
4622           }
4623           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4624                                 &fromX, &fromY, &toX, &toY, &promoChar)
4625                || ParseOneMove(buf, moveNum - 1, &moveType,
4626                                 &fromX, &fromY, &toX, &toY, &promoChar);
4627           // end of long SAN patch
4628           if (valid) {
4629             (void) CoordsToAlgebraic(boards[moveNum - 1],
4630                                      PosFlags(moveNum - 1),
4631                                      fromY, fromX, toY, toX, promoChar,
4632                                      parseList[moveNum-1]);
4633             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4634               case MT_NONE:
4635               case MT_STALEMATE:
4636               default:
4637                 break;
4638               case MT_CHECK:
4639                 if(gameInfo.variant != VariantShogi)
4640                     strcat(parseList[moveNum - 1], "+");
4641                 break;
4642               case MT_CHECKMATE:
4643               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4644                 strcat(parseList[moveNum - 1], "#");
4645                 break;
4646             }
4647             strcat(parseList[moveNum - 1], " ");
4648             strcat(parseList[moveNum - 1], elapsed_time);
4649             /* currentMoveString is set as a side-effect of ParseOneMove */
4650             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4651             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4652             strcat(moveList[moveNum - 1], "\n");
4653
4654             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4655                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4656               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4657                 ChessSquare old, new = boards[moveNum][k][j];
4658                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4659                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4660                   if(old == new) continue;
4661                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4662                   else if(new == WhiteWazir || new == BlackWazir) {
4663                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4664                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4665                       else boards[moveNum][k][j] = old; // preserve type of Gold
4666                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4667                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4668               }
4669           } else {
4670             /* Move from ICS was illegal!?  Punt. */
4671             if (appData.debugMode) {
4672               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4673               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4674             }
4675             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4676             strcat(parseList[moveNum - 1], " ");
4677             strcat(parseList[moveNum - 1], elapsed_time);
4678             moveList[moveNum - 1][0] = NULLCHAR;
4679             fromX = fromY = toX = toY = -1;
4680           }
4681         }
4682   if (appData.debugMode) {
4683     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4684     setbuf(debugFP, NULL);
4685   }
4686
4687 #if ZIPPY
4688         /* Send move to chess program (BEFORE animating it). */
4689         if (appData.zippyPlay && !newGame && newMove &&
4690            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4691
4692             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4693                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4694                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4695                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4696                             move_str);
4697                     DisplayError(str, 0);
4698                 } else {
4699                     if (first.sendTime) {
4700                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4701                     }
4702                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4703                     if (firstMove && !bookHit) {
4704                         firstMove = FALSE;
4705                         if (first.useColors) {
4706                           SendToProgram(gameMode == IcsPlayingWhite ?
4707                                         "white\ngo\n" :
4708                                         "black\ngo\n", &first);
4709                         } else {
4710                           SendToProgram("go\n", &first);
4711                         }
4712                         first.maybeThinking = TRUE;
4713                     }
4714                 }
4715             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4716               if (moveList[moveNum - 1][0] == NULLCHAR) {
4717                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4718                 DisplayError(str, 0);
4719               } else {
4720                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4721                 SendMoveToProgram(moveNum - 1, &first);
4722               }
4723             }
4724         }
4725 #endif
4726     }
4727
4728     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4729         /* If move comes from a remote source, animate it.  If it
4730            isn't remote, it will have already been animated. */
4731         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4732             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4733         }
4734         if (!pausing && appData.highlightLastMove) {
4735             SetHighlights(fromX, fromY, toX, toY);
4736         }
4737     }
4738
4739     /* Start the clocks */
4740     whiteFlag = blackFlag = FALSE;
4741     appData.clockMode = !(basetime == 0 && increment == 0);
4742     if (ticking == 0) {
4743       ics_clock_paused = TRUE;
4744       StopClocks();
4745     } else if (ticking == 1) {
4746       ics_clock_paused = FALSE;
4747     }
4748     if (gameMode == IcsIdle ||
4749         relation == RELATION_OBSERVING_STATIC ||
4750         relation == RELATION_EXAMINING ||
4751         ics_clock_paused)
4752       DisplayBothClocks();
4753     else
4754       StartClocks();
4755
4756     /* Display opponents and material strengths */
4757     if (gameInfo.variant != VariantBughouse &&
4758         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4759         if (tinyLayout || smallLayout) {
4760             if(gameInfo.variant == VariantNormal)
4761               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4762                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4763                     basetime, increment);
4764             else
4765               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4766                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4767                     basetime, increment, (int) gameInfo.variant);
4768         } else {
4769             if(gameInfo.variant == VariantNormal)
4770               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4771                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4772                     basetime, increment);
4773             else
4774               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4775                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4776                     basetime, increment, VariantName(gameInfo.variant));
4777         }
4778         DisplayTitle(str);
4779   if (appData.debugMode) {
4780     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4781   }
4782     }
4783
4784
4785     /* Display the board */
4786     if (!pausing && !appData.noGUI) {
4787
4788       if (appData.premove)
4789           if (!gotPremove ||
4790              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4791              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4792               ClearPremoveHighlights();
4793
4794       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4795         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4796       DrawPosition(j, boards[currentMove]);
4797
4798       DisplayMove(moveNum - 1);
4799       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4800             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4801               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4802         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4803       }
4804     }
4805
4806     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4807 #if ZIPPY
4808     if(bookHit) { // [HGM] book: simulate book reply
4809         static char bookMove[MSG_SIZ]; // a bit generous?
4810
4811         programStats.nodes = programStats.depth = programStats.time =
4812         programStats.score = programStats.got_only_move = 0;
4813         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4814
4815         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4816         strcat(bookMove, bookHit);
4817         HandleMachineMove(bookMove, &first);
4818     }
4819 #endif
4820 }
4821
4822 void
4823 GetMoveListEvent()
4824 {
4825     char buf[MSG_SIZ];
4826     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4827         ics_getting_history = H_REQUESTED;
4828         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4829         SendToICS(buf);
4830     }
4831 }
4832
4833 void
4834 AnalysisPeriodicEvent(force)
4835      int force;
4836 {
4837     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4838          && !force) || !appData.periodicUpdates)
4839       return;
4840
4841     /* Send . command to Crafty to collect stats */
4842     SendToProgram(".\n", &first);
4843
4844     /* Don't send another until we get a response (this makes
4845        us stop sending to old Crafty's which don't understand
4846        the "." command (sending illegal cmds resets node count & time,
4847        which looks bad)) */
4848     programStats.ok_to_send = 0;
4849 }
4850
4851 void ics_update_width(new_width)
4852         int new_width;
4853 {
4854         ics_printf("set width %d\n", new_width);
4855 }
4856
4857 void
4858 SendMoveToProgram(moveNum, cps)
4859      int moveNum;
4860      ChessProgramState *cps;
4861 {
4862     char buf[MSG_SIZ];
4863
4864     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4865         // null move in variant where engine does not understand it (for analysis purposes)
4866         SendBoard(cps, moveNum + 1); // send position after move in stead.
4867         return;
4868     }
4869     if (cps->useUsermove) {
4870       SendToProgram("usermove ", cps);
4871     }
4872     if (cps->useSAN) {
4873       char *space;
4874       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4875         int len = space - parseList[moveNum];
4876         memcpy(buf, parseList[moveNum], len);
4877         buf[len++] = '\n';
4878         buf[len] = NULLCHAR;
4879       } else {
4880         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4881       }
4882       SendToProgram(buf, cps);
4883     } else {
4884       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4885         AlphaRank(moveList[moveNum], 4);
4886         SendToProgram(moveList[moveNum], cps);
4887         AlphaRank(moveList[moveNum], 4); // and back
4888       } else
4889       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4890        * the engine. It would be nice to have a better way to identify castle
4891        * moves here. */
4892       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4893                                                                          && cps->useOOCastle) {
4894         int fromX = moveList[moveNum][0] - AAA;
4895         int fromY = moveList[moveNum][1] - ONE;
4896         int toX = moveList[moveNum][2] - AAA;
4897         int toY = moveList[moveNum][3] - ONE;
4898         if((boards[moveNum][fromY][fromX] == WhiteKing
4899             && boards[moveNum][toY][toX] == WhiteRook)
4900            || (boards[moveNum][fromY][fromX] == BlackKing
4901                && boards[moveNum][toY][toX] == BlackRook)) {
4902           if(toX > fromX) SendToProgram("O-O\n", cps);
4903           else SendToProgram("O-O-O\n", cps);
4904         }
4905         else SendToProgram(moveList[moveNum], cps);
4906       } else
4907       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4908         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4909           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4910           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4911                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4912         } else
4913           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4914                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4915         SendToProgram(buf, cps);
4916       }
4917       else SendToProgram(moveList[moveNum], cps);
4918       /* End of additions by Tord */
4919     }
4920
4921     /* [HGM] setting up the opening has brought engine in force mode! */
4922     /*       Send 'go' if we are in a mode where machine should play. */
4923     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4924         (gameMode == TwoMachinesPlay   ||
4925 #if ZIPPY
4926          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4927 #endif
4928          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4929         SendToProgram("go\n", cps);
4930   if (appData.debugMode) {
4931     fprintf(debugFP, "(extra)\n");
4932   }
4933     }
4934     setboardSpoiledMachineBlack = 0;
4935 }
4936
4937 void
4938 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4939      ChessMove moveType;
4940      int fromX, fromY, toX, toY;
4941      char promoChar;
4942 {
4943     char user_move[MSG_SIZ];
4944
4945     switch (moveType) {
4946       default:
4947         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4948                 (int)moveType, fromX, fromY, toX, toY);
4949         DisplayError(user_move + strlen("say "), 0);
4950         break;
4951       case WhiteKingSideCastle:
4952       case BlackKingSideCastle:
4953       case WhiteQueenSideCastleWild:
4954       case BlackQueenSideCastleWild:
4955       /* PUSH Fabien */
4956       case WhiteHSideCastleFR:
4957       case BlackHSideCastleFR:
4958       /* POP Fabien */
4959         snprintf(user_move, MSG_SIZ, "o-o\n");
4960         break;
4961       case WhiteQueenSideCastle:
4962       case BlackQueenSideCastle:
4963       case WhiteKingSideCastleWild:
4964       case BlackKingSideCastleWild:
4965       /* PUSH Fabien */
4966       case WhiteASideCastleFR:
4967       case BlackASideCastleFR:
4968       /* POP Fabien */
4969         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4970         break;
4971       case WhiteNonPromotion:
4972       case BlackNonPromotion:
4973         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4974         break;
4975       case WhitePromotion:
4976       case BlackPromotion:
4977         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4978           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4979                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4980                 PieceToChar(WhiteFerz));
4981         else if(gameInfo.variant == VariantGreat)
4982           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4983                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4984                 PieceToChar(WhiteMan));
4985         else
4986           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4987                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4988                 promoChar);
4989         break;
4990       case WhiteDrop:
4991       case BlackDrop:
4992       drop:
4993         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4994                  ToUpper(PieceToChar((ChessSquare) fromX)),
4995                  AAA + toX, ONE + toY);
4996         break;
4997       case IllegalMove:  /* could be a variant we don't quite understand */
4998         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4999       case NormalMove:
5000       case WhiteCapturesEnPassant:
5001       case BlackCapturesEnPassant:
5002         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5003                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5004         break;
5005     }
5006     SendToICS(user_move);
5007     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5008         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5009 }
5010
5011 void
5012 UploadGameEvent()
5013 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5014     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5015     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5016     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5017         DisplayError("You cannot do this while you are playing or observing", 0);
5018         return;
5019     }
5020     if(gameMode != IcsExamining) { // is this ever not the case?
5021         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5022
5023         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5024           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5025         } else { // on FICS we must first go to general examine mode
5026           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5027         }
5028         if(gameInfo.variant != VariantNormal) {
5029             // try figure out wild number, as xboard names are not always valid on ICS
5030             for(i=1; i<=36; i++) {
5031               snprintf(buf, MSG_SIZ, "wild/%d", i);
5032                 if(StringToVariant(buf) == gameInfo.variant) break;
5033             }
5034             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5035             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5036             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5037         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5038         SendToICS(ics_prefix);
5039         SendToICS(buf);
5040         if(startedFromSetupPosition || backwardMostMove != 0) {
5041           fen = PositionToFEN(backwardMostMove, NULL);
5042           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5043             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5044             SendToICS(buf);
5045           } else { // FICS: everything has to set by separate bsetup commands
5046             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5047             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5048             SendToICS(buf);
5049             if(!WhiteOnMove(backwardMostMove)) {
5050                 SendToICS("bsetup tomove black\n");
5051             }
5052             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5053             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5054             SendToICS(buf);
5055             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5056             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5057             SendToICS(buf);
5058             i = boards[backwardMostMove][EP_STATUS];
5059             if(i >= 0) { // set e.p.
5060               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5061                 SendToICS(buf);
5062             }
5063             bsetup++;
5064           }
5065         }
5066       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5067             SendToICS("bsetup done\n"); // switch to normal examining.
5068     }
5069     for(i = backwardMostMove; i<last; i++) {
5070         char buf[20];
5071         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5072         SendToICS(buf);
5073     }
5074     SendToICS(ics_prefix);
5075     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5076 }
5077
5078 void
5079 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5080      int rf, ff, rt, ft;
5081      char promoChar;
5082      char move[7];
5083 {
5084     if (rf == DROP_RANK) {
5085       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5086       sprintf(move, "%c@%c%c\n",
5087                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5088     } else {
5089         if (promoChar == 'x' || promoChar == NULLCHAR) {
5090           sprintf(move, "%c%c%c%c\n",
5091                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5092         } else {
5093             sprintf(move, "%c%c%c%c%c\n",
5094                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5095         }
5096     }
5097 }
5098
5099 void
5100 ProcessICSInitScript(f)
5101      FILE *f;
5102 {
5103     char buf[MSG_SIZ];
5104
5105     while (fgets(buf, MSG_SIZ, f)) {
5106         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5107     }
5108
5109     fclose(f);
5110 }
5111
5112
5113 static int lastX, lastY, selectFlag, dragging;
5114
5115 void
5116 Sweep(int step)
5117 {
5118     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5119     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5120     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5121     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5122     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5123     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5124     do {
5125         promoSweep -= step;
5126         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5127         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5128         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5129         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5130         if(!step) step = 1;
5131     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5132             appData.testLegality && (promoSweep == king ||
5133             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5134     ChangeDragPiece(promoSweep);
5135 }
5136
5137 int PromoScroll(int x, int y)
5138 {
5139   int step = 0;
5140
5141   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5142   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5143   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5144   if(!step) return FALSE;
5145   lastX = x; lastY = y;
5146   if((promoSweep < BlackPawn) == flipView) step = -step;
5147   if(step > 0) selectFlag = 1;
5148   if(!selectFlag) Sweep(step);
5149   return FALSE;
5150 }
5151
5152 void
5153 NextPiece(int step)
5154 {
5155     ChessSquare piece = boards[currentMove][toY][toX];
5156     do {
5157         pieceSweep -= step;
5158         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5159         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5160         if(!step) step = -1;
5161     } while(PieceToChar(pieceSweep) == '.');
5162     boards[currentMove][toY][toX] = pieceSweep;
5163     DrawPosition(FALSE, boards[currentMove]);
5164     boards[currentMove][toY][toX] = piece;
5165 }
5166 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5167 void
5168 AlphaRank(char *move, int n)
5169 {
5170 //    char *p = move, c; int x, y;
5171
5172     if (appData.debugMode) {
5173         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5174     }
5175
5176     if(move[1]=='*' &&
5177        move[2]>='0' && move[2]<='9' &&
5178        move[3]>='a' && move[3]<='x'    ) {
5179         move[1] = '@';
5180         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5181         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5182     } else
5183     if(move[0]>='0' && move[0]<='9' &&
5184        move[1]>='a' && move[1]<='x' &&
5185        move[2]>='0' && move[2]<='9' &&
5186        move[3]>='a' && move[3]<='x'    ) {
5187         /* input move, Shogi -> normal */
5188         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5189         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5190         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5191         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5192     } else
5193     if(move[1]=='@' &&
5194        move[3]>='0' && move[3]<='9' &&
5195        move[2]>='a' && move[2]<='x'    ) {
5196         move[1] = '*';
5197         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5198         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5199     } else
5200     if(
5201        move[0]>='a' && move[0]<='x' &&
5202        move[3]>='0' && move[3]<='9' &&
5203        move[2]>='a' && move[2]<='x'    ) {
5204          /* output move, normal -> Shogi */
5205         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5206         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5207         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5208         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5209         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5210     }
5211     if (appData.debugMode) {
5212         fprintf(debugFP, "   out = '%s'\n", move);
5213     }
5214 }
5215
5216 char yy_textstr[8000];
5217
5218 /* Parser for moves from gnuchess, ICS, or user typein box */
5219 Boolean
5220 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5221      char *move;
5222      int moveNum;
5223      ChessMove *moveType;
5224      int *fromX, *fromY, *toX, *toY;
5225      char *promoChar;
5226 {
5227     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5228
5229     switch (*moveType) {
5230       case WhitePromotion:
5231       case BlackPromotion:
5232       case WhiteNonPromotion:
5233       case BlackNonPromotion:
5234       case NormalMove:
5235       case WhiteCapturesEnPassant:
5236       case BlackCapturesEnPassant:
5237       case WhiteKingSideCastle:
5238       case WhiteQueenSideCastle:
5239       case BlackKingSideCastle:
5240       case BlackQueenSideCastle:
5241       case WhiteKingSideCastleWild:
5242       case WhiteQueenSideCastleWild:
5243       case BlackKingSideCastleWild:
5244       case BlackQueenSideCastleWild:
5245       /* Code added by Tord: */
5246       case WhiteHSideCastleFR:
5247       case WhiteASideCastleFR:
5248       case BlackHSideCastleFR:
5249       case BlackASideCastleFR:
5250       /* End of code added by Tord */
5251       case IllegalMove:         /* bug or odd chess variant */
5252         *fromX = currentMoveString[0] - AAA;
5253         *fromY = currentMoveString[1] - ONE;
5254         *toX = currentMoveString[2] - AAA;
5255         *toY = currentMoveString[3] - ONE;
5256         *promoChar = currentMoveString[4];
5257         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5258             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5259     if (appData.debugMode) {
5260         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5261     }
5262             *fromX = *fromY = *toX = *toY = 0;
5263             return FALSE;
5264         }
5265         if (appData.testLegality) {
5266           return (*moveType != IllegalMove);
5267         } else {
5268           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5269                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5270         }
5271
5272       case WhiteDrop:
5273       case BlackDrop:
5274         *fromX = *moveType == WhiteDrop ?
5275           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5276           (int) CharToPiece(ToLower(currentMoveString[0]));
5277         *fromY = DROP_RANK;
5278         *toX = currentMoveString[2] - AAA;
5279         *toY = currentMoveString[3] - ONE;
5280         *promoChar = NULLCHAR;
5281         return TRUE;
5282
5283       case AmbiguousMove:
5284       case ImpossibleMove:
5285       case EndOfFile:
5286       case ElapsedTime:
5287       case Comment:
5288       case PGNTag:
5289       case NAG:
5290       case WhiteWins:
5291       case BlackWins:
5292       case GameIsDrawn:
5293       default:
5294     if (appData.debugMode) {
5295         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5296     }
5297         /* bug? */
5298         *fromX = *fromY = *toX = *toY = 0;
5299         *promoChar = NULLCHAR;
5300         return FALSE;
5301     }
5302 }
5303
5304 Boolean pushed = FALSE;
5305 char *lastParseAttempt;
5306
5307 void
5308 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5309 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5310   int fromX, fromY, toX, toY; char promoChar;
5311   ChessMove moveType;
5312   Boolean valid;
5313   int nr = 0;
5314
5315   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5316     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5317     pushed = TRUE;
5318   }
5319   endPV = forwardMostMove;
5320   do {
5321     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5322     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5323     lastParseAttempt = pv;
5324     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5325 if(appData.debugMode){
5326 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);
5327 }
5328     if(!valid && nr == 0 &&
5329        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5330         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5331         // Hande case where played move is different from leading PV move
5332         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5333         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5334         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5335         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5336           endPV += 2; // if position different, keep this
5337           moveList[endPV-1][0] = fromX + AAA;
5338           moveList[endPV-1][1] = fromY + ONE;
5339           moveList[endPV-1][2] = toX + AAA;
5340           moveList[endPV-1][3] = toY + ONE;
5341           parseList[endPV-1][0] = NULLCHAR;
5342           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5343         }
5344       }
5345     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5346     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5347     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5348     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5349         valid++; // allow comments in PV
5350         continue;
5351     }
5352     nr++;
5353     if(endPV+1 > framePtr) break; // no space, truncate
5354     if(!valid) break;
5355     endPV++;
5356     CopyBoard(boards[endPV], boards[endPV-1]);
5357     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5358     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5359     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5360     CoordsToAlgebraic(boards[endPV - 1],
5361                              PosFlags(endPV - 1),
5362                              fromY, fromX, toY, toX, promoChar,
5363                              parseList[endPV - 1]);
5364   } while(valid);
5365   if(atEnd == 2) return; // used hidden, for PV conversion
5366   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5367   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5368   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5369                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5370   DrawPosition(TRUE, boards[currentMove]);
5371 }
5372
5373 int
5374 MultiPV(ChessProgramState *cps)
5375 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5376         int i;
5377         for(i=0; i<cps->nrOptions; i++)
5378             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5379                 return i;
5380         return -1;
5381 }
5382
5383 Boolean
5384 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5385 {
5386         int startPV, multi, lineStart, origIndex = index;
5387         char *p, buf2[MSG_SIZ];
5388
5389         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5390         lastX = x; lastY = y;
5391         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5392         lineStart = startPV = index;
5393         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5394         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5395         index = startPV;
5396         do{ while(buf[index] && buf[index] != '\n') index++;
5397         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5398         buf[index] = 0;
5399         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5400                 int n = first.option[multi].value;
5401                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5402                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5403                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5404                 first.option[multi].value = n;
5405                 *start = *end = 0;
5406                 return FALSE;
5407         }
5408         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5409         *start = startPV; *end = index-1;
5410         return TRUE;
5411 }
5412
5413 char *
5414 PvToSAN(char *pv)
5415 {
5416         static char buf[10*MSG_SIZ];
5417         int i, k=0, savedEnd=endPV;
5418         *buf = NULLCHAR;
5419         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5420         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5421         for(i = forwardMostMove; i<endPV; i++){
5422             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5423             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5424             k += strlen(buf+k);
5425         }
5426         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5427         if(forwardMostMove < savedEnd) PopInner(0);
5428         endPV = savedEnd;
5429         return buf;
5430 }
5431
5432 Boolean
5433 LoadPV(int x, int y)
5434 { // called on right mouse click to load PV
5435   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5436   lastX = x; lastY = y;
5437   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5438   return TRUE;
5439 }
5440
5441 void
5442 UnLoadPV()
5443 {
5444   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5445   if(endPV < 0) return;
5446   endPV = -1;
5447   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5448         Boolean saveAnimate = appData.animate;
5449         if(pushed) {
5450             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5451                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5452             } else storedGames--; // abandon shelved tail of original game
5453         }
5454         pushed = FALSE;
5455         forwardMostMove = currentMove;
5456         currentMove = oldFMM;
5457         appData.animate = FALSE;
5458         ToNrEvent(forwardMostMove);
5459         appData.animate = saveAnimate;
5460   }
5461   currentMove = forwardMostMove;
5462   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5463   ClearPremoveHighlights();
5464   DrawPosition(TRUE, boards[currentMove]);
5465 }
5466
5467 void
5468 MovePV(int x, int y, int h)
5469 { // step through PV based on mouse coordinates (called on mouse move)
5470   int margin = h>>3, step = 0;
5471
5472   // we must somehow check if right button is still down (might be released off board!)
5473   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5474   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5475   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5476   if(!step) return;
5477   lastX = x; lastY = y;
5478
5479   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5480   if(endPV < 0) return;
5481   if(y < margin) step = 1; else
5482   if(y > h - margin) step = -1;
5483   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5484   currentMove += step;
5485   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5486   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5487                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5488   DrawPosition(FALSE, boards[currentMove]);
5489 }
5490
5491
5492 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5493 // All positions will have equal probability, but the current method will not provide a unique
5494 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5495 #define DARK 1
5496 #define LITE 2
5497 #define ANY 3
5498
5499 int squaresLeft[4];
5500 int piecesLeft[(int)BlackPawn];
5501 int seed, nrOfShuffles;
5502
5503 void GetPositionNumber()
5504 {       // sets global variable seed
5505         int i;
5506
5507         seed = appData.defaultFrcPosition;
5508         if(seed < 0) { // randomize based on time for negative FRC position numbers
5509                 for(i=0; i<50; i++) seed += random();
5510                 seed = random() ^ random() >> 8 ^ random() << 8;
5511                 if(seed<0) seed = -seed;
5512         }
5513 }
5514
5515 int put(Board board, int pieceType, int rank, int n, int shade)
5516 // put the piece on the (n-1)-th empty squares of the given shade
5517 {
5518         int i;
5519
5520         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5521                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5522                         board[rank][i] = (ChessSquare) pieceType;
5523                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5524                         squaresLeft[ANY]--;
5525                         piecesLeft[pieceType]--;
5526                         return i;
5527                 }
5528         }
5529         return -1;
5530 }
5531
5532
5533 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5534 // calculate where the next piece goes, (any empty square), and put it there
5535 {
5536         int i;
5537
5538         i = seed % squaresLeft[shade];
5539         nrOfShuffles *= squaresLeft[shade];
5540         seed /= squaresLeft[shade];
5541         put(board, pieceType, rank, i, shade);
5542 }
5543
5544 void AddTwoPieces(Board board, int pieceType, int rank)
5545 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5546 {
5547         int i, n=squaresLeft[ANY], j=n-1, k;
5548
5549         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5550         i = seed % k;  // pick one
5551         nrOfShuffles *= k;
5552         seed /= k;
5553         while(i >= j) i -= j--;
5554         j = n - 1 - j; i += j;
5555         put(board, pieceType, rank, j, ANY);
5556         put(board, pieceType, rank, i, ANY);
5557 }
5558
5559 void SetUpShuffle(Board board, int number)
5560 {
5561         int i, p, first=1;
5562
5563         GetPositionNumber(); nrOfShuffles = 1;
5564
5565         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5566         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5567         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5568
5569         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5570
5571         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5572             p = (int) board[0][i];
5573             if(p < (int) BlackPawn) piecesLeft[p] ++;
5574             board[0][i] = EmptySquare;
5575         }
5576
5577         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5578             // shuffles restricted to allow normal castling put KRR first
5579             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5580                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5581             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5582                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5583             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5584                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5585             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5586                 put(board, WhiteRook, 0, 0, ANY);
5587             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5588         }
5589
5590         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5591             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5592             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5593                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5594                 while(piecesLeft[p] >= 2) {
5595                     AddOnePiece(board, p, 0, LITE);
5596                     AddOnePiece(board, p, 0, DARK);
5597                 }
5598                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5599             }
5600
5601         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5602             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5603             // but we leave King and Rooks for last, to possibly obey FRC restriction
5604             if(p == (int)WhiteRook) continue;
5605             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5606             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5607         }
5608
5609         // now everything is placed, except perhaps King (Unicorn) and Rooks
5610
5611         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5612             // Last King gets castling rights
5613             while(piecesLeft[(int)WhiteUnicorn]) {
5614                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5615                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5616             }
5617
5618             while(piecesLeft[(int)WhiteKing]) {
5619                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5620                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5621             }
5622
5623
5624         } else {
5625             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5626             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5627         }
5628
5629         // Only Rooks can be left; simply place them all
5630         while(piecesLeft[(int)WhiteRook]) {
5631                 i = put(board, WhiteRook, 0, 0, ANY);
5632                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5633                         if(first) {
5634                                 first=0;
5635                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5636                         }
5637                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5638                 }
5639         }
5640         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5641             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5642         }
5643
5644         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5645 }
5646
5647 int SetCharTable( char *table, const char * map )
5648 /* [HGM] moved here from winboard.c because of its general usefulness */
5649 /*       Basically a safe strcpy that uses the last character as King */
5650 {
5651     int result = FALSE; int NrPieces;
5652
5653     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5654                     && NrPieces >= 12 && !(NrPieces&1)) {
5655         int i; /* [HGM] Accept even length from 12 to 34 */
5656
5657         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5658         for( i=0; i<NrPieces/2-1; i++ ) {
5659             table[i] = map[i];
5660             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5661         }
5662         table[(int) WhiteKing]  = map[NrPieces/2-1];
5663         table[(int) BlackKing]  = map[NrPieces-1];
5664
5665         result = TRUE;
5666     }
5667
5668     return result;
5669 }
5670
5671 void Prelude(Board board)
5672 {       // [HGM] superchess: random selection of exo-pieces
5673         int i, j, k; ChessSquare p;
5674         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5675
5676         GetPositionNumber(); // use FRC position number
5677
5678         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5679             SetCharTable(pieceToChar, appData.pieceToCharTable);
5680             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5681                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5682         }
5683
5684         j = seed%4;                 seed /= 4;
5685         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5686         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5687         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5688         j = seed%3 + (seed%3 >= j); seed /= 3;
5689         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5690         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5691         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5692         j = seed%3;                 seed /= 3;
5693         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5694         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5695         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5696         j = seed%2 + (seed%2 >= j); seed /= 2;
5697         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5698         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5699         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5700         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5701         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5702         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5703         put(board, exoPieces[0],    0, 0, ANY);
5704         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5705 }
5706
5707 void
5708 InitPosition(redraw)
5709      int redraw;
5710 {
5711     ChessSquare (* pieces)[BOARD_FILES];
5712     int i, j, pawnRow, overrule,
5713     oldx = gameInfo.boardWidth,
5714     oldy = gameInfo.boardHeight,
5715     oldh = gameInfo.holdingsWidth;
5716     static int oldv;
5717
5718     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5719
5720     /* [AS] Initialize pv info list [HGM] and game status */
5721     {
5722         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5723             pvInfoList[i].depth = 0;
5724             boards[i][EP_STATUS] = EP_NONE;
5725             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5726         }
5727
5728         initialRulePlies = 0; /* 50-move counter start */
5729
5730         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5731         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5732     }
5733
5734
5735     /* [HGM] logic here is completely changed. In stead of full positions */
5736     /* the initialized data only consist of the two backranks. The switch */
5737     /* selects which one we will use, which is than copied to the Board   */
5738     /* initialPosition, which for the rest is initialized by Pawns and    */
5739     /* empty squares. This initial position is then copied to boards[0],  */
5740     /* possibly after shuffling, so that it remains available.            */
5741
5742     gameInfo.holdingsWidth = 0; /* default board sizes */
5743     gameInfo.boardWidth    = 8;
5744     gameInfo.boardHeight   = 8;
5745     gameInfo.holdingsSize  = 0;
5746     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5747     for(i=0; i<BOARD_FILES-2; i++)
5748       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5749     initialPosition[EP_STATUS] = EP_NONE;
5750     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5751     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5752          SetCharTable(pieceNickName, appData.pieceNickNames);
5753     else SetCharTable(pieceNickName, "............");
5754     pieces = FIDEArray;
5755
5756     switch (gameInfo.variant) {
5757     case VariantFischeRandom:
5758       shuffleOpenings = TRUE;
5759     default:
5760       break;
5761     case VariantShatranj:
5762       pieces = ShatranjArray;
5763       nrCastlingRights = 0;
5764       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5765       break;
5766     case VariantMakruk:
5767       pieces = makrukArray;
5768       nrCastlingRights = 0;
5769       startedFromSetupPosition = TRUE;
5770       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5771       break;
5772     case VariantTwoKings:
5773       pieces = twoKingsArray;
5774       break;
5775     case VariantGrand:
5776       pieces = GrandArray;
5777       nrCastlingRights = 0;
5778       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5779       gameInfo.boardWidth = 10;
5780       gameInfo.boardHeight = 10;
5781       gameInfo.holdingsSize = 7;
5782       break;
5783     case VariantCapaRandom:
5784       shuffleOpenings = TRUE;
5785     case VariantCapablanca:
5786       pieces = CapablancaArray;
5787       gameInfo.boardWidth = 10;
5788       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5789       break;
5790     case VariantGothic:
5791       pieces = GothicArray;
5792       gameInfo.boardWidth = 10;
5793       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5794       break;
5795     case VariantSChess:
5796       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5797       gameInfo.holdingsSize = 7;
5798       break;
5799     case VariantJanus:
5800       pieces = JanusArray;
5801       gameInfo.boardWidth = 10;
5802       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5803       nrCastlingRights = 6;
5804         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5805         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5806         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5807         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5808         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5809         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5810       break;
5811     case VariantFalcon:
5812       pieces = FalconArray;
5813       gameInfo.boardWidth = 10;
5814       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5815       break;
5816     case VariantXiangqi:
5817       pieces = XiangqiArray;
5818       gameInfo.boardWidth  = 9;
5819       gameInfo.boardHeight = 10;
5820       nrCastlingRights = 0;
5821       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5822       break;
5823     case VariantShogi:
5824       pieces = ShogiArray;
5825       gameInfo.boardWidth  = 9;
5826       gameInfo.boardHeight = 9;
5827       gameInfo.holdingsSize = 7;
5828       nrCastlingRights = 0;
5829       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5830       break;
5831     case VariantCourier:
5832       pieces = CourierArray;
5833       gameInfo.boardWidth  = 12;
5834       nrCastlingRights = 0;
5835       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5836       break;
5837     case VariantKnightmate:
5838       pieces = KnightmateArray;
5839       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5840       break;
5841     case VariantSpartan:
5842       pieces = SpartanArray;
5843       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5844       break;
5845     case VariantFairy:
5846       pieces = fairyArray;
5847       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5848       break;
5849     case VariantGreat:
5850       pieces = GreatArray;
5851       gameInfo.boardWidth = 10;
5852       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5853       gameInfo.holdingsSize = 8;
5854       break;
5855     case VariantSuper:
5856       pieces = FIDEArray;
5857       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5858       gameInfo.holdingsSize = 8;
5859       startedFromSetupPosition = TRUE;
5860       break;
5861     case VariantCrazyhouse:
5862     case VariantBughouse:
5863       pieces = FIDEArray;
5864       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5865       gameInfo.holdingsSize = 5;
5866       break;
5867     case VariantWildCastle:
5868       pieces = FIDEArray;
5869       /* !!?shuffle with kings guaranteed to be on d or e file */
5870       shuffleOpenings = 1;
5871       break;
5872     case VariantNoCastle:
5873       pieces = FIDEArray;
5874       nrCastlingRights = 0;
5875       /* !!?unconstrained back-rank shuffle */
5876       shuffleOpenings = 1;
5877       break;
5878     }
5879
5880     overrule = 0;
5881     if(appData.NrFiles >= 0) {
5882         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5883         gameInfo.boardWidth = appData.NrFiles;
5884     }
5885     if(appData.NrRanks >= 0) {
5886         gameInfo.boardHeight = appData.NrRanks;
5887     }
5888     if(appData.holdingsSize >= 0) {
5889         i = appData.holdingsSize;
5890         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5891         gameInfo.holdingsSize = i;
5892     }
5893     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5894     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5895         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5896
5897     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5898     if(pawnRow < 1) pawnRow = 1;
5899     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5900
5901     /* User pieceToChar list overrules defaults */
5902     if(appData.pieceToCharTable != NULL)
5903         SetCharTable(pieceToChar, appData.pieceToCharTable);
5904
5905     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5906
5907         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5908             s = (ChessSquare) 0; /* account holding counts in guard band */
5909         for( i=0; i<BOARD_HEIGHT; i++ )
5910             initialPosition[i][j] = s;
5911
5912         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5913         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5914         initialPosition[pawnRow][j] = WhitePawn;
5915         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5916         if(gameInfo.variant == VariantXiangqi) {
5917             if(j&1) {
5918                 initialPosition[pawnRow][j] =
5919                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5920                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5921                    initialPosition[2][j] = WhiteCannon;
5922                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5923                 }
5924             }
5925         }
5926         if(gameInfo.variant == VariantGrand) {
5927             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5928                initialPosition[0][j] = WhiteRook;
5929                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5930             }
5931         }
5932         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5933     }
5934     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5935
5936             j=BOARD_LEFT+1;
5937             initialPosition[1][j] = WhiteBishop;
5938             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5939             j=BOARD_RGHT-2;
5940             initialPosition[1][j] = WhiteRook;
5941             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5942     }
5943
5944     if( nrCastlingRights == -1) {
5945         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5946         /*       This sets default castling rights from none to normal corners   */
5947         /* Variants with other castling rights must set them themselves above    */
5948         nrCastlingRights = 6;
5949
5950         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5951         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5952         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5953         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5954         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5955         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5956      }
5957
5958      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5959      if(gameInfo.variant == VariantGreat) { // promotion commoners
5960         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5961         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5962         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5963         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5964      }
5965      if( gameInfo.variant == VariantSChess ) {
5966       initialPosition[1][0] = BlackMarshall;
5967       initialPosition[2][0] = BlackAngel;
5968       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5969       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5970       initialPosition[1][1] = initialPosition[2][1] = 
5971       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5972      }
5973   if (appData.debugMode) {
5974     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5975   }
5976     if(shuffleOpenings) {
5977         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5978         startedFromSetupPosition = TRUE;
5979     }
5980     if(startedFromPositionFile) {
5981       /* [HGM] loadPos: use PositionFile for every new game */
5982       CopyBoard(initialPosition, filePosition);
5983       for(i=0; i<nrCastlingRights; i++)
5984           initialRights[i] = filePosition[CASTLING][i];
5985       startedFromSetupPosition = TRUE;
5986     }
5987
5988     CopyBoard(boards[0], initialPosition);
5989
5990     if(oldx != gameInfo.boardWidth ||
5991        oldy != gameInfo.boardHeight ||
5992        oldv != gameInfo.variant ||
5993        oldh != gameInfo.holdingsWidth
5994                                          )
5995             InitDrawingSizes(-2 ,0);
5996
5997     oldv = gameInfo.variant;
5998     if (redraw)
5999       DrawPosition(TRUE, boards[currentMove]);
6000 }
6001
6002 void
6003 SendBoard(cps, moveNum)
6004      ChessProgramState *cps;
6005      int moveNum;
6006 {
6007     char message[MSG_SIZ];
6008
6009     if (cps->useSetboard) {
6010       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6011       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6012       SendToProgram(message, cps);
6013       free(fen);
6014
6015     } else {
6016       ChessSquare *bp;
6017       int i, j;
6018       /* Kludge to set black to move, avoiding the troublesome and now
6019        * deprecated "black" command.
6020        */
6021       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6022         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6023
6024       SendToProgram("edit\n", cps);
6025       SendToProgram("#\n", cps);
6026       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6027         bp = &boards[moveNum][i][BOARD_LEFT];
6028         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6029           if ((int) *bp < (int) BlackPawn) {
6030             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6031                     AAA + j, ONE + i);
6032             if(message[0] == '+' || message[0] == '~') {
6033               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6034                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6035                         AAA + j, ONE + i);
6036             }
6037             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6038                 message[1] = BOARD_RGHT   - 1 - j + '1';
6039                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6040             }
6041             SendToProgram(message, cps);
6042           }
6043         }
6044       }
6045
6046       SendToProgram("c\n", cps);
6047       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6048         bp = &boards[moveNum][i][BOARD_LEFT];
6049         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6050           if (((int) *bp != (int) EmptySquare)
6051               && ((int) *bp >= (int) BlackPawn)) {
6052             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6053                     AAA + j, ONE + i);
6054             if(message[0] == '+' || message[0] == '~') {
6055               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6056                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6057                         AAA + j, ONE + i);
6058             }
6059             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6060                 message[1] = BOARD_RGHT   - 1 - j + '1';
6061                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6062             }
6063             SendToProgram(message, cps);
6064           }
6065         }
6066       }
6067
6068       SendToProgram(".\n", cps);
6069     }
6070     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6071 }
6072
6073 ChessSquare
6074 DefaultPromoChoice(int white)
6075 {
6076     ChessSquare result;
6077     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6078         result = WhiteFerz; // no choice
6079     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6080         result= WhiteKing; // in Suicide Q is the last thing we want
6081     else if(gameInfo.variant == VariantSpartan)
6082         result = white ? WhiteQueen : WhiteAngel;
6083     else result = WhiteQueen;
6084     if(!white) result = WHITE_TO_BLACK result;
6085     return result;
6086 }
6087
6088 static int autoQueen; // [HGM] oneclick
6089
6090 int
6091 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6092 {
6093     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6094     /* [HGM] add Shogi promotions */
6095     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6096     ChessSquare piece;
6097     ChessMove moveType;
6098     Boolean premove;
6099
6100     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6101     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6102
6103     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6104       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6105         return FALSE;
6106
6107     piece = boards[currentMove][fromY][fromX];
6108     if(gameInfo.variant == VariantShogi) {
6109         promotionZoneSize = BOARD_HEIGHT/3;
6110         highestPromotingPiece = (int)WhiteFerz;
6111     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6112         promotionZoneSize = 3;
6113     }
6114
6115     // Treat Lance as Pawn when it is not representing Amazon
6116     if(gameInfo.variant != VariantSuper) {
6117         if(piece == WhiteLance) piece = WhitePawn; else
6118         if(piece == BlackLance) piece = BlackPawn;
6119     }
6120
6121     // next weed out all moves that do not touch the promotion zone at all
6122     if((int)piece >= BlackPawn) {
6123         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6124              return FALSE;
6125         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6126     } else {
6127         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6128            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6129     }
6130
6131     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6132
6133     // weed out mandatory Shogi promotions
6134     if(gameInfo.variant == VariantShogi) {
6135         if(piece >= BlackPawn) {
6136             if(toY == 0 && piece == BlackPawn ||
6137                toY == 0 && piece == BlackQueen ||
6138                toY <= 1 && piece == BlackKnight) {
6139                 *promoChoice = '+';
6140                 return FALSE;
6141             }
6142         } else {
6143             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6144                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6145                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6146                 *promoChoice = '+';
6147                 return FALSE;
6148             }
6149         }
6150     }
6151
6152     // weed out obviously illegal Pawn moves
6153     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6154         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6155         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6156         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6157         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6158         // note we are not allowed to test for valid (non-)capture, due to premove
6159     }
6160
6161     // we either have a choice what to promote to, or (in Shogi) whether to promote
6162     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6163         *promoChoice = PieceToChar(BlackFerz);  // no choice
6164         return FALSE;
6165     }
6166     // no sense asking what we must promote to if it is going to explode...
6167     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6168         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6169         return FALSE;
6170     }
6171     // give caller the default choice even if we will not make it
6172     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6173     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6174     if(        sweepSelect && gameInfo.variant != VariantGreat
6175                            && gameInfo.variant != VariantGrand
6176                            && gameInfo.variant != VariantSuper) return FALSE;
6177     if(autoQueen) return FALSE; // predetermined
6178
6179     // suppress promotion popup on illegal moves that are not premoves
6180     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6181               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6182     if(appData.testLegality && !premove) {
6183         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6184                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6185         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6186             return FALSE;
6187     }
6188
6189     return TRUE;
6190 }
6191
6192 int
6193 InPalace(row, column)
6194      int row, column;
6195 {   /* [HGM] for Xiangqi */
6196     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6197          column < (BOARD_WIDTH + 4)/2 &&
6198          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6199     return FALSE;
6200 }
6201
6202 int
6203 PieceForSquare (x, y)
6204      int x;
6205      int y;
6206 {
6207   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6208      return -1;
6209   else
6210      return boards[currentMove][y][x];
6211 }
6212
6213 int
6214 OKToStartUserMove(x, y)
6215      int x, y;
6216 {
6217     ChessSquare from_piece;
6218     int white_piece;
6219
6220     if (matchMode) return FALSE;
6221     if (gameMode == EditPosition) return TRUE;
6222
6223     if (x >= 0 && y >= 0)
6224       from_piece = boards[currentMove][y][x];
6225     else
6226       from_piece = EmptySquare;
6227
6228     if (from_piece == EmptySquare) return FALSE;
6229
6230     white_piece = (int)from_piece >= (int)WhitePawn &&
6231       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6232
6233     switch (gameMode) {
6234       case PlayFromGameFile:
6235       case AnalyzeFile:
6236       case TwoMachinesPlay:
6237       case EndOfGame:
6238         return FALSE;
6239
6240       case IcsObserving:
6241       case IcsIdle:
6242         return FALSE;
6243
6244       case MachinePlaysWhite:
6245       case IcsPlayingBlack:
6246         if (appData.zippyPlay) return FALSE;
6247         if (white_piece) {
6248             DisplayMoveError(_("You are playing Black"));
6249             return FALSE;
6250         }
6251         break;
6252
6253       case MachinePlaysBlack:
6254       case IcsPlayingWhite:
6255         if (appData.zippyPlay) return FALSE;
6256         if (!white_piece) {
6257             DisplayMoveError(_("You are playing White"));
6258             return FALSE;
6259         }
6260         break;
6261
6262       case EditGame:
6263         if (!white_piece && WhiteOnMove(currentMove)) {
6264             DisplayMoveError(_("It is White's turn"));
6265             return FALSE;
6266         }
6267         if (white_piece && !WhiteOnMove(currentMove)) {
6268             DisplayMoveError(_("It is Black's turn"));
6269             return FALSE;
6270         }
6271         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6272             /* Editing correspondence game history */
6273             /* Could disallow this or prompt for confirmation */
6274             cmailOldMove = -1;
6275         }
6276         break;
6277
6278       case BeginningOfGame:
6279         if (appData.icsActive) return FALSE;
6280         if (!appData.noChessProgram) {
6281             if (!white_piece) {
6282                 DisplayMoveError(_("You are playing White"));
6283                 return FALSE;
6284             }
6285         }
6286         break;
6287
6288       case Training:
6289         if (!white_piece && WhiteOnMove(currentMove)) {
6290             DisplayMoveError(_("It is White's turn"));
6291             return FALSE;
6292         }
6293         if (white_piece && !WhiteOnMove(currentMove)) {
6294             DisplayMoveError(_("It is Black's turn"));
6295             return FALSE;
6296         }
6297         break;
6298
6299       default:
6300       case IcsExamining:
6301         break;
6302     }
6303     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6304         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6305         && gameMode != AnalyzeFile && gameMode != Training) {
6306         DisplayMoveError(_("Displayed position is not current"));
6307         return FALSE;
6308     }
6309     return TRUE;
6310 }
6311
6312 Boolean
6313 OnlyMove(int *x, int *y, Boolean captures) {
6314     DisambiguateClosure cl;
6315     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6316     switch(gameMode) {
6317       case MachinePlaysBlack:
6318       case IcsPlayingWhite:
6319       case BeginningOfGame:
6320         if(!WhiteOnMove(currentMove)) return FALSE;
6321         break;
6322       case MachinePlaysWhite:
6323       case IcsPlayingBlack:
6324         if(WhiteOnMove(currentMove)) return FALSE;
6325         break;
6326       case EditGame:
6327         break;
6328       default:
6329         return FALSE;
6330     }
6331     cl.pieceIn = EmptySquare;
6332     cl.rfIn = *y;
6333     cl.ffIn = *x;
6334     cl.rtIn = -1;
6335     cl.ftIn = -1;
6336     cl.promoCharIn = NULLCHAR;
6337     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6338     if( cl.kind == NormalMove ||
6339         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6340         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6341         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6342       fromX = cl.ff;
6343       fromY = cl.rf;
6344       *x = cl.ft;
6345       *y = cl.rt;
6346       return TRUE;
6347     }
6348     if(cl.kind != ImpossibleMove) return FALSE;
6349     cl.pieceIn = EmptySquare;
6350     cl.rfIn = -1;
6351     cl.ffIn = -1;
6352     cl.rtIn = *y;
6353     cl.ftIn = *x;
6354     cl.promoCharIn = NULLCHAR;
6355     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6356     if( cl.kind == NormalMove ||
6357         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6358         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6359         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6360       fromX = cl.ff;
6361       fromY = cl.rf;
6362       *x = cl.ft;
6363       *y = cl.rt;
6364       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6365       return TRUE;
6366     }
6367     return FALSE;
6368 }
6369
6370 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6371 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6372 int lastLoadGameUseList = FALSE;
6373 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6374 ChessMove lastLoadGameStart = EndOfFile;
6375
6376 void
6377 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6378      int fromX, fromY, toX, toY;
6379      int promoChar;
6380 {
6381     ChessMove moveType;
6382     ChessSquare pdown, pup;
6383
6384     /* Check if the user is playing in turn.  This is complicated because we
6385        let the user "pick up" a piece before it is his turn.  So the piece he
6386        tried to pick up may have been captured by the time he puts it down!
6387        Therefore we use the color the user is supposed to be playing in this
6388        test, not the color of the piece that is currently on the starting
6389        square---except in EditGame mode, where the user is playing both
6390        sides; fortunately there the capture race can't happen.  (It can
6391        now happen in IcsExamining mode, but that's just too bad.  The user
6392        will get a somewhat confusing message in that case.)
6393        */
6394
6395     switch (gameMode) {
6396       case PlayFromGameFile:
6397       case AnalyzeFile:
6398       case TwoMachinesPlay:
6399       case EndOfGame:
6400       case IcsObserving:
6401       case IcsIdle:
6402         /* We switched into a game mode where moves are not accepted,
6403            perhaps while the mouse button was down. */
6404         return;
6405
6406       case MachinePlaysWhite:
6407         /* User is moving for Black */
6408         if (WhiteOnMove(currentMove)) {
6409             DisplayMoveError(_("It is White's turn"));
6410             return;
6411         }
6412         break;
6413
6414       case MachinePlaysBlack:
6415         /* User is moving for White */
6416         if (!WhiteOnMove(currentMove)) {
6417             DisplayMoveError(_("It is Black's turn"));
6418             return;
6419         }
6420         break;
6421
6422       case EditGame:
6423       case IcsExamining:
6424       case BeginningOfGame:
6425       case AnalyzeMode:
6426       case Training:
6427         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6428         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6429             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6430             /* User is moving for Black */
6431             if (WhiteOnMove(currentMove)) {
6432                 DisplayMoveError(_("It is White's turn"));
6433                 return;
6434             }
6435         } else {
6436             /* User is moving for White */
6437             if (!WhiteOnMove(currentMove)) {
6438                 DisplayMoveError(_("It is Black's turn"));
6439                 return;
6440             }
6441         }
6442         break;
6443
6444       case IcsPlayingBlack:
6445         /* User is moving for Black */
6446         if (WhiteOnMove(currentMove)) {
6447             if (!appData.premove) {
6448                 DisplayMoveError(_("It is White's turn"));
6449             } else if (toX >= 0 && toY >= 0) {
6450                 premoveToX = toX;
6451                 premoveToY = toY;
6452                 premoveFromX = fromX;
6453                 premoveFromY = fromY;
6454                 premovePromoChar = promoChar;
6455                 gotPremove = 1;
6456                 if (appData.debugMode)
6457                     fprintf(debugFP, "Got premove: fromX %d,"
6458                             "fromY %d, toX %d, toY %d\n",
6459                             fromX, fromY, toX, toY);
6460             }
6461             return;
6462         }
6463         break;
6464
6465       case IcsPlayingWhite:
6466         /* User is moving for White */
6467         if (!WhiteOnMove(currentMove)) {
6468             if (!appData.premove) {
6469                 DisplayMoveError(_("It is Black's turn"));
6470             } else if (toX >= 0 && toY >= 0) {
6471                 premoveToX = toX;
6472                 premoveToY = toY;
6473                 premoveFromX = fromX;
6474                 premoveFromY = fromY;
6475                 premovePromoChar = promoChar;
6476                 gotPremove = 1;
6477                 if (appData.debugMode)
6478                     fprintf(debugFP, "Got premove: fromX %d,"
6479                             "fromY %d, toX %d, toY %d\n",
6480                             fromX, fromY, toX, toY);
6481             }
6482             return;
6483         }
6484         break;
6485
6486       default:
6487         break;
6488
6489       case EditPosition:
6490         /* EditPosition, empty square, or different color piece;
6491            click-click move is possible */
6492         if (toX == -2 || toY == -2) {
6493             boards[0][fromY][fromX] = EmptySquare;
6494             DrawPosition(FALSE, boards[currentMove]);
6495             return;
6496         } else if (toX >= 0 && toY >= 0) {
6497             boards[0][toY][toX] = boards[0][fromY][fromX];
6498             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6499                 if(boards[0][fromY][0] != EmptySquare) {
6500                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6501                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6502                 }
6503             } else
6504             if(fromX == BOARD_RGHT+1) {
6505                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6506                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6507                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6508                 }
6509             } else
6510             boards[0][fromY][fromX] = EmptySquare;
6511             DrawPosition(FALSE, boards[currentMove]);
6512             return;
6513         }
6514         return;
6515     }
6516
6517     if(toX < 0 || toY < 0) return;
6518     pdown = boards[currentMove][fromY][fromX];
6519     pup = boards[currentMove][toY][toX];
6520
6521     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6522     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6523          if( pup != EmptySquare ) return;
6524          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6525            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6526                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6527            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6528            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6529            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6530            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6531          fromY = DROP_RANK;
6532     }
6533
6534     /* [HGM] always test for legality, to get promotion info */
6535     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6536                                          fromY, fromX, toY, toX, promoChar);
6537
6538     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6539
6540     /* [HGM] but possibly ignore an IllegalMove result */
6541     if (appData.testLegality) {
6542         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6543             DisplayMoveError(_("Illegal move"));
6544             return;
6545         }
6546     }
6547
6548     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6549 }
6550
6551 /* Common tail of UserMoveEvent and DropMenuEvent */
6552 int
6553 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6554      ChessMove moveType;
6555      int fromX, fromY, toX, toY;
6556      /*char*/int promoChar;
6557 {
6558     char *bookHit = 0;
6559
6560     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6561         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6562         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6563         if(WhiteOnMove(currentMove)) {
6564             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6565         } else {
6566             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6567         }
6568     }
6569
6570     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6571        move type in caller when we know the move is a legal promotion */
6572     if(moveType == NormalMove && promoChar)
6573         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6574
6575     /* [HGM] <popupFix> The following if has been moved here from
6576        UserMoveEvent(). Because it seemed to belong here (why not allow
6577        piece drops in training games?), and because it can only be
6578        performed after it is known to what we promote. */
6579     if (gameMode == Training) {
6580       /* compare the move played on the board to the next move in the
6581        * game. If they match, display the move and the opponent's response.
6582        * If they don't match, display an error message.
6583        */
6584       int saveAnimate;
6585       Board testBoard;
6586       CopyBoard(testBoard, boards[currentMove]);
6587       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6588
6589       if (CompareBoards(testBoard, boards[currentMove+1])) {
6590         ForwardInner(currentMove+1);
6591
6592         /* Autoplay the opponent's response.
6593          * if appData.animate was TRUE when Training mode was entered,
6594          * the response will be animated.
6595          */
6596         saveAnimate = appData.animate;
6597         appData.animate = animateTraining;
6598         ForwardInner(currentMove+1);
6599         appData.animate = saveAnimate;
6600
6601         /* check for the end of the game */
6602         if (currentMove >= forwardMostMove) {
6603           gameMode = PlayFromGameFile;
6604           ModeHighlight();
6605           SetTrainingModeOff();
6606           DisplayInformation(_("End of game"));
6607         }
6608       } else {
6609         DisplayError(_("Incorrect move"), 0);
6610       }
6611       return 1;
6612     }
6613
6614   /* Ok, now we know that the move is good, so we can kill
6615      the previous line in Analysis Mode */
6616   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6617                                 && currentMove < forwardMostMove) {
6618     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6619     else forwardMostMove = currentMove;
6620   }
6621
6622   /* If we need the chess program but it's dead, restart it */
6623   ResurrectChessProgram();
6624
6625   /* A user move restarts a paused game*/
6626   if (pausing)
6627     PauseEvent();
6628
6629   thinkOutput[0] = NULLCHAR;
6630
6631   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6632
6633   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6634     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6635     return 1;
6636   }
6637
6638   if (gameMode == BeginningOfGame) {
6639     if (appData.noChessProgram) {
6640       gameMode = EditGame;
6641       SetGameInfo();
6642     } else {
6643       char buf[MSG_SIZ];
6644       gameMode = MachinePlaysBlack;
6645       StartClocks();
6646       SetGameInfo();
6647       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6648       DisplayTitle(buf);
6649       if (first.sendName) {
6650         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6651         SendToProgram(buf, &first);
6652       }
6653       StartClocks();
6654     }
6655     ModeHighlight();
6656   }
6657
6658   /* Relay move to ICS or chess engine */
6659   if (appData.icsActive) {
6660     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6661         gameMode == IcsExamining) {
6662       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6663         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6664         SendToICS("draw ");
6665         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6666       }
6667       // also send plain move, in case ICS does not understand atomic claims
6668       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6669       ics_user_moved = 1;
6670     }
6671   } else {
6672     if (first.sendTime && (gameMode == BeginningOfGame ||
6673                            gameMode == MachinePlaysWhite ||
6674                            gameMode == MachinePlaysBlack)) {
6675       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6676     }
6677     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6678          // [HGM] book: if program might be playing, let it use book
6679         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6680         first.maybeThinking = TRUE;
6681     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6682         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6683         SendBoard(&first, currentMove+1);
6684     } else SendMoveToProgram(forwardMostMove-1, &first);
6685     if (currentMove == cmailOldMove + 1) {
6686       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6687     }
6688   }
6689
6690   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6691
6692   switch (gameMode) {
6693   case EditGame:
6694     if(appData.testLegality)
6695     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6696     case MT_NONE:
6697     case MT_CHECK:
6698       break;
6699     case MT_CHECKMATE:
6700     case MT_STAINMATE:
6701       if (WhiteOnMove(currentMove)) {
6702         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6703       } else {
6704         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6705       }
6706       break;
6707     case MT_STALEMATE:
6708       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6709       break;
6710     }
6711     break;
6712
6713   case MachinePlaysBlack:
6714   case MachinePlaysWhite:
6715     /* disable certain menu options while machine is thinking */
6716     SetMachineThinkingEnables();
6717     break;
6718
6719   default:
6720     break;
6721   }
6722
6723   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6724   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6725
6726   if(bookHit) { // [HGM] book: simulate book reply
6727         static char bookMove[MSG_SIZ]; // a bit generous?
6728
6729         programStats.nodes = programStats.depth = programStats.time =
6730         programStats.score = programStats.got_only_move = 0;
6731         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6732
6733         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6734         strcat(bookMove, bookHit);
6735         HandleMachineMove(bookMove, &first);
6736   }
6737   return 1;
6738 }
6739
6740 void
6741 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6742      Board board;
6743      int flags;
6744      ChessMove kind;
6745      int rf, ff, rt, ft;
6746      VOIDSTAR closure;
6747 {
6748     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6749     Markers *m = (Markers *) closure;
6750     if(rf == fromY && ff == fromX)
6751         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6752                          || kind == WhiteCapturesEnPassant
6753                          || kind == BlackCapturesEnPassant);
6754     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6755 }
6756
6757 void
6758 MarkTargetSquares(int clear)
6759 {
6760   int x, y;
6761   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6762      !appData.testLegality || gameMode == EditPosition) return;
6763   if(clear) {
6764     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6765   } else {
6766     int capt = 0;
6767     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6768     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6769       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6770       if(capt)
6771       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6772     }
6773   }
6774   DrawPosition(TRUE, NULL);
6775 }
6776
6777 int
6778 Explode(Board board, int fromX, int fromY, int toX, int toY)
6779 {
6780     if(gameInfo.variant == VariantAtomic &&
6781        (board[toY][toX] != EmptySquare ||                     // capture?
6782         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6783                          board[fromY][fromX] == BlackPawn   )
6784       )) {
6785         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6786         return TRUE;
6787     }
6788     return FALSE;
6789 }
6790
6791 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6792
6793 int CanPromote(ChessSquare piece, int y)
6794 {
6795         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6796         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6797         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6798            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6799            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6800                                                   gameInfo.variant == VariantMakruk) return FALSE;
6801         return (piece == BlackPawn && y == 1 ||
6802                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6803                 piece == BlackLance && y == 1 ||
6804                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6805 }
6806
6807 void LeftClick(ClickType clickType, int xPix, int yPix)
6808 {
6809     int x, y;
6810     Boolean saveAnimate;
6811     static int second = 0, promotionChoice = 0, clearFlag = 0;
6812     char promoChoice = NULLCHAR;
6813     ChessSquare piece;
6814
6815     if(appData.seekGraph && appData.icsActive && loggedOn &&
6816         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6817         SeekGraphClick(clickType, xPix, yPix, 0);
6818         return;
6819     }
6820
6821     if (clickType == Press) ErrorPopDown();
6822
6823     x = EventToSquare(xPix, BOARD_WIDTH);
6824     y = EventToSquare(yPix, BOARD_HEIGHT);
6825     if (!flipView && y >= 0) {
6826         y = BOARD_HEIGHT - 1 - y;
6827     }
6828     if (flipView && x >= 0) {
6829         x = BOARD_WIDTH - 1 - x;
6830     }
6831
6832     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6833         defaultPromoChoice = promoSweep;
6834         promoSweep = EmptySquare;   // terminate sweep
6835         promoDefaultAltered = TRUE;
6836         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6837     }
6838
6839     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6840         if(clickType == Release) return; // ignore upclick of click-click destination
6841         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6842         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6843         if(gameInfo.holdingsWidth &&
6844                 (WhiteOnMove(currentMove)
6845                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6846                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6847             // click in right holdings, for determining promotion piece
6848             ChessSquare p = boards[currentMove][y][x];
6849             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6850             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6851             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6852                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6853                 fromX = fromY = -1;
6854                 return;
6855             }
6856         }
6857         DrawPosition(FALSE, boards[currentMove]);
6858         return;
6859     }
6860
6861     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6862     if(clickType == Press
6863             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6864               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6865               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6866         return;
6867
6868     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6869         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6870
6871     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6872         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6873                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6874         defaultPromoChoice = DefaultPromoChoice(side);
6875     }
6876
6877     autoQueen = appData.alwaysPromoteToQueen;
6878
6879     if (fromX == -1) {
6880       int originalY = y;
6881       gatingPiece = EmptySquare;
6882       if (clickType != Press) {
6883         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6884             DragPieceEnd(xPix, yPix); dragging = 0;
6885             DrawPosition(FALSE, NULL);
6886         }
6887         return;
6888       }
6889       fromX = x; fromY = y; toX = toY = -1;
6890       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6891          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6892          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6893             /* First square */
6894             if (OKToStartUserMove(fromX, fromY)) {
6895                 second = 0;
6896                 MarkTargetSquares(0);
6897                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6898                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6899                     promoSweep = defaultPromoChoice;
6900                     selectFlag = 0; lastX = xPix; lastY = yPix;
6901                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6902                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6903                 }
6904                 if (appData.highlightDragging) {
6905                     SetHighlights(fromX, fromY, -1, -1);
6906                 }
6907             } else fromX = fromY = -1;
6908             return;
6909         }
6910     }
6911
6912     /* fromX != -1 */
6913     if (clickType == Press && gameMode != EditPosition) {
6914         ChessSquare fromP;
6915         ChessSquare toP;
6916         int frc;
6917
6918         // ignore off-board to clicks
6919         if(y < 0 || x < 0) return;
6920
6921         /* Check if clicking again on the same color piece */
6922         fromP = boards[currentMove][fromY][fromX];
6923         toP = boards[currentMove][y][x];
6924         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6925         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6926              WhitePawn <= toP && toP <= WhiteKing &&
6927              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6928              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6929             (BlackPawn <= fromP && fromP <= BlackKing &&
6930              BlackPawn <= toP && toP <= BlackKing &&
6931              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6932              !(fromP == BlackKing && toP == BlackRook && frc))) {
6933             /* Clicked again on same color piece -- changed his mind */
6934             second = (x == fromX && y == fromY);
6935             promoDefaultAltered = FALSE;
6936             MarkTargetSquares(1);
6937            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6938             if (appData.highlightDragging) {
6939                 SetHighlights(x, y, -1, -1);
6940             } else {
6941                 ClearHighlights();
6942             }
6943             if (OKToStartUserMove(x, y)) {
6944                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6945                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6946                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6947                  gatingPiece = boards[currentMove][fromY][fromX];
6948                 else gatingPiece = EmptySquare;
6949                 fromX = x;
6950                 fromY = y; dragging = 1;
6951                 MarkTargetSquares(0);
6952                 DragPieceBegin(xPix, yPix, FALSE);
6953                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6954                     promoSweep = defaultPromoChoice;
6955                     selectFlag = 0; lastX = xPix; lastY = yPix;
6956                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6957                 }
6958             }
6959            }
6960            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6961            second = FALSE; 
6962         }
6963         // ignore clicks on holdings
6964         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6965     }
6966
6967     if (clickType == Release && x == fromX && y == fromY) {
6968         DragPieceEnd(xPix, yPix); dragging = 0;
6969         if(clearFlag) {
6970             // a deferred attempt to click-click move an empty square on top of a piece
6971             boards[currentMove][y][x] = EmptySquare;
6972             ClearHighlights();
6973             DrawPosition(FALSE, boards[currentMove]);
6974             fromX = fromY = -1; clearFlag = 0;
6975             return;
6976         }
6977         if (appData.animateDragging) {
6978             /* Undo animation damage if any */
6979             DrawPosition(FALSE, NULL);
6980         }
6981         if (second) {
6982             /* Second up/down in same square; just abort move */
6983             second = 0;
6984             fromX = fromY = -1;
6985             gatingPiece = EmptySquare;
6986             ClearHighlights();
6987             gotPremove = 0;
6988             ClearPremoveHighlights();
6989         } else {
6990             /* First upclick in same square; start click-click mode */
6991             SetHighlights(x, y, -1, -1);
6992         }
6993         return;
6994     }
6995
6996     clearFlag = 0;
6997
6998     /* we now have a different from- and (possibly off-board) to-square */
6999     /* Completed move */
7000     toX = x;
7001     toY = y;
7002     saveAnimate = appData.animate;
7003     MarkTargetSquares(1);
7004     if (clickType == Press) {
7005         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7006             // must be Edit Position mode with empty-square selected
7007             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7008             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7009             return;
7010         }
7011         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7012             ChessSquare piece = boards[currentMove][fromY][fromX];
7013             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7014             promoSweep = defaultPromoChoice;
7015             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7016             selectFlag = 0; lastX = xPix; lastY = yPix;
7017             Sweep(0); // Pawn that is going to promote: preview promotion piece
7018             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7019             DrawPosition(FALSE, boards[currentMove]);
7020             return;
7021         }
7022         /* Finish clickclick move */
7023         if (appData.animate || appData.highlightLastMove) {
7024             SetHighlights(fromX, fromY, toX, toY);
7025         } else {
7026             ClearHighlights();
7027         }
7028     } else {
7029         /* Finish drag move */
7030         if (appData.highlightLastMove) {
7031             SetHighlights(fromX, fromY, toX, toY);
7032         } else {
7033             ClearHighlights();
7034         }
7035         DragPieceEnd(xPix, yPix); dragging = 0;
7036         /* Don't animate move and drag both */
7037         appData.animate = FALSE;
7038     }
7039
7040     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7041     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7042         ChessSquare piece = boards[currentMove][fromY][fromX];
7043         if(gameMode == EditPosition && piece != EmptySquare &&
7044            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7045             int n;
7046
7047             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7048                 n = PieceToNumber(piece - (int)BlackPawn);
7049                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7050                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7051                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7052             } else
7053             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7054                 n = PieceToNumber(piece);
7055                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7056                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7057                 boards[currentMove][n][BOARD_WIDTH-2]++;
7058             }
7059             boards[currentMove][fromY][fromX] = EmptySquare;
7060         }
7061         ClearHighlights();
7062         fromX = fromY = -1;
7063         DrawPosition(TRUE, boards[currentMove]);
7064         return;
7065     }
7066
7067     // off-board moves should not be highlighted
7068     if(x < 0 || y < 0) ClearHighlights();
7069
7070     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7071
7072     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7073         SetHighlights(fromX, fromY, toX, toY);
7074         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7075             // [HGM] super: promotion to captured piece selected from holdings
7076             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7077             promotionChoice = TRUE;
7078             // kludge follows to temporarily execute move on display, without promoting yet
7079             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7080             boards[currentMove][toY][toX] = p;
7081             DrawPosition(FALSE, boards[currentMove]);
7082             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7083             boards[currentMove][toY][toX] = q;
7084             DisplayMessage("Click in holdings to choose piece", "");
7085             return;
7086         }
7087         PromotionPopUp();
7088     } else {
7089         int oldMove = currentMove;
7090         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7091         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7092         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7093         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7094            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7095             DrawPosition(TRUE, boards[currentMove]);
7096         fromX = fromY = -1;
7097     }
7098     appData.animate = saveAnimate;
7099     if (appData.animate || appData.animateDragging) {
7100         /* Undo animation damage if needed */
7101         DrawPosition(FALSE, NULL);
7102     }
7103 }
7104
7105 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7106 {   // front-end-free part taken out of PieceMenuPopup
7107     int whichMenu; int xSqr, ySqr;
7108
7109     if(seekGraphUp) { // [HGM] seekgraph
7110         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7111         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7112         return -2;
7113     }
7114
7115     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7116          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7117         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7118         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7119         if(action == Press)   {
7120             originalFlip = flipView;
7121             flipView = !flipView; // temporarily flip board to see game from partners perspective
7122             DrawPosition(TRUE, partnerBoard);
7123             DisplayMessage(partnerStatus, "");
7124             partnerUp = TRUE;
7125         } else if(action == Release) {
7126             flipView = originalFlip;
7127             DrawPosition(TRUE, boards[currentMove]);
7128             partnerUp = FALSE;
7129         }
7130         return -2;
7131     }
7132
7133     xSqr = EventToSquare(x, BOARD_WIDTH);
7134     ySqr = EventToSquare(y, BOARD_HEIGHT);
7135     if (action == Release) {
7136         if(pieceSweep != EmptySquare) {
7137             EditPositionMenuEvent(pieceSweep, toX, toY);
7138             pieceSweep = EmptySquare;
7139         } else UnLoadPV(); // [HGM] pv
7140     }
7141     if (action != Press) return -2; // return code to be ignored
7142     switch (gameMode) {
7143       case IcsExamining:
7144         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7145       case EditPosition:
7146         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7147         if (xSqr < 0 || ySqr < 0) return -1;
7148         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7149         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7150         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7151         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7152         NextPiece(0);
7153         return 2; // grab
7154       case IcsObserving:
7155         if(!appData.icsEngineAnalyze) return -1;
7156       case IcsPlayingWhite:
7157       case IcsPlayingBlack:
7158         if(!appData.zippyPlay) goto noZip;
7159       case AnalyzeMode:
7160       case AnalyzeFile:
7161       case MachinePlaysWhite:
7162       case MachinePlaysBlack:
7163       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7164         if (!appData.dropMenu) {
7165           LoadPV(x, y);
7166           return 2; // flag front-end to grab mouse events
7167         }
7168         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7169            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7170       case EditGame:
7171       noZip:
7172         if (xSqr < 0 || ySqr < 0) return -1;
7173         if (!appData.dropMenu || appData.testLegality &&
7174             gameInfo.variant != VariantBughouse &&
7175             gameInfo.variant != VariantCrazyhouse) return -1;
7176         whichMenu = 1; // drop menu
7177         break;
7178       default:
7179         return -1;
7180     }
7181
7182     if (((*fromX = xSqr) < 0) ||
7183         ((*fromY = ySqr) < 0)) {
7184         *fromX = *fromY = -1;
7185         return -1;
7186     }
7187     if (flipView)
7188       *fromX = BOARD_WIDTH - 1 - *fromX;
7189     else
7190       *fromY = BOARD_HEIGHT - 1 - *fromY;
7191
7192     return whichMenu;
7193 }
7194
7195 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7196 {
7197 //    char * hint = lastHint;
7198     FrontEndProgramStats stats;
7199
7200     stats.which = cps == &first ? 0 : 1;
7201     stats.depth = cpstats->depth;
7202     stats.nodes = cpstats->nodes;
7203     stats.score = cpstats->score;
7204     stats.time = cpstats->time;
7205     stats.pv = cpstats->movelist;
7206     stats.hint = lastHint;
7207     stats.an_move_index = 0;
7208     stats.an_move_count = 0;
7209
7210     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7211         stats.hint = cpstats->move_name;
7212         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7213         stats.an_move_count = cpstats->nr_moves;
7214     }
7215
7216     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
7217
7218     SetProgramStats( &stats );
7219 }
7220
7221 void
7222 ClearEngineOutputPane(int which)
7223 {
7224     static FrontEndProgramStats dummyStats;
7225     dummyStats.which = which;
7226     dummyStats.pv = "#";
7227     SetProgramStats( &dummyStats );
7228 }
7229
7230 #define MAXPLAYERS 500
7231
7232 char *
7233 TourneyStandings(int display)
7234 {
7235     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7236     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7237     char result, *p, *names[MAXPLAYERS];
7238
7239     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7240         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7241     names[0] = p = strdup(appData.participants);
7242     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7243
7244     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7245
7246     while(result = appData.results[nr]) {
7247         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7248         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7249         wScore = bScore = 0;
7250         switch(result) {
7251           case '+': wScore = 2; break;
7252           case '-': bScore = 2; break;
7253           case '=': wScore = bScore = 1; break;
7254           case ' ':
7255           case '*': return strdup("busy"); // tourney not finished
7256         }
7257         score[w] += wScore;
7258         score[b] += bScore;
7259         games[w]++;
7260         games[b]++;
7261         nr++;
7262     }
7263     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7264     for(w=0; w<nPlayers; w++) {
7265         bScore = -1;
7266         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7267         ranking[w] = b; points[w] = bScore; score[b] = -2;
7268     }
7269     p = malloc(nPlayers*34+1);
7270     for(w=0; w<nPlayers && w<display; w++)
7271         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7272     free(names[0]);
7273     return p;
7274 }
7275
7276 void
7277 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7278 {       // count all piece types
7279         int p, f, r;
7280         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7281         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7282         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7283                 p = board[r][f];
7284                 pCnt[p]++;
7285                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7286                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7287                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7288                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7289                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7290                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7291         }
7292 }
7293
7294 int
7295 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7296 {
7297         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7298         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7299
7300         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7301         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7302         if(myPawns == 2 && nMine == 3) // KPP
7303             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7304         if(myPawns == 1 && nMine == 2) // KP
7305             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7306         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7307             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7308         if(myPawns) return FALSE;
7309         if(pCnt[WhiteRook+side])
7310             return pCnt[BlackRook-side] ||
7311                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7312                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7313                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7314         if(pCnt[WhiteCannon+side]) {
7315             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7316             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7317         }
7318         if(pCnt[WhiteKnight+side])
7319             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7320         return FALSE;
7321 }
7322
7323 int
7324 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7325 {
7326         VariantClass v = gameInfo.variant;
7327
7328         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7329         if(v == VariantShatranj) return TRUE; // always winnable through baring
7330         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7331         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7332
7333         if(v == VariantXiangqi) {
7334                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7335
7336                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7337                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7338                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7339                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7340                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7341                 if(stale) // we have at least one last-rank P plus perhaps C
7342                     return majors // KPKX
7343                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7344                 else // KCA*E*
7345                     return pCnt[WhiteFerz+side] // KCAK
7346                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7347                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7348                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7349
7350         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7351                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7352
7353                 if(nMine == 1) return FALSE; // bare King
7354                 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
7355                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7356                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7357                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7358                 if(pCnt[WhiteKnight+side])
7359                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7360                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7361                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7362                 if(nBishops)
7363                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7364                 if(pCnt[WhiteAlfil+side])
7365                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7366                 if(pCnt[WhiteWazir+side])
7367                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7368         }
7369
7370         return TRUE;
7371 }
7372
7373 int
7374 Adjudicate(ChessProgramState *cps)
7375 {       // [HGM] some adjudications useful with buggy engines
7376         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7377         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7378         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7379         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7380         int k, count = 0; static int bare = 1;
7381         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7382         Boolean canAdjudicate = !appData.icsActive;
7383
7384         // most tests only when we understand the game, i.e. legality-checking on
7385             if( appData.testLegality )
7386             {   /* [HGM] Some more adjudications for obstinate engines */
7387                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7388                 static int moveCount = 6;
7389                 ChessMove result;
7390                 char *reason = NULL;
7391
7392                 /* Count what is on board. */
7393                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7394
7395                 /* Some material-based adjudications that have to be made before stalemate test */
7396                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7397                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7398                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7399                      if(canAdjudicate && appData.checkMates) {
7400                          if(engineOpponent)
7401                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7402                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7403                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7404                          return 1;
7405                      }
7406                 }
7407
7408                 /* Bare King in Shatranj (loses) or Losers (wins) */
7409                 if( nrW == 1 || nrB == 1) {
7410                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7411                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7412                      if(canAdjudicate && appData.checkMates) {
7413                          if(engineOpponent)
7414                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7415                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7416                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7417                          return 1;
7418                      }
7419                   } else
7420                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7421                   {    /* bare King */
7422                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7423                         if(canAdjudicate && appData.checkMates) {
7424                             /* but only adjudicate if adjudication enabled */
7425                             if(engineOpponent)
7426                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7427                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7428                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7429                             return 1;
7430                         }
7431                   }
7432                 } else bare = 1;
7433
7434
7435             // don't wait for engine to announce game end if we can judge ourselves
7436             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7437               case MT_CHECK:
7438                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7439                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7440                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7441                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7442                             checkCnt++;
7443                         if(checkCnt >= 2) {
7444                             reason = "Xboard adjudication: 3rd check";
7445                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7446                             break;
7447                         }
7448                     }
7449                 }
7450               case MT_NONE:
7451               default:
7452                 break;
7453               case MT_STALEMATE:
7454               case MT_STAINMATE:
7455                 reason = "Xboard adjudication: Stalemate";
7456                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7457                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7458                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7459                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7460                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7461                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7462                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7463                                                                         EP_CHECKMATE : EP_WINS);
7464                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7465                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7466                 }
7467                 break;
7468               case MT_CHECKMATE:
7469                 reason = "Xboard adjudication: Checkmate";
7470                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7471                 break;
7472             }
7473
7474                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7475                     case EP_STALEMATE:
7476                         result = GameIsDrawn; break;
7477                     case EP_CHECKMATE:
7478                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7479                     case EP_WINS:
7480                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7481                     default:
7482                         result = EndOfFile;
7483                 }
7484                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7485                     if(engineOpponent)
7486                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7487                     GameEnds( result, reason, GE_XBOARD );
7488                     return 1;
7489                 }
7490
7491                 /* Next absolutely insufficient mating material. */
7492                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7493                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7494                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7495
7496                      /* always flag draws, for judging claims */
7497                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7498
7499                      if(canAdjudicate && appData.materialDraws) {
7500                          /* but only adjudicate them if adjudication enabled */
7501                          if(engineOpponent) {
7502                            SendToProgram("force\n", engineOpponent); // suppress reply
7503                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7504                          }
7505                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7506                          return 1;
7507                      }
7508                 }
7509
7510                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7511                 if(gameInfo.variant == VariantXiangqi ?
7512                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7513                  : nrW + nrB == 4 &&
7514                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7515                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7516                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7517                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7518                    ) ) {
7519                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7520                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7521                           if(engineOpponent) {
7522                             SendToProgram("force\n", engineOpponent); // suppress reply
7523                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7524                           }
7525                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7526                           return 1;
7527                      }
7528                 } else moveCount = 6;
7529             }
7530         if (appData.debugMode) { int i;
7531             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7532                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7533                     appData.drawRepeats);
7534             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7535               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7536
7537         }
7538
7539         // Repetition draws and 50-move rule can be applied independently of legality testing
7540
7541                 /* Check for rep-draws */
7542                 count = 0;
7543                 for(k = forwardMostMove-2;
7544                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7545                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7546                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7547                     k-=2)
7548                 {   int rights=0;
7549                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7550                         /* compare castling rights */
7551                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7552                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7553                                 rights++; /* King lost rights, while rook still had them */
7554                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7555                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7556                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7557                                    rights++; /* but at least one rook lost them */
7558                         }
7559                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7560                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7561                                 rights++;
7562                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7563                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7564                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7565                                    rights++;
7566                         }
7567                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7568                             && appData.drawRepeats > 1) {
7569                              /* adjudicate after user-specified nr of repeats */
7570                              int result = GameIsDrawn;
7571                              char *details = "XBoard adjudication: repetition draw";
7572                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7573                                 // [HGM] xiangqi: check for forbidden perpetuals
7574                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7575                                 for(m=forwardMostMove; m>k; m-=2) {
7576                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7577                                         ourPerpetual = 0; // the current mover did not always check
7578                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7579                                         hisPerpetual = 0; // the opponent did not always check
7580                                 }
7581                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7582                                                                         ourPerpetual, hisPerpetual);
7583                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7584                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7585                                     details = "Xboard adjudication: perpetual checking";
7586                                 } else
7587                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7588                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7589                                 } else
7590                                 // Now check for perpetual chases
7591                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7592                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7593                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7594                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7595                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7596                                         details = "Xboard adjudication: perpetual chasing";
7597                                     } else
7598                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7599                                         break; // Abort repetition-checking loop.
7600                                 }
7601                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7602                              }
7603                              if(engineOpponent) {
7604                                SendToProgram("force\n", engineOpponent); // suppress reply
7605                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7606                              }
7607                              GameEnds( result, details, GE_XBOARD );
7608                              return 1;
7609                         }
7610                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7611                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7612                     }
7613                 }
7614
7615                 /* Now we test for 50-move draws. Determine ply count */
7616                 count = forwardMostMove;
7617                 /* look for last irreversble move */
7618                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7619                     count--;
7620                 /* if we hit starting position, add initial plies */
7621                 if( count == backwardMostMove )
7622                     count -= initialRulePlies;
7623                 count = forwardMostMove - count;
7624                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7625                         // adjust reversible move counter for checks in Xiangqi
7626                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7627                         if(i < backwardMostMove) i = backwardMostMove;
7628                         while(i <= forwardMostMove) {
7629                                 lastCheck = inCheck; // check evasion does not count
7630                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7631                                 if(inCheck || lastCheck) count--; // check does not count
7632                                 i++;
7633                         }
7634                 }
7635                 if( count >= 100)
7636                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7637                          /* this is used to judge if draw claims are legal */
7638                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7639                          if(engineOpponent) {
7640                            SendToProgram("force\n", engineOpponent); // suppress reply
7641                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7642                          }
7643                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7644                          return 1;
7645                 }
7646
7647                 /* if draw offer is pending, treat it as a draw claim
7648                  * when draw condition present, to allow engines a way to
7649                  * claim draws before making their move to avoid a race
7650                  * condition occurring after their move
7651                  */
7652                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7653                          char *p = NULL;
7654                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7655                              p = "Draw claim: 50-move rule";
7656                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7657                              p = "Draw claim: 3-fold repetition";
7658                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7659                              p = "Draw claim: insufficient mating material";
7660                          if( p != NULL && canAdjudicate) {
7661                              if(engineOpponent) {
7662                                SendToProgram("force\n", engineOpponent); // suppress reply
7663                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7664                              }
7665                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7666                              return 1;
7667                          }
7668                 }
7669
7670                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7671                     if(engineOpponent) {
7672                       SendToProgram("force\n", engineOpponent); // suppress reply
7673                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7674                     }
7675                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7676                     return 1;
7677                 }
7678         return 0;
7679 }
7680
7681 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7682 {   // [HGM] book: this routine intercepts moves to simulate book replies
7683     char *bookHit = NULL;
7684
7685     //first determine if the incoming move brings opponent into his book
7686     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7687         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7688     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7689     if(bookHit != NULL && !cps->bookSuspend) {
7690         // make sure opponent is not going to reply after receiving move to book position
7691         SendToProgram("force\n", cps);
7692         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7693     }
7694     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7695     // now arrange restart after book miss
7696     if(bookHit) {
7697         // after a book hit we never send 'go', and the code after the call to this routine
7698         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7699         char buf[MSG_SIZ], *move = bookHit;
7700         if(cps->useSAN) {
7701             int fromX, fromY, toX, toY;
7702             char promoChar;
7703             ChessMove moveType;
7704             move = buf + 30;
7705             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7706                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7707                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7708                                     PosFlags(forwardMostMove),
7709                                     fromY, fromX, toY, toX, promoChar, move);
7710             } else {
7711                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7712                 bookHit = NULL;
7713             }
7714         }
7715         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7716         SendToProgram(buf, cps);
7717         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7718     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7719         SendToProgram("go\n", cps);
7720         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7721     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7722         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7723             SendToProgram("go\n", cps);
7724         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7725     }
7726     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7727 }
7728
7729 char *savedMessage;
7730 ChessProgramState *savedState;
7731 void DeferredBookMove(void)
7732 {
7733         if(savedState->lastPing != savedState->lastPong)
7734                     ScheduleDelayedEvent(DeferredBookMove, 10);
7735         else
7736         HandleMachineMove(savedMessage, savedState);
7737 }
7738
7739 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7740
7741 void
7742 HandleMachineMove(message, cps)
7743      char *message;
7744      ChessProgramState *cps;
7745 {
7746     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7747     char realname[MSG_SIZ];
7748     int fromX, fromY, toX, toY;
7749     ChessMove moveType;
7750     char promoChar;
7751     char *p, *pv=buf1;
7752     int machineWhite;
7753     char *bookHit;
7754
7755     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7756         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7757         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7758             DisplayError(_("Invalid pairing from pairing engine"), 0);
7759             return;
7760         }
7761         pairingReceived = 1;
7762         NextMatchGame();
7763         return; // Skim the pairing messages here.
7764     }
7765
7766     cps->userError = 0;
7767
7768 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7769     /*
7770      * Kludge to ignore BEL characters
7771      */
7772     while (*message == '\007') message++;
7773
7774     /*
7775      * [HGM] engine debug message: ignore lines starting with '#' character
7776      */
7777     if(cps->debug && *message == '#') return;
7778
7779     /*
7780      * Look for book output
7781      */
7782     if (cps == &first && bookRequested) {
7783         if (message[0] == '\t' || message[0] == ' ') {
7784             /* Part of the book output is here; append it */
7785             strcat(bookOutput, message);
7786             strcat(bookOutput, "  \n");
7787             return;
7788         } else if (bookOutput[0] != NULLCHAR) {
7789             /* All of book output has arrived; display it */
7790             char *p = bookOutput;
7791             while (*p != NULLCHAR) {
7792                 if (*p == '\t') *p = ' ';
7793                 p++;
7794             }
7795             DisplayInformation(bookOutput);
7796             bookRequested = FALSE;
7797             /* Fall through to parse the current output */
7798         }
7799     }
7800
7801     /*
7802      * Look for machine move.
7803      */
7804     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7805         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7806     {
7807         /* This method is only useful on engines that support ping */
7808         if (cps->lastPing != cps->lastPong) {
7809           if (gameMode == BeginningOfGame) {
7810             /* Extra move from before last new; ignore */
7811             if (appData.debugMode) {
7812                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7813             }
7814           } else {
7815             if (appData.debugMode) {
7816                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7817                         cps->which, gameMode);
7818             }
7819
7820             SendToProgram("undo\n", cps);
7821           }
7822           return;
7823         }
7824
7825         switch (gameMode) {
7826           case BeginningOfGame:
7827             /* Extra move from before last reset; ignore */
7828             if (appData.debugMode) {
7829                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7830             }
7831             return;
7832
7833           case EndOfGame:
7834           case IcsIdle:
7835           default:
7836             /* Extra move after we tried to stop.  The mode test is
7837                not a reliable way of detecting this problem, but it's
7838                the best we can do on engines that don't support ping.
7839             */
7840             if (appData.debugMode) {
7841                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7842                         cps->which, gameMode);
7843             }
7844             SendToProgram("undo\n", cps);
7845             return;
7846
7847           case MachinePlaysWhite:
7848           case IcsPlayingWhite:
7849             machineWhite = TRUE;
7850             break;
7851
7852           case MachinePlaysBlack:
7853           case IcsPlayingBlack:
7854             machineWhite = FALSE;
7855             break;
7856
7857           case TwoMachinesPlay:
7858             machineWhite = (cps->twoMachinesColor[0] == 'w');
7859             break;
7860         }
7861         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7862             if (appData.debugMode) {
7863                 fprintf(debugFP,
7864                         "Ignoring move out of turn by %s, gameMode %d"
7865                         ", forwardMost %d\n",
7866                         cps->which, gameMode, forwardMostMove);
7867             }
7868             return;
7869         }
7870
7871     if (appData.debugMode) { int f = forwardMostMove;
7872         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7873                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7874                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7875     }
7876         if(cps->alphaRank) AlphaRank(machineMove, 4);
7877         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7878                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7879             /* Machine move could not be parsed; ignore it. */
7880           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7881                     machineMove, _(cps->which));
7882             DisplayError(buf1, 0);
7883             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7884                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7885             if (gameMode == TwoMachinesPlay) {
7886               GameEnds(machineWhite ? BlackWins : WhiteWins,
7887                        buf1, GE_XBOARD);
7888             }
7889             return;
7890         }
7891
7892         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7893         /* So we have to redo legality test with true e.p. status here,  */
7894         /* to make sure an illegal e.p. capture does not slip through,   */
7895         /* to cause a forfeit on a justified illegal-move complaint      */
7896         /* of the opponent.                                              */
7897         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7898            ChessMove moveType;
7899            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7900                              fromY, fromX, toY, toX, promoChar);
7901             if (appData.debugMode) {
7902                 int i;
7903                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7904                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7905                 fprintf(debugFP, "castling rights\n");
7906             }
7907             if(moveType == IllegalMove) {
7908               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7909                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7910                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7911                            buf1, GE_XBOARD);
7912                 return;
7913            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7914            /* [HGM] Kludge to handle engines that send FRC-style castling
7915               when they shouldn't (like TSCP-Gothic) */
7916            switch(moveType) {
7917              case WhiteASideCastleFR:
7918              case BlackASideCastleFR:
7919                toX+=2;
7920                currentMoveString[2]++;
7921                break;
7922              case WhiteHSideCastleFR:
7923              case BlackHSideCastleFR:
7924                toX--;
7925                currentMoveString[2]--;
7926                break;
7927              default: ; // nothing to do, but suppresses warning of pedantic compilers
7928            }
7929         }
7930         hintRequested = FALSE;
7931         lastHint[0] = NULLCHAR;
7932         bookRequested = FALSE;
7933         /* Program may be pondering now */
7934         cps->maybeThinking = TRUE;
7935         if (cps->sendTime == 2) cps->sendTime = 1;
7936         if (cps->offeredDraw) cps->offeredDraw--;
7937
7938         /* [AS] Save move info*/
7939         pvInfoList[ forwardMostMove ].score = programStats.score;
7940         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7941         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7942
7943         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7944
7945         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7946         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7947             int count = 0;
7948
7949             while( count < adjudicateLossPlies ) {
7950                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7951
7952                 if( count & 1 ) {
7953                     score = -score; /* Flip score for winning side */
7954                 }
7955
7956                 if( score > adjudicateLossThreshold ) {
7957                     break;
7958                 }
7959
7960                 count++;
7961             }
7962
7963             if( count >= adjudicateLossPlies ) {
7964                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7965
7966                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7967                     "Xboard adjudication",
7968                     GE_XBOARD );
7969
7970                 return;
7971             }
7972         }
7973
7974         if(Adjudicate(cps)) {
7975             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7976             return; // [HGM] adjudicate: for all automatic game ends
7977         }
7978
7979 #if ZIPPY
7980         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7981             first.initDone) {
7982           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7983                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7984                 SendToICS("draw ");
7985                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7986           }
7987           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7988           ics_user_moved = 1;
7989           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7990                 char buf[3*MSG_SIZ];
7991
7992                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7993                         programStats.score / 100.,
7994                         programStats.depth,
7995                         programStats.time / 100.,
7996                         (unsigned int)programStats.nodes,
7997                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7998                         programStats.movelist);
7999                 SendToICS(buf);
8000 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8001           }
8002         }
8003 #endif
8004
8005         /* [AS] Clear stats for next move */
8006         ClearProgramStats();
8007         thinkOutput[0] = NULLCHAR;
8008         hiddenThinkOutputState = 0;
8009
8010         bookHit = NULL;
8011         if (gameMode == TwoMachinesPlay) {
8012             /* [HGM] relaying draw offers moved to after reception of move */
8013             /* and interpreting offer as claim if it brings draw condition */
8014             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8015                 SendToProgram("draw\n", cps->other);
8016             }
8017             if (cps->other->sendTime) {
8018                 SendTimeRemaining(cps->other,
8019                                   cps->other->twoMachinesColor[0] == 'w');
8020             }
8021             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8022             if (firstMove && !bookHit) {
8023                 firstMove = FALSE;
8024                 if (cps->other->useColors) {
8025                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8026                 }
8027                 SendToProgram("go\n", cps->other);
8028             }
8029             cps->other->maybeThinking = TRUE;
8030         }
8031
8032         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8033
8034         if (!pausing && appData.ringBellAfterMoves) {
8035             RingBell();
8036         }
8037
8038         /*
8039          * Reenable menu items that were disabled while
8040          * machine was thinking
8041          */
8042         if (gameMode != TwoMachinesPlay)
8043             SetUserThinkingEnables();
8044
8045         // [HGM] book: after book hit opponent has received move and is now in force mode
8046         // force the book reply into it, and then fake that it outputted this move by jumping
8047         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8048         if(bookHit) {
8049                 static char bookMove[MSG_SIZ]; // a bit generous?
8050
8051                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8052                 strcat(bookMove, bookHit);
8053                 message = bookMove;
8054                 cps = cps->other;
8055                 programStats.nodes = programStats.depth = programStats.time =
8056                 programStats.score = programStats.got_only_move = 0;
8057                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8058
8059                 if(cps->lastPing != cps->lastPong) {
8060                     savedMessage = message; // args for deferred call
8061                     savedState = cps;
8062                     ScheduleDelayedEvent(DeferredBookMove, 10);
8063                     return;
8064                 }
8065                 goto FakeBookMove;
8066         }
8067
8068         return;
8069     }
8070
8071     /* Set special modes for chess engines.  Later something general
8072      *  could be added here; for now there is just one kludge feature,
8073      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8074      *  when "xboard" is given as an interactive command.
8075      */
8076     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8077         cps->useSigint = FALSE;
8078         cps->useSigterm = FALSE;
8079     }
8080     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8081       ParseFeatures(message+8, cps);
8082       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8083     }
8084
8085     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8086       int dummy, s=6; char buf[MSG_SIZ];
8087       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8088       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8089       ParseFEN(boards[0], &dummy, message+s);
8090       DrawPosition(TRUE, boards[0]);
8091       startedFromSetupPosition = TRUE;
8092       return;
8093     }
8094     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8095      * want this, I was asked to put it in, and obliged.
8096      */
8097     if (!strncmp(message, "setboard ", 9)) {
8098         Board initial_position;
8099
8100         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8101
8102         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8103             DisplayError(_("Bad FEN received from engine"), 0);
8104             return ;
8105         } else {
8106            Reset(TRUE, FALSE);
8107            CopyBoard(boards[0], initial_position);
8108            initialRulePlies = FENrulePlies;
8109            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8110            else gameMode = MachinePlaysBlack;
8111            DrawPosition(FALSE, boards[currentMove]);
8112         }
8113         return;
8114     }
8115
8116     /*
8117      * Look for communication commands
8118      */
8119     if (!strncmp(message, "telluser ", 9)) {
8120         if(message[9] == '\\' && message[10] == '\\')
8121             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8122         PlayTellSound();
8123         DisplayNote(message + 9);
8124         return;
8125     }
8126     if (!strncmp(message, "tellusererror ", 14)) {
8127         cps->userError = 1;
8128         if(message[14] == '\\' && message[15] == '\\')
8129             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8130         PlayTellSound();
8131         DisplayError(message + 14, 0);
8132         return;
8133     }
8134     if (!strncmp(message, "tellopponent ", 13)) {
8135       if (appData.icsActive) {
8136         if (loggedOn) {
8137           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8138           SendToICS(buf1);
8139         }
8140       } else {
8141         DisplayNote(message + 13);
8142       }
8143       return;
8144     }
8145     if (!strncmp(message, "tellothers ", 11)) {
8146       if (appData.icsActive) {
8147         if (loggedOn) {
8148           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8149           SendToICS(buf1);
8150         }
8151       }
8152       return;
8153     }
8154     if (!strncmp(message, "tellall ", 8)) {
8155       if (appData.icsActive) {
8156         if (loggedOn) {
8157           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8158           SendToICS(buf1);
8159         }
8160       } else {
8161         DisplayNote(message + 8);
8162       }
8163       return;
8164     }
8165     if (strncmp(message, "warning", 7) == 0) {
8166         /* Undocumented feature, use tellusererror in new code */
8167         DisplayError(message, 0);
8168         return;
8169     }
8170     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8171         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8172         strcat(realname, " query");
8173         AskQuestion(realname, buf2, buf1, cps->pr);
8174         return;
8175     }
8176     /* Commands from the engine directly to ICS.  We don't allow these to be
8177      *  sent until we are logged on. Crafty kibitzes have been known to
8178      *  interfere with the login process.
8179      */
8180     if (loggedOn) {
8181         if (!strncmp(message, "tellics ", 8)) {
8182             SendToICS(message + 8);
8183             SendToICS("\n");
8184             return;
8185         }
8186         if (!strncmp(message, "tellicsnoalias ", 15)) {
8187             SendToICS(ics_prefix);
8188             SendToICS(message + 15);
8189             SendToICS("\n");
8190             return;
8191         }
8192         /* The following are for backward compatibility only */
8193         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8194             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8195             SendToICS(ics_prefix);
8196             SendToICS(message);
8197             SendToICS("\n");
8198             return;
8199         }
8200     }
8201     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8202         return;
8203     }
8204     /*
8205      * If the move is illegal, cancel it and redraw the board.
8206      * Also deal with other error cases.  Matching is rather loose
8207      * here to accommodate engines written before the spec.
8208      */
8209     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8210         strncmp(message, "Error", 5) == 0) {
8211         if (StrStr(message, "name") ||
8212             StrStr(message, "rating") || StrStr(message, "?") ||
8213             StrStr(message, "result") || StrStr(message, "board") ||
8214             StrStr(message, "bk") || StrStr(message, "computer") ||
8215             StrStr(message, "variant") || StrStr(message, "hint") ||
8216             StrStr(message, "random") || StrStr(message, "depth") ||
8217             StrStr(message, "accepted")) {
8218             return;
8219         }
8220         if (StrStr(message, "protover")) {
8221           /* Program is responding to input, so it's apparently done
8222              initializing, and this error message indicates it is
8223              protocol version 1.  So we don't need to wait any longer
8224              for it to initialize and send feature commands. */
8225           FeatureDone(cps, 1);
8226           cps->protocolVersion = 1;
8227           return;
8228         }
8229         cps->maybeThinking = FALSE;
8230
8231         if (StrStr(message, "draw")) {
8232             /* Program doesn't have "draw" command */
8233             cps->sendDrawOffers = 0;
8234             return;
8235         }
8236         if (cps->sendTime != 1 &&
8237             (StrStr(message, "time") || StrStr(message, "otim"))) {
8238           /* Program apparently doesn't have "time" or "otim" command */
8239           cps->sendTime = 0;
8240           return;
8241         }
8242         if (StrStr(message, "analyze")) {
8243             cps->analysisSupport = FALSE;
8244             cps->analyzing = FALSE;
8245             Reset(FALSE, TRUE);
8246             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8247             DisplayError(buf2, 0);
8248             return;
8249         }
8250         if (StrStr(message, "(no matching move)st")) {
8251           /* Special kludge for GNU Chess 4 only */
8252           cps->stKludge = TRUE;
8253           SendTimeControl(cps, movesPerSession, timeControl,
8254                           timeIncrement, appData.searchDepth,
8255                           searchTime);
8256           return;
8257         }
8258         if (StrStr(message, "(no matching move)sd")) {
8259           /* Special kludge for GNU Chess 4 only */
8260           cps->sdKludge = TRUE;
8261           SendTimeControl(cps, movesPerSession, timeControl,
8262                           timeIncrement, appData.searchDepth,
8263                           searchTime);
8264           return;
8265         }
8266         if (!StrStr(message, "llegal")) {
8267             return;
8268         }
8269         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8270             gameMode == IcsIdle) return;
8271         if (forwardMostMove <= backwardMostMove) return;
8272         if (pausing) PauseEvent();
8273       if(appData.forceIllegal) {
8274             // [HGM] illegal: machine refused move; force position after move into it
8275           SendToProgram("force\n", cps);
8276           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8277                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8278                 // when black is to move, while there might be nothing on a2 or black
8279                 // might already have the move. So send the board as if white has the move.
8280                 // But first we must change the stm of the engine, as it refused the last move
8281                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8282                 if(WhiteOnMove(forwardMostMove)) {
8283                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8284                     SendBoard(cps, forwardMostMove); // kludgeless board
8285                 } else {
8286                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8287                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8288                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8289                 }
8290           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8291             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8292                  gameMode == TwoMachinesPlay)
8293               SendToProgram("go\n", cps);
8294             return;
8295       } else
8296         if (gameMode == PlayFromGameFile) {
8297             /* Stop reading this game file */
8298             gameMode = EditGame;
8299             ModeHighlight();
8300         }
8301         /* [HGM] illegal-move claim should forfeit game when Xboard */
8302         /* only passes fully legal moves                            */
8303         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8304             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8305                                 "False illegal-move claim", GE_XBOARD );
8306             return; // do not take back move we tested as valid
8307         }
8308         currentMove = forwardMostMove-1;
8309         DisplayMove(currentMove-1); /* before DisplayMoveError */
8310         SwitchClocks(forwardMostMove-1); // [HGM] race
8311         DisplayBothClocks();
8312         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8313                 parseList[currentMove], _(cps->which));
8314         DisplayMoveError(buf1);
8315         DrawPosition(FALSE, boards[currentMove]);
8316         return;
8317     }
8318     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8319         /* Program has a broken "time" command that
8320            outputs a string not ending in newline.
8321            Don't use it. */
8322         cps->sendTime = 0;
8323     }
8324
8325     /*
8326      * If chess program startup fails, exit with an error message.
8327      * Attempts to recover here are futile.
8328      */
8329     if ((StrStr(message, "unknown host") != NULL)
8330         || (StrStr(message, "No remote directory") != NULL)
8331         || (StrStr(message, "not found") != NULL)
8332         || (StrStr(message, "No such file") != NULL)
8333         || (StrStr(message, "can't alloc") != NULL)
8334         || (StrStr(message, "Permission denied") != NULL)) {
8335
8336         cps->maybeThinking = FALSE;
8337         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8338                 _(cps->which), cps->program, cps->host, message);
8339         RemoveInputSource(cps->isr);
8340         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8341             if(cps == &first) appData.noChessProgram = TRUE;
8342             DisplayError(buf1, 0);
8343         }
8344         return;
8345     }
8346
8347     /*
8348      * Look for hint output
8349      */
8350     if (sscanf(message, "Hint: %s", buf1) == 1) {
8351         if (cps == &first && hintRequested) {
8352             hintRequested = FALSE;
8353             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8354                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8355                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8356                                     PosFlags(forwardMostMove),
8357                                     fromY, fromX, toY, toX, promoChar, buf1);
8358                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8359                 DisplayInformation(buf2);
8360             } else {
8361                 /* Hint move could not be parsed!? */
8362               snprintf(buf2, sizeof(buf2),
8363                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8364                         buf1, _(cps->which));
8365                 DisplayError(buf2, 0);
8366             }
8367         } else {
8368           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8369         }
8370         return;
8371     }
8372
8373     /*
8374      * Ignore other messages if game is not in progress
8375      */
8376     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8377         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8378
8379     /*
8380      * look for win, lose, draw, or draw offer
8381      */
8382     if (strncmp(message, "1-0", 3) == 0) {
8383         char *p, *q, *r = "";
8384         p = strchr(message, '{');
8385         if (p) {
8386             q = strchr(p, '}');
8387             if (q) {
8388                 *q = NULLCHAR;
8389                 r = p + 1;
8390             }
8391         }
8392         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8393         return;
8394     } else if (strncmp(message, "0-1", 3) == 0) {
8395         char *p, *q, *r = "";
8396         p = strchr(message, '{');
8397         if (p) {
8398             q = strchr(p, '}');
8399             if (q) {
8400                 *q = NULLCHAR;
8401                 r = p + 1;
8402             }
8403         }
8404         /* Kludge for Arasan 4.1 bug */
8405         if (strcmp(r, "Black resigns") == 0) {
8406             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8407             return;
8408         }
8409         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8410         return;
8411     } else if (strncmp(message, "1/2", 3) == 0) {
8412         char *p, *q, *r = "";
8413         p = strchr(message, '{');
8414         if (p) {
8415             q = strchr(p, '}');
8416             if (q) {
8417                 *q = NULLCHAR;
8418                 r = p + 1;
8419             }
8420         }
8421
8422         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8423         return;
8424
8425     } else if (strncmp(message, "White resign", 12) == 0) {
8426         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8427         return;
8428     } else if (strncmp(message, "Black resign", 12) == 0) {
8429         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8430         return;
8431     } else if (strncmp(message, "White matches", 13) == 0 ||
8432                strncmp(message, "Black matches", 13) == 0   ) {
8433         /* [HGM] ignore GNUShogi noises */
8434         return;
8435     } else if (strncmp(message, "White", 5) == 0 &&
8436                message[5] != '(' &&
8437                StrStr(message, "Black") == NULL) {
8438         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8439         return;
8440     } else if (strncmp(message, "Black", 5) == 0 &&
8441                message[5] != '(') {
8442         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8443         return;
8444     } else if (strcmp(message, "resign") == 0 ||
8445                strcmp(message, "computer resigns") == 0) {
8446         switch (gameMode) {
8447           case MachinePlaysBlack:
8448           case IcsPlayingBlack:
8449             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8450             break;
8451           case MachinePlaysWhite:
8452           case IcsPlayingWhite:
8453             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8454             break;
8455           case TwoMachinesPlay:
8456             if (cps->twoMachinesColor[0] == 'w')
8457               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8458             else
8459               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8460             break;
8461           default:
8462             /* can't happen */
8463             break;
8464         }
8465         return;
8466     } else if (strncmp(message, "opponent mates", 14) == 0) {
8467         switch (gameMode) {
8468           case MachinePlaysBlack:
8469           case IcsPlayingBlack:
8470             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8471             break;
8472           case MachinePlaysWhite:
8473           case IcsPlayingWhite:
8474             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8475             break;
8476           case TwoMachinesPlay:
8477             if (cps->twoMachinesColor[0] == 'w')
8478               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8479             else
8480               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8481             break;
8482           default:
8483             /* can't happen */
8484             break;
8485         }
8486         return;
8487     } else if (strncmp(message, "computer mates", 14) == 0) {
8488         switch (gameMode) {
8489           case MachinePlaysBlack:
8490           case IcsPlayingBlack:
8491             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8492             break;
8493           case MachinePlaysWhite:
8494           case IcsPlayingWhite:
8495             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8496             break;
8497           case TwoMachinesPlay:
8498             if (cps->twoMachinesColor[0] == 'w')
8499               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8500             else
8501               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8502             break;
8503           default:
8504             /* can't happen */
8505             break;
8506         }
8507         return;
8508     } else if (strncmp(message, "checkmate", 9) == 0) {
8509         if (WhiteOnMove(forwardMostMove)) {
8510             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8511         } else {
8512             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8513         }
8514         return;
8515     } else if (strstr(message, "Draw") != NULL ||
8516                strstr(message, "game is a draw") != NULL) {
8517         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8518         return;
8519     } else if (strstr(message, "offer") != NULL &&
8520                strstr(message, "draw") != NULL) {
8521 #if ZIPPY
8522         if (appData.zippyPlay && first.initDone) {
8523             /* Relay offer to ICS */
8524             SendToICS(ics_prefix);
8525             SendToICS("draw\n");
8526         }
8527 #endif
8528         cps->offeredDraw = 2; /* valid until this engine moves twice */
8529         if (gameMode == TwoMachinesPlay) {
8530             if (cps->other->offeredDraw) {
8531                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8532             /* [HGM] in two-machine mode we delay relaying draw offer      */
8533             /* until after we also have move, to see if it is really claim */
8534             }
8535         } else if (gameMode == MachinePlaysWhite ||
8536                    gameMode == MachinePlaysBlack) {
8537           if (userOfferedDraw) {
8538             DisplayInformation(_("Machine accepts your draw offer"));
8539             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8540           } else {
8541             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8542           }
8543         }
8544     }
8545
8546
8547     /*
8548      * Look for thinking output
8549      */
8550     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8551           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8552                                 ) {
8553         int plylev, mvleft, mvtot, curscore, time;
8554         char mvname[MOVE_LEN];
8555         u64 nodes; // [DM]
8556         char plyext;
8557         int ignore = FALSE;
8558         int prefixHint = FALSE;
8559         mvname[0] = NULLCHAR;
8560
8561         switch (gameMode) {
8562           case MachinePlaysBlack:
8563           case IcsPlayingBlack:
8564             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8565             break;
8566           case MachinePlaysWhite:
8567           case IcsPlayingWhite:
8568             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8569             break;
8570           case AnalyzeMode:
8571           case AnalyzeFile:
8572             break;
8573           case IcsObserving: /* [DM] icsEngineAnalyze */
8574             if (!appData.icsEngineAnalyze) ignore = TRUE;
8575             break;
8576           case TwoMachinesPlay:
8577             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8578                 ignore = TRUE;
8579             }
8580             break;
8581           default:
8582             ignore = TRUE;
8583             break;
8584         }
8585
8586         if (!ignore) {
8587             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8588             buf1[0] = NULLCHAR;
8589             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8590                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8591
8592                 if (plyext != ' ' && plyext != '\t') {
8593                     time *= 100;
8594                 }
8595
8596                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8597                 if( cps->scoreIsAbsolute &&
8598                     ( gameMode == MachinePlaysBlack ||
8599                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8600                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8601                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8602                      !WhiteOnMove(currentMove)
8603                     ) )
8604                 {
8605                     curscore = -curscore;
8606                 }
8607
8608                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8609
8610                 tempStats.depth = plylev;
8611                 tempStats.nodes = nodes;
8612                 tempStats.time = time;
8613                 tempStats.score = curscore;
8614                 tempStats.got_only_move = 0;
8615
8616                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8617                         int ticklen;
8618
8619                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8620                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8621                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8622                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8623                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8624                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8625                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8626                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8627                 }
8628
8629                 /* Buffer overflow protection */
8630                 if (pv[0] != NULLCHAR) {
8631                     if (strlen(pv) >= sizeof(tempStats.movelist)
8632                         && appData.debugMode) {
8633                         fprintf(debugFP,
8634                                 "PV is too long; using the first %u bytes.\n",
8635                                 (unsigned) sizeof(tempStats.movelist) - 1);
8636                     }
8637
8638                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8639                 } else {
8640                     sprintf(tempStats.movelist, " no PV\n");
8641                 }
8642
8643                 if (tempStats.seen_stat) {
8644                     tempStats.ok_to_send = 1;
8645                 }
8646
8647                 if (strchr(tempStats.movelist, '(') != NULL) {
8648                     tempStats.line_is_book = 1;
8649                     tempStats.nr_moves = 0;
8650                     tempStats.moves_left = 0;
8651                 } else {
8652                     tempStats.line_is_book = 0;
8653                 }
8654
8655                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8656                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8657
8658                 SendProgramStatsToFrontend( cps, &tempStats );
8659
8660                 /*
8661                     [AS] Protect the thinkOutput buffer from overflow... this
8662                     is only useful if buf1 hasn't overflowed first!
8663                 */
8664                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8665                          plylev,
8666                          (gameMode == TwoMachinesPlay ?
8667                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8668                          ((double) curscore) / 100.0,
8669                          prefixHint ? lastHint : "",
8670                          prefixHint ? " " : "" );
8671
8672                 if( buf1[0] != NULLCHAR ) {
8673                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8674
8675                     if( strlen(pv) > max_len ) {
8676                         if( appData.debugMode) {
8677                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8678                         }
8679                         pv[max_len+1] = '\0';
8680                     }
8681
8682                     strcat( thinkOutput, pv);
8683                 }
8684
8685                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8686                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8687                     DisplayMove(currentMove - 1);
8688                 }
8689                 return;
8690
8691             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8692                 /* crafty (9.25+) says "(only move) <move>"
8693                  * if there is only 1 legal move
8694                  */
8695                 sscanf(p, "(only move) %s", buf1);
8696                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8697                 sprintf(programStats.movelist, "%s (only move)", buf1);
8698                 programStats.depth = 1;
8699                 programStats.nr_moves = 1;
8700                 programStats.moves_left = 1;
8701                 programStats.nodes = 1;
8702                 programStats.time = 1;
8703                 programStats.got_only_move = 1;
8704
8705                 /* Not really, but we also use this member to
8706                    mean "line isn't going to change" (Crafty
8707                    isn't searching, so stats won't change) */
8708                 programStats.line_is_book = 1;
8709
8710                 SendProgramStatsToFrontend( cps, &programStats );
8711
8712                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8713                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8714                     DisplayMove(currentMove - 1);
8715                 }
8716                 return;
8717             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8718                               &time, &nodes, &plylev, &mvleft,
8719                               &mvtot, mvname) >= 5) {
8720                 /* The stat01: line is from Crafty (9.29+) in response
8721                    to the "." command */
8722                 programStats.seen_stat = 1;
8723                 cps->maybeThinking = TRUE;
8724
8725                 if (programStats.got_only_move || !appData.periodicUpdates)
8726                   return;
8727
8728                 programStats.depth = plylev;
8729                 programStats.time = time;
8730                 programStats.nodes = nodes;
8731                 programStats.moves_left = mvleft;
8732                 programStats.nr_moves = mvtot;
8733                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8734                 programStats.ok_to_send = 1;
8735                 programStats.movelist[0] = '\0';
8736
8737                 SendProgramStatsToFrontend( cps, &programStats );
8738
8739                 return;
8740
8741             } else if (strncmp(message,"++",2) == 0) {
8742                 /* Crafty 9.29+ outputs this */
8743                 programStats.got_fail = 2;
8744                 return;
8745
8746             } else if (strncmp(message,"--",2) == 0) {
8747                 /* Crafty 9.29+ outputs this */
8748                 programStats.got_fail = 1;
8749                 return;
8750
8751             } else if (thinkOutput[0] != NULLCHAR &&
8752                        strncmp(message, "    ", 4) == 0) {
8753                 unsigned message_len;
8754
8755                 p = message;
8756                 while (*p && *p == ' ') p++;
8757
8758                 message_len = strlen( p );
8759
8760                 /* [AS] Avoid buffer overflow */
8761                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8762                     strcat(thinkOutput, " ");
8763                     strcat(thinkOutput, p);
8764                 }
8765
8766                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8767                     strcat(programStats.movelist, " ");
8768                     strcat(programStats.movelist, p);
8769                 }
8770
8771                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8772                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8773                     DisplayMove(currentMove - 1);
8774                 }
8775                 return;
8776             }
8777         }
8778         else {
8779             buf1[0] = NULLCHAR;
8780
8781             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8782                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8783             {
8784                 ChessProgramStats cpstats;
8785
8786                 if (plyext != ' ' && plyext != '\t') {
8787                     time *= 100;
8788                 }
8789
8790                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8791                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8792                     curscore = -curscore;
8793                 }
8794
8795                 cpstats.depth = plylev;
8796                 cpstats.nodes = nodes;
8797                 cpstats.time = time;
8798                 cpstats.score = curscore;
8799                 cpstats.got_only_move = 0;
8800                 cpstats.movelist[0] = '\0';
8801
8802                 if (buf1[0] != NULLCHAR) {
8803                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8804                 }
8805
8806                 cpstats.ok_to_send = 0;
8807                 cpstats.line_is_book = 0;
8808                 cpstats.nr_moves = 0;
8809                 cpstats.moves_left = 0;
8810
8811                 SendProgramStatsToFrontend( cps, &cpstats );
8812             }
8813         }
8814     }
8815 }
8816
8817
8818 /* Parse a game score from the character string "game", and
8819    record it as the history of the current game.  The game
8820    score is NOT assumed to start from the standard position.
8821    The display is not updated in any way.
8822    */
8823 void
8824 ParseGameHistory(game)
8825      char *game;
8826 {
8827     ChessMove moveType;
8828     int fromX, fromY, toX, toY, boardIndex;
8829     char promoChar;
8830     char *p, *q;
8831     char buf[MSG_SIZ];
8832
8833     if (appData.debugMode)
8834       fprintf(debugFP, "Parsing game history: %s\n", game);
8835
8836     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8837     gameInfo.site = StrSave(appData.icsHost);
8838     gameInfo.date = PGNDate();
8839     gameInfo.round = StrSave("-");
8840
8841     /* Parse out names of players */
8842     while (*game == ' ') game++;
8843     p = buf;
8844     while (*game != ' ') *p++ = *game++;
8845     *p = NULLCHAR;
8846     gameInfo.white = StrSave(buf);
8847     while (*game == ' ') game++;
8848     p = buf;
8849     while (*game != ' ' && *game != '\n') *p++ = *game++;
8850     *p = NULLCHAR;
8851     gameInfo.black = StrSave(buf);
8852
8853     /* Parse moves */
8854     boardIndex = blackPlaysFirst ? 1 : 0;
8855     yynewstr(game);
8856     for (;;) {
8857         yyboardindex = boardIndex;
8858         moveType = (ChessMove) Myylex();
8859         switch (moveType) {
8860           case IllegalMove:             /* maybe suicide chess, etc. */
8861   if (appData.debugMode) {
8862     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8863     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8864     setbuf(debugFP, NULL);
8865   }
8866           case WhitePromotion:
8867           case BlackPromotion:
8868           case WhiteNonPromotion:
8869           case BlackNonPromotion:
8870           case NormalMove:
8871           case WhiteCapturesEnPassant:
8872           case BlackCapturesEnPassant:
8873           case WhiteKingSideCastle:
8874           case WhiteQueenSideCastle:
8875           case BlackKingSideCastle:
8876           case BlackQueenSideCastle:
8877           case WhiteKingSideCastleWild:
8878           case WhiteQueenSideCastleWild:
8879           case BlackKingSideCastleWild:
8880           case BlackQueenSideCastleWild:
8881           /* PUSH Fabien */
8882           case WhiteHSideCastleFR:
8883           case WhiteASideCastleFR:
8884           case BlackHSideCastleFR:
8885           case BlackASideCastleFR:
8886           /* POP Fabien */
8887             fromX = currentMoveString[0] - AAA;
8888             fromY = currentMoveString[1] - ONE;
8889             toX = currentMoveString[2] - AAA;
8890             toY = currentMoveString[3] - ONE;
8891             promoChar = currentMoveString[4];
8892             break;
8893           case WhiteDrop:
8894           case BlackDrop:
8895             fromX = moveType == WhiteDrop ?
8896               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8897             (int) CharToPiece(ToLower(currentMoveString[0]));
8898             fromY = DROP_RANK;
8899             toX = currentMoveString[2] - AAA;
8900             toY = currentMoveString[3] - ONE;
8901             promoChar = NULLCHAR;
8902             break;
8903           case AmbiguousMove:
8904             /* bug? */
8905             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8906   if (appData.debugMode) {
8907     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8908     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8909     setbuf(debugFP, NULL);
8910   }
8911             DisplayError(buf, 0);
8912             return;
8913           case ImpossibleMove:
8914             /* bug? */
8915             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8916   if (appData.debugMode) {
8917     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8918     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8919     setbuf(debugFP, NULL);
8920   }
8921             DisplayError(buf, 0);
8922             return;
8923           case EndOfFile:
8924             if (boardIndex < backwardMostMove) {
8925                 /* Oops, gap.  How did that happen? */
8926                 DisplayError(_("Gap in move list"), 0);
8927                 return;
8928             }
8929             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8930             if (boardIndex > forwardMostMove) {
8931                 forwardMostMove = boardIndex;
8932             }
8933             return;
8934           case ElapsedTime:
8935             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8936                 strcat(parseList[boardIndex-1], " ");
8937                 strcat(parseList[boardIndex-1], yy_text);
8938             }
8939             continue;
8940           case Comment:
8941           case PGNTag:
8942           case NAG:
8943           default:
8944             /* ignore */
8945             continue;
8946           case WhiteWins:
8947           case BlackWins:
8948           case GameIsDrawn:
8949           case GameUnfinished:
8950             if (gameMode == IcsExamining) {
8951                 if (boardIndex < backwardMostMove) {
8952                     /* Oops, gap.  How did that happen? */
8953                     return;
8954                 }
8955                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8956                 return;
8957             }
8958             gameInfo.result = moveType;
8959             p = strchr(yy_text, '{');
8960             if (p == NULL) p = strchr(yy_text, '(');
8961             if (p == NULL) {
8962                 p = yy_text;
8963                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8964             } else {
8965                 q = strchr(p, *p == '{' ? '}' : ')');
8966                 if (q != NULL) *q = NULLCHAR;
8967                 p++;
8968             }
8969             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8970             gameInfo.resultDetails = StrSave(p);
8971             continue;
8972         }
8973         if (boardIndex >= forwardMostMove &&
8974             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8975             backwardMostMove = blackPlaysFirst ? 1 : 0;
8976             return;
8977         }
8978         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8979                                  fromY, fromX, toY, toX, promoChar,
8980                                  parseList[boardIndex]);
8981         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8982         /* currentMoveString is set as a side-effect of yylex */
8983         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8984         strcat(moveList[boardIndex], "\n");
8985         boardIndex++;
8986         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8987         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8988           case MT_NONE:
8989           case MT_STALEMATE:
8990           default:
8991             break;
8992           case MT_CHECK:
8993             if(gameInfo.variant != VariantShogi)
8994                 strcat(parseList[boardIndex - 1], "+");
8995             break;
8996           case MT_CHECKMATE:
8997           case MT_STAINMATE:
8998             strcat(parseList[boardIndex - 1], "#");
8999             break;
9000         }
9001     }
9002 }
9003
9004
9005 /* Apply a move to the given board  */
9006 void
9007 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9008      int fromX, fromY, toX, toY;
9009      int promoChar;
9010      Board board;
9011 {
9012   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9013   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9014
9015     /* [HGM] compute & store e.p. status and castling rights for new position */
9016     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9017
9018       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9019       oldEP = (signed char)board[EP_STATUS];
9020       board[EP_STATUS] = EP_NONE;
9021
9022   if (fromY == DROP_RANK) {
9023         /* must be first */
9024         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9025             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9026             return;
9027         }
9028         piece = board[toY][toX] = (ChessSquare) fromX;
9029   } else {
9030       int i;
9031
9032       if( board[toY][toX] != EmptySquare )
9033            board[EP_STATUS] = EP_CAPTURE;
9034
9035       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9036            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9037                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9038       } else
9039       if( board[fromY][fromX] == WhitePawn ) {
9040            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9041                board[EP_STATUS] = EP_PAWN_MOVE;
9042            if( toY-fromY==2) {
9043                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9044                         gameInfo.variant != VariantBerolina || toX < fromX)
9045                       board[EP_STATUS] = toX | berolina;
9046                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9047                         gameInfo.variant != VariantBerolina || toX > fromX)
9048                       board[EP_STATUS] = toX;
9049            }
9050       } else
9051       if( board[fromY][fromX] == BlackPawn ) {
9052            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9053                board[EP_STATUS] = EP_PAWN_MOVE;
9054            if( toY-fromY== -2) {
9055                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9056                         gameInfo.variant != VariantBerolina || toX < fromX)
9057                       board[EP_STATUS] = toX | berolina;
9058                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9059                         gameInfo.variant != VariantBerolina || toX > fromX)
9060                       board[EP_STATUS] = toX;
9061            }
9062        }
9063
9064        for(i=0; i<nrCastlingRights; i++) {
9065            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9066               board[CASTLING][i] == toX   && castlingRank[i] == toY
9067              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9068        }
9069
9070      if (fromX == toX && fromY == toY) return;
9071
9072      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9073      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9074      if(gameInfo.variant == VariantKnightmate)
9075          king += (int) WhiteUnicorn - (int) WhiteKing;
9076
9077     /* Code added by Tord: */
9078     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9079     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9080         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9081       board[fromY][fromX] = EmptySquare;
9082       board[toY][toX] = EmptySquare;
9083       if((toX > fromX) != (piece == WhiteRook)) {
9084         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9085       } else {
9086         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9087       }
9088     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9089                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9090       board[fromY][fromX] = EmptySquare;
9091       board[toY][toX] = EmptySquare;
9092       if((toX > fromX) != (piece == BlackRook)) {
9093         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9094       } else {
9095         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9096       }
9097     /* End of code added by Tord */
9098
9099     } else if (board[fromY][fromX] == king
9100         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9101         && toY == fromY && toX > fromX+1) {
9102         board[fromY][fromX] = EmptySquare;
9103         board[toY][toX] = king;
9104         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9105         board[fromY][BOARD_RGHT-1] = EmptySquare;
9106     } else if (board[fromY][fromX] == king
9107         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9108                && toY == fromY && toX < fromX-1) {
9109         board[fromY][fromX] = EmptySquare;
9110         board[toY][toX] = king;
9111         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9112         board[fromY][BOARD_LEFT] = EmptySquare;
9113     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9114                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9115                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9116                ) {
9117         /* white pawn promotion */
9118         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9119         if(gameInfo.variant==VariantBughouse ||
9120            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9121             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9122         board[fromY][fromX] = EmptySquare;
9123     } else if ((fromY >= BOARD_HEIGHT>>1)
9124                && (toX != fromX)
9125                && gameInfo.variant != VariantXiangqi
9126                && gameInfo.variant != VariantBerolina
9127                && (board[fromY][fromX] == WhitePawn)
9128                && (board[toY][toX] == EmptySquare)) {
9129         board[fromY][fromX] = EmptySquare;
9130         board[toY][toX] = WhitePawn;
9131         captured = board[toY - 1][toX];
9132         board[toY - 1][toX] = EmptySquare;
9133     } else if ((fromY == BOARD_HEIGHT-4)
9134                && (toX == fromX)
9135                && gameInfo.variant == VariantBerolina
9136                && (board[fromY][fromX] == WhitePawn)
9137                && (board[toY][toX] == EmptySquare)) {
9138         board[fromY][fromX] = EmptySquare;
9139         board[toY][toX] = WhitePawn;
9140         if(oldEP & EP_BEROLIN_A) {
9141                 captured = board[fromY][fromX-1];
9142                 board[fromY][fromX-1] = EmptySquare;
9143         }else{  captured = board[fromY][fromX+1];
9144                 board[fromY][fromX+1] = EmptySquare;
9145         }
9146     } else if (board[fromY][fromX] == king
9147         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9148                && toY == fromY && toX > fromX+1) {
9149         board[fromY][fromX] = EmptySquare;
9150         board[toY][toX] = king;
9151         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9152         board[fromY][BOARD_RGHT-1] = EmptySquare;
9153     } else if (board[fromY][fromX] == king
9154         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9155                && toY == fromY && toX < fromX-1) {
9156         board[fromY][fromX] = EmptySquare;
9157         board[toY][toX] = king;
9158         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9159         board[fromY][BOARD_LEFT] = EmptySquare;
9160     } else if (fromY == 7 && fromX == 3
9161                && board[fromY][fromX] == BlackKing
9162                && toY == 7 && toX == 5) {
9163         board[fromY][fromX] = EmptySquare;
9164         board[toY][toX] = BlackKing;
9165         board[fromY][7] = EmptySquare;
9166         board[toY][4] = BlackRook;
9167     } else if (fromY == 7 && fromX == 3
9168                && board[fromY][fromX] == BlackKing
9169                && toY == 7 && toX == 1) {
9170         board[fromY][fromX] = EmptySquare;
9171         board[toY][toX] = BlackKing;
9172         board[fromY][0] = EmptySquare;
9173         board[toY][2] = BlackRook;
9174     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9175                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9176                && toY < promoRank && promoChar
9177                ) {
9178         /* black pawn promotion */
9179         board[toY][toX] = CharToPiece(ToLower(promoChar));
9180         if(gameInfo.variant==VariantBughouse ||
9181            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9182             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9183         board[fromY][fromX] = EmptySquare;
9184     } else if ((fromY < BOARD_HEIGHT>>1)
9185                && (toX != fromX)
9186                && gameInfo.variant != VariantXiangqi
9187                && gameInfo.variant != VariantBerolina
9188                && (board[fromY][fromX] == BlackPawn)
9189                && (board[toY][toX] == EmptySquare)) {
9190         board[fromY][fromX] = EmptySquare;
9191         board[toY][toX] = BlackPawn;
9192         captured = board[toY + 1][toX];
9193         board[toY + 1][toX] = EmptySquare;
9194     } else if ((fromY == 3)
9195                && (toX == fromX)
9196                && gameInfo.variant == VariantBerolina
9197                && (board[fromY][fromX] == BlackPawn)
9198                && (board[toY][toX] == EmptySquare)) {
9199         board[fromY][fromX] = EmptySquare;
9200         board[toY][toX] = BlackPawn;
9201         if(oldEP & EP_BEROLIN_A) {
9202                 captured = board[fromY][fromX-1];
9203                 board[fromY][fromX-1] = EmptySquare;
9204         }else{  captured = board[fromY][fromX+1];
9205                 board[fromY][fromX+1] = EmptySquare;
9206         }
9207     } else {
9208         board[toY][toX] = board[fromY][fromX];
9209         board[fromY][fromX] = EmptySquare;
9210     }
9211   }
9212
9213     if (gameInfo.holdingsWidth != 0) {
9214
9215       /* !!A lot more code needs to be written to support holdings  */
9216       /* [HGM] OK, so I have written it. Holdings are stored in the */
9217       /* penultimate board files, so they are automaticlly stored   */
9218       /* in the game history.                                       */
9219       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9220                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9221         /* Delete from holdings, by decreasing count */
9222         /* and erasing image if necessary            */
9223         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9224         if(p < (int) BlackPawn) { /* white drop */
9225              p -= (int)WhitePawn;
9226                  p = PieceToNumber((ChessSquare)p);
9227              if(p >= gameInfo.holdingsSize) p = 0;
9228              if(--board[p][BOARD_WIDTH-2] <= 0)
9229                   board[p][BOARD_WIDTH-1] = EmptySquare;
9230              if((int)board[p][BOARD_WIDTH-2] < 0)
9231                         board[p][BOARD_WIDTH-2] = 0;
9232         } else {                  /* black drop */
9233              p -= (int)BlackPawn;
9234                  p = PieceToNumber((ChessSquare)p);
9235              if(p >= gameInfo.holdingsSize) p = 0;
9236              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9237                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9238              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9239                         board[BOARD_HEIGHT-1-p][1] = 0;
9240         }
9241       }
9242       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9243           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9244         /* [HGM] holdings: Add to holdings, if holdings exist */
9245         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9246                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9247                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9248         }
9249         p = (int) captured;
9250         if (p >= (int) BlackPawn) {
9251           p -= (int)BlackPawn;
9252           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9253                   /* in Shogi restore piece to its original  first */
9254                   captured = (ChessSquare) (DEMOTED captured);
9255                   p = DEMOTED p;
9256           }
9257           p = PieceToNumber((ChessSquare)p);
9258           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9259           board[p][BOARD_WIDTH-2]++;
9260           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9261         } else {
9262           p -= (int)WhitePawn;
9263           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9264                   captured = (ChessSquare) (DEMOTED captured);
9265                   p = DEMOTED p;
9266           }
9267           p = PieceToNumber((ChessSquare)p);
9268           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9269           board[BOARD_HEIGHT-1-p][1]++;
9270           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9271         }
9272       }
9273     } else if (gameInfo.variant == VariantAtomic) {
9274       if (captured != EmptySquare) {
9275         int y, x;
9276         for (y = toY-1; y <= toY+1; y++) {
9277           for (x = toX-1; x <= toX+1; x++) {
9278             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9279                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9280               board[y][x] = EmptySquare;
9281             }
9282           }
9283         }
9284         board[toY][toX] = EmptySquare;
9285       }
9286     }
9287     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9288         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9289     } else
9290     if(promoChar == '+') {
9291         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9292         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9293     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9294         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9295     }
9296     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9297                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9298         // [HGM] superchess: take promotion piece out of holdings
9299         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9300         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9301             if(!--board[k][BOARD_WIDTH-2])
9302                 board[k][BOARD_WIDTH-1] = EmptySquare;
9303         } else {
9304             if(!--board[BOARD_HEIGHT-1-k][1])
9305                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9306         }
9307     }
9308
9309 }
9310
9311 /* Updates forwardMostMove */
9312 void
9313 MakeMove(fromX, fromY, toX, toY, promoChar)
9314      int fromX, fromY, toX, toY;
9315      int promoChar;
9316 {
9317 //    forwardMostMove++; // [HGM] bare: moved downstream
9318
9319     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9320         int timeLeft; static int lastLoadFlag=0; int king, piece;
9321         piece = boards[forwardMostMove][fromY][fromX];
9322         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9323         if(gameInfo.variant == VariantKnightmate)
9324             king += (int) WhiteUnicorn - (int) WhiteKing;
9325         if(forwardMostMove == 0) {
9326             if(blackPlaysFirst)
9327                 fprintf(serverMoves, "%s;", second.tidy);
9328             fprintf(serverMoves, "%s;", first.tidy);
9329             if(!blackPlaysFirst)
9330                 fprintf(serverMoves, "%s;", second.tidy);
9331         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9332         lastLoadFlag = loadFlag;
9333         // print base move
9334         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9335         // print castling suffix
9336         if( toY == fromY && piece == king ) {
9337             if(toX-fromX > 1)
9338                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9339             if(fromX-toX >1)
9340                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9341         }
9342         // e.p. suffix
9343         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9344              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9345              boards[forwardMostMove][toY][toX] == EmptySquare
9346              && fromX != toX && fromY != toY)
9347                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9348         // promotion suffix
9349         if(promoChar != NULLCHAR)
9350                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9351         if(!loadFlag) {
9352             fprintf(serverMoves, "/%d/%d",
9353                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9354             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9355             else                      timeLeft = blackTimeRemaining/1000;
9356             fprintf(serverMoves, "/%d", timeLeft);
9357         }
9358         fflush(serverMoves);
9359     }
9360
9361     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9362       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9363                         0, 1);
9364       return;
9365     }
9366     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9367     if (commentList[forwardMostMove+1] != NULL) {
9368         free(commentList[forwardMostMove+1]);
9369         commentList[forwardMostMove+1] = NULL;
9370     }
9371     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9372     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9373     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9374     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9375     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9376     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9377     gameInfo.result = GameUnfinished;
9378     if (gameInfo.resultDetails != NULL) {
9379         free(gameInfo.resultDetails);
9380         gameInfo.resultDetails = NULL;
9381     }
9382     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9383                               moveList[forwardMostMove - 1]);
9384     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9385                              PosFlags(forwardMostMove - 1),
9386                              fromY, fromX, toY, toX, promoChar,
9387                              parseList[forwardMostMove - 1]);
9388     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9389       case MT_NONE:
9390       case MT_STALEMATE:
9391       default:
9392         break;
9393       case MT_CHECK:
9394         if(gameInfo.variant != VariantShogi)
9395             strcat(parseList[forwardMostMove - 1], "+");
9396         break;
9397       case MT_CHECKMATE:
9398       case MT_STAINMATE:
9399         strcat(parseList[forwardMostMove - 1], "#");
9400         break;
9401     }
9402     if (appData.debugMode) {
9403         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9404     }
9405
9406 }
9407
9408 /* Updates currentMove if not pausing */
9409 void
9410 ShowMove(fromX, fromY, toX, toY)
9411 {
9412     int instant = (gameMode == PlayFromGameFile) ?
9413         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9414     if(appData.noGUI) return;
9415     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9416         if (!instant) {
9417             if (forwardMostMove == currentMove + 1) {
9418                 AnimateMove(boards[forwardMostMove - 1],
9419                             fromX, fromY, toX, toY);
9420             }
9421             if (appData.highlightLastMove) {
9422                 SetHighlights(fromX, fromY, toX, toY);
9423             }
9424         }
9425         currentMove = forwardMostMove;
9426     }
9427
9428     if (instant) return;
9429
9430     DisplayMove(currentMove - 1);
9431     DrawPosition(FALSE, boards[currentMove]);
9432     DisplayBothClocks();
9433     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9434     DisplayBook(currentMove);
9435 }
9436
9437 void SendEgtPath(ChessProgramState *cps)
9438 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9439         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9440
9441         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9442
9443         while(*p) {
9444             char c, *q = name+1, *r, *s;
9445
9446             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9447             while(*p && *p != ',') *q++ = *p++;
9448             *q++ = ':'; *q = 0;
9449             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9450                 strcmp(name, ",nalimov:") == 0 ) {
9451                 // take nalimov path from the menu-changeable option first, if it is defined
9452               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9453                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9454             } else
9455             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9456                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9457                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9458                 s = r = StrStr(s, ":") + 1; // beginning of path info
9459                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9460                 c = *r; *r = 0;             // temporarily null-terminate path info
9461                     *--q = 0;               // strip of trailig ':' from name
9462                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9463                 *r = c;
9464                 SendToProgram(buf,cps);     // send egtbpath command for this format
9465             }
9466             if(*p == ',') p++; // read away comma to position for next format name
9467         }
9468 }
9469
9470 void
9471 InitChessProgram(cps, setup)
9472      ChessProgramState *cps;
9473      int setup; /* [HGM] needed to setup FRC opening position */
9474 {
9475     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9476     if (appData.noChessProgram) return;
9477     hintRequested = FALSE;
9478     bookRequested = FALSE;
9479
9480     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9481     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9482     if(cps->memSize) { /* [HGM] memory */
9483       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9484         SendToProgram(buf, cps);
9485     }
9486     SendEgtPath(cps); /* [HGM] EGT */
9487     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9488       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9489         SendToProgram(buf, cps);
9490     }
9491
9492     SendToProgram(cps->initString, cps);
9493     if (gameInfo.variant != VariantNormal &&
9494         gameInfo.variant != VariantLoadable
9495         /* [HGM] also send variant if board size non-standard */
9496         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9497                                             ) {
9498       char *v = VariantName(gameInfo.variant);
9499       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9500         /* [HGM] in protocol 1 we have to assume all variants valid */
9501         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9502         DisplayFatalError(buf, 0, 1);
9503         return;
9504       }
9505
9506       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9507       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9508       if( gameInfo.variant == VariantXiangqi )
9509            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9510       if( gameInfo.variant == VariantShogi )
9511            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9512       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9513            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9514       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9515           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9516            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9517       if( gameInfo.variant == VariantCourier )
9518            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9519       if( gameInfo.variant == VariantSuper )
9520            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9521       if( gameInfo.variant == VariantGreat )
9522            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9523       if( gameInfo.variant == VariantSChess )
9524            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9525       if( gameInfo.variant == VariantGrand )
9526            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9527
9528       if(overruled) {
9529         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9530                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9531            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9532            if(StrStr(cps->variants, b) == NULL) {
9533                // specific sized variant not known, check if general sizing allowed
9534                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9535                    if(StrStr(cps->variants, "boardsize") == NULL) {
9536                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9537                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9538                        DisplayFatalError(buf, 0, 1);
9539                        return;
9540                    }
9541                    /* [HGM] here we really should compare with the maximum supported board size */
9542                }
9543            }
9544       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9545       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9546       SendToProgram(buf, cps);
9547     }
9548     currentlyInitializedVariant = gameInfo.variant;
9549
9550     /* [HGM] send opening position in FRC to first engine */
9551     if(setup) {
9552           SendToProgram("force\n", cps);
9553           SendBoard(cps, 0);
9554           /* engine is now in force mode! Set flag to wake it up after first move. */
9555           setboardSpoiledMachineBlack = 1;
9556     }
9557
9558     if (cps->sendICS) {
9559       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9560       SendToProgram(buf, cps);
9561     }
9562     cps->maybeThinking = FALSE;
9563     cps->offeredDraw = 0;
9564     if (!appData.icsActive) {
9565         SendTimeControl(cps, movesPerSession, timeControl,
9566                         timeIncrement, appData.searchDepth,
9567                         searchTime);
9568     }
9569     if (appData.showThinking
9570         // [HGM] thinking: four options require thinking output to be sent
9571         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9572                                 ) {
9573         SendToProgram("post\n", cps);
9574     }
9575     SendToProgram("hard\n", cps);
9576     if (!appData.ponderNextMove) {
9577         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9578            it without being sure what state we are in first.  "hard"
9579            is not a toggle, so that one is OK.
9580          */
9581         SendToProgram("easy\n", cps);
9582     }
9583     if (cps->usePing) {
9584       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9585       SendToProgram(buf, cps);
9586     }
9587     cps->initDone = TRUE;
9588     ClearEngineOutputPane(cps == &second);
9589 }
9590
9591
9592 void
9593 StartChessProgram(cps)
9594      ChessProgramState *cps;
9595 {
9596     char buf[MSG_SIZ];
9597     int err;
9598
9599     if (appData.noChessProgram) return;
9600     cps->initDone = FALSE;
9601
9602     if (strcmp(cps->host, "localhost") == 0) {
9603         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9604     } else if (*appData.remoteShell == NULLCHAR) {
9605         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9606     } else {
9607         if (*appData.remoteUser == NULLCHAR) {
9608           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9609                     cps->program);
9610         } else {
9611           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9612                     cps->host, appData.remoteUser, cps->program);
9613         }
9614         err = StartChildProcess(buf, "", &cps->pr);
9615     }
9616
9617     if (err != 0) {
9618       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9619         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9620         if(cps != &first) return;
9621         appData.noChessProgram = TRUE;
9622         ThawUI();
9623         SetNCPMode();
9624 //      DisplayFatalError(buf, err, 1);
9625 //      cps->pr = NoProc;
9626 //      cps->isr = NULL;
9627         return;
9628     }
9629
9630     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9631     if (cps->protocolVersion > 1) {
9632       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9633       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9634       cps->comboCnt = 0;  //                and values of combo boxes
9635       SendToProgram(buf, cps);
9636     } else {
9637       SendToProgram("xboard\n", cps);
9638     }
9639 }
9640
9641 void
9642 TwoMachinesEventIfReady P((void))
9643 {
9644   static int curMess = 0;
9645   if (first.lastPing != first.lastPong) {
9646     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9647     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9648     return;
9649   }
9650   if (second.lastPing != second.lastPong) {
9651     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9652     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9653     return;
9654   }
9655   DisplayMessage("", ""); curMess = 0;
9656   ThawUI();
9657   TwoMachinesEvent();
9658 }
9659
9660 char *
9661 MakeName(char *template)
9662 {
9663     time_t clock;
9664     struct tm *tm;
9665     static char buf[MSG_SIZ];
9666     char *p = buf;
9667     int i;
9668
9669     clock = time((time_t *)NULL);
9670     tm = localtime(&clock);
9671
9672     while(*p++ = *template++) if(p[-1] == '%') {
9673         switch(*template++) {
9674           case 0:   *p = 0; return buf;
9675           case 'Y': i = tm->tm_year+1900; break;
9676           case 'y': i = tm->tm_year-100; break;
9677           case 'M': i = tm->tm_mon+1; break;
9678           case 'd': i = tm->tm_mday; break;
9679           case 'h': i = tm->tm_hour; break;
9680           case 'm': i = tm->tm_min; break;
9681           case 's': i = tm->tm_sec; break;
9682           default:  i = 0;
9683         }
9684         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9685     }
9686     return buf;
9687 }
9688
9689 int
9690 CountPlayers(char *p)
9691 {
9692     int n = 0;
9693     while(p = strchr(p, '\n')) p++, n++; // count participants
9694     return n;
9695 }
9696
9697 FILE *
9698 WriteTourneyFile(char *results)
9699 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9700     FILE *f = fopen(appData.tourneyFile, "w");
9701     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9702         // create a file with tournament description
9703         fprintf(f, "-participants {%s}\n", appData.participants);
9704         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9705         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9706         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9707         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9708         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9709         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9710         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9711         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9712         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9713         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9714         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9715         if(searchTime > 0)
9716                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9717         else {
9718                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9719                 fprintf(f, "-tc %s\n", appData.timeControl);
9720                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9721         }
9722         fprintf(f, "-results \"%s\"\n", results);
9723     }
9724     return f;
9725 }
9726
9727 int
9728 CreateTourney(char *name)
9729 {
9730         FILE *f;
9731         if(name[0] == NULLCHAR) {
9732             if(appData.participants[0])
9733                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9734             return 0;
9735         }
9736         f = fopen(name, "r");
9737         if(f) { // file exists
9738             ASSIGN(appData.tourneyFile, name);
9739             ParseArgsFromFile(f); // parse it
9740         } else {
9741             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9742             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9743                 DisplayError(_("Not enough participants"), 0);
9744                 return 0;
9745             }
9746             ASSIGN(appData.tourneyFile, name);
9747             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9748             if((f = WriteTourneyFile("")) == NULL) return 0;
9749         }
9750         fclose(f);
9751         appData.noChessProgram = FALSE;
9752         appData.clockMode = TRUE;
9753         SetGNUMode();
9754         return 1;
9755 }
9756
9757 #define MAXENGINES 1000
9758 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9759
9760 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9761 {
9762     char buf[MSG_SIZ], *p, *q;
9763     int i=1;
9764     while(*names) {
9765         p = names; q = buf;
9766         while(*p && *p != '\n') *q++ = *p++;
9767         *q = 0;
9768         if(engineList[i]) free(engineList[i]);
9769         engineList[i] = strdup(buf);
9770         if(*p == '\n') p++;
9771         TidyProgramName(engineList[i], "localhost", buf);
9772         if(engineMnemonic[i]) free(engineMnemonic[i]);
9773         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9774             strcat(buf, " (");
9775             sscanf(q + 8, "%s", buf + strlen(buf));
9776             strcat(buf, ")");
9777         }
9778         engineMnemonic[i] = strdup(buf);
9779         names = p; i++;
9780       if(i > MAXENGINES - 2) break;
9781     }
9782     engineList[i] = NULL;
9783 }
9784
9785 // following implemented as macro to avoid type limitations
9786 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9787
9788 void SwapEngines(int n)
9789 {   // swap settings for first engine and other engine (so far only some selected options)
9790     int h;
9791     char *p;
9792     if(n == 0) return;
9793     SWAP(directory, p)
9794     SWAP(chessProgram, p)
9795     SWAP(isUCI, h)
9796     SWAP(hasOwnBookUCI, h)
9797     SWAP(protocolVersion, h)
9798     SWAP(reuse, h)
9799     SWAP(scoreIsAbsolute, h)
9800     SWAP(timeOdds, h)
9801     SWAP(logo, p)
9802     SWAP(pgnName, p)
9803     SWAP(pvSAN, h)
9804 }
9805
9806 void
9807 SetPlayer(int player)
9808 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9809     int i;
9810     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9811     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9812     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9813     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9814     if(mnemonic[i]) {
9815         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9816         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9817         ParseArgsFromString(buf);
9818     }
9819     free(engineName);
9820 }
9821
9822 int
9823 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9824 {   // determine players from game number
9825     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9826
9827     if(appData.tourneyType == 0) {
9828         roundsPerCycle = (nPlayers - 1) | 1;
9829         pairingsPerRound = nPlayers / 2;
9830     } else if(appData.tourneyType > 0) {
9831         roundsPerCycle = nPlayers - appData.tourneyType;
9832         pairingsPerRound = appData.tourneyType;
9833     }
9834     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9835     gamesPerCycle = gamesPerRound * roundsPerCycle;
9836     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9837     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9838     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9839     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9840     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9841     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9842
9843     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9844     if(appData.roundSync) *syncInterval = gamesPerRound;
9845
9846     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9847
9848     if(appData.tourneyType == 0) {
9849         if(curPairing == (nPlayers-1)/2 ) {
9850             *whitePlayer = curRound;
9851             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9852         } else {
9853             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9854             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9855             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9856             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9857         }
9858     } else if(appData.tourneyType > 0) {
9859         *whitePlayer = curPairing;
9860         *blackPlayer = curRound + appData.tourneyType;
9861     }
9862
9863     // take care of white/black alternation per round. 
9864     // For cycles and games this is already taken care of by default, derived from matchGame!
9865     return curRound & 1;
9866 }
9867
9868 int
9869 NextTourneyGame(int nr, int *swapColors)
9870 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9871     char *p, *q;
9872     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9873     FILE *tf;
9874     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9875     tf = fopen(appData.tourneyFile, "r");
9876     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9877     ParseArgsFromFile(tf); fclose(tf);
9878     InitTimeControls(); // TC might be altered from tourney file
9879
9880     nPlayers = CountPlayers(appData.participants); // count participants
9881     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9882     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9883
9884     if(syncInterval) {
9885         p = q = appData.results;
9886         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9887         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9888             DisplayMessage(_("Waiting for other game(s)"),"");
9889             waitingForGame = TRUE;
9890             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9891             return 0;
9892         }
9893         waitingForGame = FALSE;
9894     }
9895
9896     if(appData.tourneyType < 0) {
9897         if(nr>=0 && !pairingReceived) {
9898             char buf[1<<16];
9899             if(pairing.pr == NoProc) {
9900                 if(!appData.pairingEngine[0]) {
9901                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9902                     return 0;
9903                 }
9904                 StartChessProgram(&pairing); // starts the pairing engine
9905             }
9906             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9907             SendToProgram(buf, &pairing);
9908             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9909             SendToProgram(buf, &pairing);
9910             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9911         }
9912         pairingReceived = 0;                              // ... so we continue here 
9913         *swapColors = 0;
9914         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9915         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9916         matchGame = 1; roundNr = nr / syncInterval + 1;
9917     }
9918
9919     if(first.pr != NoProc) return 1; // engines already loaded
9920
9921     // redefine engines, engine dir, etc.
9922     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9923     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9924     SwapEngines(1);
9925     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9926     SwapEngines(1);         // and make that valid for second engine by swapping
9927     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9928     InitEngine(&second, 1);
9929     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9930     UpdateLogos(FALSE);     // leave display to ModeHiglight()
9931     return 1;
9932 }
9933
9934 void
9935 NextMatchGame()
9936 {   // performs game initialization that does not invoke engines, and then tries to start the game
9937     int firstWhite, swapColors = 0;
9938     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9939     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9940     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9941     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9942     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9943     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9944     Reset(FALSE, first.pr != NoProc);
9945     appData.noChessProgram = FALSE;
9946     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9947     TwoMachinesEvent();
9948 }
9949
9950 void UserAdjudicationEvent( int result )
9951 {
9952     ChessMove gameResult = GameIsDrawn;
9953
9954     if( result > 0 ) {
9955         gameResult = WhiteWins;
9956     }
9957     else if( result < 0 ) {
9958         gameResult = BlackWins;
9959     }
9960
9961     if( gameMode == TwoMachinesPlay ) {
9962         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9963     }
9964 }
9965
9966
9967 // [HGM] save: calculate checksum of game to make games easily identifiable
9968 int StringCheckSum(char *s)
9969 {
9970         int i = 0;
9971         if(s==NULL) return 0;
9972         while(*s) i = i*259 + *s++;
9973         return i;
9974 }
9975
9976 int GameCheckSum()
9977 {
9978         int i, sum=0;
9979         for(i=backwardMostMove; i<forwardMostMove; i++) {
9980                 sum += pvInfoList[i].depth;
9981                 sum += StringCheckSum(parseList[i]);
9982                 sum += StringCheckSum(commentList[i]);
9983                 sum *= 261;
9984         }
9985         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9986         return sum + StringCheckSum(commentList[i]);
9987 } // end of save patch
9988
9989 void
9990 GameEnds(result, resultDetails, whosays)
9991      ChessMove result;
9992      char *resultDetails;
9993      int whosays;
9994 {
9995     GameMode nextGameMode;
9996     int isIcsGame;
9997     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9998
9999     if(endingGame) return; /* [HGM] crash: forbid recursion */
10000     endingGame = 1;
10001     if(twoBoards) { // [HGM] dual: switch back to one board
10002         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10003         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10004     }
10005     if (appData.debugMode) {
10006       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10007               result, resultDetails ? resultDetails : "(null)", whosays);
10008     }
10009
10010     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10011
10012     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10013         /* If we are playing on ICS, the server decides when the
10014            game is over, but the engine can offer to draw, claim
10015            a draw, or resign.
10016          */
10017 #if ZIPPY
10018         if (appData.zippyPlay && first.initDone) {
10019             if (result == GameIsDrawn) {
10020                 /* In case draw still needs to be claimed */
10021                 SendToICS(ics_prefix);
10022                 SendToICS("draw\n");
10023             } else if (StrCaseStr(resultDetails, "resign")) {
10024                 SendToICS(ics_prefix);
10025                 SendToICS("resign\n");
10026             }
10027         }
10028 #endif
10029         endingGame = 0; /* [HGM] crash */
10030         return;
10031     }
10032
10033     /* If we're loading the game from a file, stop */
10034     if (whosays == GE_FILE) {
10035       (void) StopLoadGameTimer();
10036       gameFileFP = NULL;
10037     }
10038
10039     /* Cancel draw offers */
10040     first.offeredDraw = second.offeredDraw = 0;
10041
10042     /* If this is an ICS game, only ICS can really say it's done;
10043        if not, anyone can. */
10044     isIcsGame = (gameMode == IcsPlayingWhite ||
10045                  gameMode == IcsPlayingBlack ||
10046                  gameMode == IcsObserving    ||
10047                  gameMode == IcsExamining);
10048
10049     if (!isIcsGame || whosays == GE_ICS) {
10050         /* OK -- not an ICS game, or ICS said it was done */
10051         StopClocks();
10052         if (!isIcsGame && !appData.noChessProgram)
10053           SetUserThinkingEnables();
10054
10055         /* [HGM] if a machine claims the game end we verify this claim */
10056         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10057             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10058                 char claimer;
10059                 ChessMove trueResult = (ChessMove) -1;
10060
10061                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10062                                             first.twoMachinesColor[0] :
10063                                             second.twoMachinesColor[0] ;
10064
10065                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10066                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10067                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10068                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10069                 } else
10070                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10071                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10072                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10073                 } else
10074                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10075                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10076                 }
10077
10078                 // now verify win claims, but not in drop games, as we don't understand those yet
10079                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10080                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10081                     (result == WhiteWins && claimer == 'w' ||
10082                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10083                       if (appData.debugMode) {
10084                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10085                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10086                       }
10087                       if(result != trueResult) {
10088                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10089                               result = claimer == 'w' ? BlackWins : WhiteWins;
10090                               resultDetails = buf;
10091                       }
10092                 } else
10093                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10094                     && (forwardMostMove <= backwardMostMove ||
10095                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10096                         (claimer=='b')==(forwardMostMove&1))
10097                                                                                   ) {
10098                       /* [HGM] verify: draws that were not flagged are false claims */
10099                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10100                       result = claimer == 'w' ? BlackWins : WhiteWins;
10101                       resultDetails = buf;
10102                 }
10103                 /* (Claiming a loss is accepted no questions asked!) */
10104             }
10105             /* [HGM] bare: don't allow bare King to win */
10106             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10107                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10108                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10109                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10110                && result != GameIsDrawn)
10111             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10112                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10113                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10114                         if(p >= 0 && p <= (int)WhiteKing) k++;
10115                 }
10116                 if (appData.debugMode) {
10117                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10118                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10119                 }
10120                 if(k <= 1) {
10121                         result = GameIsDrawn;
10122                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10123                         resultDetails = buf;
10124                 }
10125             }
10126         }
10127
10128
10129         if(serverMoves != NULL && !loadFlag) { char c = '=';
10130             if(result==WhiteWins) c = '+';
10131             if(result==BlackWins) c = '-';
10132             if(resultDetails != NULL)
10133                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10134         }
10135         if (resultDetails != NULL) {
10136             gameInfo.result = result;
10137             gameInfo.resultDetails = StrSave(resultDetails);
10138
10139             /* display last move only if game was not loaded from file */
10140             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10141                 DisplayMove(currentMove - 1);
10142
10143             if (forwardMostMove != 0) {
10144                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10145                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10146                                                                 ) {
10147                     if (*appData.saveGameFile != NULLCHAR) {
10148                         SaveGameToFile(appData.saveGameFile, TRUE);
10149                     } else if (appData.autoSaveGames) {
10150                         AutoSaveGame();
10151                     }
10152                     if (*appData.savePositionFile != NULLCHAR) {
10153                         SavePositionToFile(appData.savePositionFile);
10154                     }
10155                 }
10156             }
10157
10158             /* Tell program how game ended in case it is learning */
10159             /* [HGM] Moved this to after saving the PGN, just in case */
10160             /* engine died and we got here through time loss. In that */
10161             /* case we will get a fatal error writing the pipe, which */
10162             /* would otherwise lose us the PGN.                       */
10163             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10164             /* output during GameEnds should never be fatal anymore   */
10165             if (gameMode == MachinePlaysWhite ||
10166                 gameMode == MachinePlaysBlack ||
10167                 gameMode == TwoMachinesPlay ||
10168                 gameMode == IcsPlayingWhite ||
10169                 gameMode == IcsPlayingBlack ||
10170                 gameMode == BeginningOfGame) {
10171                 char buf[MSG_SIZ];
10172                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10173                         resultDetails);
10174                 if (first.pr != NoProc) {
10175                     SendToProgram(buf, &first);
10176                 }
10177                 if (second.pr != NoProc &&
10178                     gameMode == TwoMachinesPlay) {
10179                     SendToProgram(buf, &second);
10180                 }
10181             }
10182         }
10183
10184         if (appData.icsActive) {
10185             if (appData.quietPlay &&
10186                 (gameMode == IcsPlayingWhite ||
10187                  gameMode == IcsPlayingBlack)) {
10188                 SendToICS(ics_prefix);
10189                 SendToICS("set shout 1\n");
10190             }
10191             nextGameMode = IcsIdle;
10192             ics_user_moved = FALSE;
10193             /* clean up premove.  It's ugly when the game has ended and the
10194              * premove highlights are still on the board.
10195              */
10196             if (gotPremove) {
10197               gotPremove = FALSE;
10198               ClearPremoveHighlights();
10199               DrawPosition(FALSE, boards[currentMove]);
10200             }
10201             if (whosays == GE_ICS) {
10202                 switch (result) {
10203                 case WhiteWins:
10204                     if (gameMode == IcsPlayingWhite)
10205                         PlayIcsWinSound();
10206                     else if(gameMode == IcsPlayingBlack)
10207                         PlayIcsLossSound();
10208                     break;
10209                 case BlackWins:
10210                     if (gameMode == IcsPlayingBlack)
10211                         PlayIcsWinSound();
10212                     else if(gameMode == IcsPlayingWhite)
10213                         PlayIcsLossSound();
10214                     break;
10215                 case GameIsDrawn:
10216                     PlayIcsDrawSound();
10217                     break;
10218                 default:
10219                     PlayIcsUnfinishedSound();
10220                 }
10221             }
10222         } else if (gameMode == EditGame ||
10223                    gameMode == PlayFromGameFile ||
10224                    gameMode == AnalyzeMode ||
10225                    gameMode == AnalyzeFile) {
10226             nextGameMode = gameMode;
10227         } else {
10228             nextGameMode = EndOfGame;
10229         }
10230         pausing = FALSE;
10231         ModeHighlight();
10232     } else {
10233         nextGameMode = gameMode;
10234     }
10235
10236     if (appData.noChessProgram) {
10237         gameMode = nextGameMode;
10238         ModeHighlight();
10239         endingGame = 0; /* [HGM] crash */
10240         return;
10241     }
10242
10243     if (first.reuse) {
10244         /* Put first chess program into idle state */
10245         if (first.pr != NoProc &&
10246             (gameMode == MachinePlaysWhite ||
10247              gameMode == MachinePlaysBlack ||
10248              gameMode == TwoMachinesPlay ||
10249              gameMode == IcsPlayingWhite ||
10250              gameMode == IcsPlayingBlack ||
10251              gameMode == BeginningOfGame)) {
10252             SendToProgram("force\n", &first);
10253             if (first.usePing) {
10254               char buf[MSG_SIZ];
10255               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10256               SendToProgram(buf, &first);
10257             }
10258         }
10259     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10260         /* Kill off first chess program */
10261         if (first.isr != NULL)
10262           RemoveInputSource(first.isr);
10263         first.isr = NULL;
10264
10265         if (first.pr != NoProc) {
10266             ExitAnalyzeMode();
10267             DoSleep( appData.delayBeforeQuit );
10268             SendToProgram("quit\n", &first);
10269             DoSleep( appData.delayAfterQuit );
10270             DestroyChildProcess(first.pr, first.useSigterm);
10271         }
10272         first.pr = NoProc;
10273     }
10274     if (second.reuse) {
10275         /* Put second chess program into idle state */
10276         if (second.pr != NoProc &&
10277             gameMode == TwoMachinesPlay) {
10278             SendToProgram("force\n", &second);
10279             if (second.usePing) {
10280               char buf[MSG_SIZ];
10281               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10282               SendToProgram(buf, &second);
10283             }
10284         }
10285     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10286         /* Kill off second chess program */
10287         if (second.isr != NULL)
10288           RemoveInputSource(second.isr);
10289         second.isr = NULL;
10290
10291         if (second.pr != NoProc) {
10292             DoSleep( appData.delayBeforeQuit );
10293             SendToProgram("quit\n", &second);
10294             DoSleep( appData.delayAfterQuit );
10295             DestroyChildProcess(second.pr, second.useSigterm);
10296         }
10297         second.pr = NoProc;
10298     }
10299
10300     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10301         char resChar = '=';
10302         switch (result) {
10303         case WhiteWins:
10304           resChar = '+';
10305           if (first.twoMachinesColor[0] == 'w') {
10306             first.matchWins++;
10307           } else {
10308             second.matchWins++;
10309           }
10310           break;
10311         case BlackWins:
10312           resChar = '-';
10313           if (first.twoMachinesColor[0] == 'b') {
10314             first.matchWins++;
10315           } else {
10316             second.matchWins++;
10317           }
10318           break;
10319         case GameUnfinished:
10320           resChar = ' ';
10321         default:
10322           break;
10323         }
10324
10325         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10326         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10327             ReserveGame(nextGame, resChar); // sets nextGame
10328             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10329             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10330         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10331
10332         if (nextGame <= appData.matchGames && !abortMatch) {
10333             gameMode = nextGameMode;
10334             matchGame = nextGame; // this will be overruled in tourney mode!
10335             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10336             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10337             endingGame = 0; /* [HGM] crash */
10338             return;
10339         } else {
10340             gameMode = nextGameMode;
10341             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10342                      first.tidy, second.tidy,
10343                      first.matchWins, second.matchWins,
10344                      appData.matchGames - (first.matchWins + second.matchWins));
10345             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10346             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10347             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10348                 first.twoMachinesColor = "black\n";
10349                 second.twoMachinesColor = "white\n";
10350             } else {
10351                 first.twoMachinesColor = "white\n";
10352                 second.twoMachinesColor = "black\n";
10353             }
10354         }
10355     }
10356     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10357         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10358       ExitAnalyzeMode();
10359     gameMode = nextGameMode;
10360     ModeHighlight();
10361     endingGame = 0;  /* [HGM] crash */
10362     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10363         if(matchMode == TRUE) { // match through command line: exit with or without popup
10364             if(ranking) {
10365                 ToNrEvent(forwardMostMove);
10366                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10367                 else ExitEvent(0);
10368             } else DisplayFatalError(buf, 0, 0);
10369         } else { // match through menu; just stop, with or without popup
10370             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10371             ModeHighlight();
10372             if(ranking){
10373                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10374             } else DisplayNote(buf);
10375       }
10376       if(ranking) free(ranking);
10377     }
10378 }
10379
10380 /* Assumes program was just initialized (initString sent).
10381    Leaves program in force mode. */
10382 void
10383 FeedMovesToProgram(cps, upto)
10384      ChessProgramState *cps;
10385      int upto;
10386 {
10387     int i;
10388
10389     if (appData.debugMode)
10390       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10391               startedFromSetupPosition ? "position and " : "",
10392               backwardMostMove, upto, cps->which);
10393     if(currentlyInitializedVariant != gameInfo.variant) {
10394       char buf[MSG_SIZ];
10395         // [HGM] variantswitch: make engine aware of new variant
10396         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10397                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10398         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10399         SendToProgram(buf, cps);
10400         currentlyInitializedVariant = gameInfo.variant;
10401     }
10402     SendToProgram("force\n", cps);
10403     if (startedFromSetupPosition) {
10404         SendBoard(cps, backwardMostMove);
10405     if (appData.debugMode) {
10406         fprintf(debugFP, "feedMoves\n");
10407     }
10408     }
10409     for (i = backwardMostMove; i < upto; i++) {
10410         SendMoveToProgram(i, cps);
10411     }
10412 }
10413
10414
10415 int
10416 ResurrectChessProgram()
10417 {
10418      /* The chess program may have exited.
10419         If so, restart it and feed it all the moves made so far. */
10420     static int doInit = 0;
10421
10422     if (appData.noChessProgram) return 1;
10423
10424     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10425         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10426         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10427         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10428     } else {
10429         if (first.pr != NoProc) return 1;
10430         StartChessProgram(&first);
10431     }
10432     InitChessProgram(&first, FALSE);
10433     FeedMovesToProgram(&first, currentMove);
10434
10435     if (!first.sendTime) {
10436         /* can't tell gnuchess what its clock should read,
10437            so we bow to its notion. */
10438         ResetClocks();
10439         timeRemaining[0][currentMove] = whiteTimeRemaining;
10440         timeRemaining[1][currentMove] = blackTimeRemaining;
10441     }
10442
10443     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10444                 appData.icsEngineAnalyze) && first.analysisSupport) {
10445       SendToProgram("analyze\n", &first);
10446       first.analyzing = TRUE;
10447     }
10448     return 1;
10449 }
10450
10451 /*
10452  * Button procedures
10453  */
10454 void
10455 Reset(redraw, init)
10456      int redraw, init;
10457 {
10458     int i;
10459
10460     if (appData.debugMode) {
10461         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10462                 redraw, init, gameMode);
10463     }
10464     CleanupTail(); // [HGM] vari: delete any stored variations
10465     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10466     pausing = pauseExamInvalid = FALSE;
10467     startedFromSetupPosition = blackPlaysFirst = FALSE;
10468     firstMove = TRUE;
10469     whiteFlag = blackFlag = FALSE;
10470     userOfferedDraw = FALSE;
10471     hintRequested = bookRequested = FALSE;
10472     first.maybeThinking = FALSE;
10473     second.maybeThinking = FALSE;
10474     first.bookSuspend = FALSE; // [HGM] book
10475     second.bookSuspend = FALSE;
10476     thinkOutput[0] = NULLCHAR;
10477     lastHint[0] = NULLCHAR;
10478     ClearGameInfo(&gameInfo);
10479     gameInfo.variant = StringToVariant(appData.variant);
10480     ics_user_moved = ics_clock_paused = FALSE;
10481     ics_getting_history = H_FALSE;
10482     ics_gamenum = -1;
10483     white_holding[0] = black_holding[0] = NULLCHAR;
10484     ClearProgramStats();
10485     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10486
10487     ResetFrontEnd();
10488     ClearHighlights();
10489     flipView = appData.flipView;
10490     ClearPremoveHighlights();
10491     gotPremove = FALSE;
10492     alarmSounded = FALSE;
10493
10494     GameEnds(EndOfFile, NULL, GE_PLAYER);
10495     if(appData.serverMovesName != NULL) {
10496         /* [HGM] prepare to make moves file for broadcasting */
10497         clock_t t = clock();
10498         if(serverMoves != NULL) fclose(serverMoves);
10499         serverMoves = fopen(appData.serverMovesName, "r");
10500         if(serverMoves != NULL) {
10501             fclose(serverMoves);
10502             /* delay 15 sec before overwriting, so all clients can see end */
10503             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10504         }
10505         serverMoves = fopen(appData.serverMovesName, "w");
10506     }
10507
10508     ExitAnalyzeMode();
10509     gameMode = BeginningOfGame;
10510     ModeHighlight();
10511     if(appData.icsActive) gameInfo.variant = VariantNormal;
10512     currentMove = forwardMostMove = backwardMostMove = 0;
10513     InitPosition(redraw);
10514     for (i = 0; i < MAX_MOVES; i++) {
10515         if (commentList[i] != NULL) {
10516             free(commentList[i]);
10517             commentList[i] = NULL;
10518         }
10519     }
10520     ResetClocks();
10521     timeRemaining[0][0] = whiteTimeRemaining;
10522     timeRemaining[1][0] = blackTimeRemaining;
10523
10524     if (first.pr == NULL) {
10525         StartChessProgram(&first);
10526     }
10527     if (init) {
10528             InitChessProgram(&first, startedFromSetupPosition);
10529     }
10530     DisplayTitle("");
10531     DisplayMessage("", "");
10532     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10533     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10534 }
10535
10536 void
10537 AutoPlayGameLoop()
10538 {
10539     for (;;) {
10540         if (!AutoPlayOneMove())
10541           return;
10542         if (matchMode || appData.timeDelay == 0)
10543           continue;
10544         if (appData.timeDelay < 0)
10545           return;
10546         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10547         break;
10548     }
10549 }
10550
10551
10552 int
10553 AutoPlayOneMove()
10554 {
10555     int fromX, fromY, toX, toY;
10556
10557     if (appData.debugMode) {
10558       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10559     }
10560
10561     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10562       return FALSE;
10563
10564     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10565       pvInfoList[currentMove].depth = programStats.depth;
10566       pvInfoList[currentMove].score = programStats.score;
10567       pvInfoList[currentMove].time  = 0;
10568       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10569     }
10570
10571     if (currentMove >= forwardMostMove) {
10572       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10573       gameMode = EditGame;
10574       ModeHighlight();
10575
10576       /* [AS] Clear current move marker at the end of a game */
10577       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10578
10579       return FALSE;
10580     }
10581
10582     toX = moveList[currentMove][2] - AAA;
10583     toY = moveList[currentMove][3] - ONE;
10584
10585     if (moveList[currentMove][1] == '@') {
10586         if (appData.highlightLastMove) {
10587             SetHighlights(-1, -1, toX, toY);
10588         }
10589     } else {
10590         fromX = moveList[currentMove][0] - AAA;
10591         fromY = moveList[currentMove][1] - ONE;
10592
10593         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10594
10595         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10596
10597         if (appData.highlightLastMove) {
10598             SetHighlights(fromX, fromY, toX, toY);
10599         }
10600     }
10601     DisplayMove(currentMove);
10602     SendMoveToProgram(currentMove++, &first);
10603     DisplayBothClocks();
10604     DrawPosition(FALSE, boards[currentMove]);
10605     // [HGM] PV info: always display, routine tests if empty
10606     DisplayComment(currentMove - 1, commentList[currentMove]);
10607     return TRUE;
10608 }
10609
10610
10611 int
10612 LoadGameOneMove(readAhead)
10613      ChessMove readAhead;
10614 {
10615     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10616     char promoChar = NULLCHAR;
10617     ChessMove moveType;
10618     char move[MSG_SIZ];
10619     char *p, *q;
10620
10621     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10622         gameMode != AnalyzeMode && gameMode != Training) {
10623         gameFileFP = NULL;
10624         return FALSE;
10625     }
10626
10627     yyboardindex = forwardMostMove;
10628     if (readAhead != EndOfFile) {
10629       moveType = readAhead;
10630     } else {
10631       if (gameFileFP == NULL)
10632           return FALSE;
10633       moveType = (ChessMove) Myylex();
10634     }
10635
10636     done = FALSE;
10637     switch (moveType) {
10638       case Comment:
10639         if (appData.debugMode)
10640           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10641         p = yy_text;
10642
10643         /* append the comment but don't display it */
10644         AppendComment(currentMove, p, FALSE);
10645         return TRUE;
10646
10647       case WhiteCapturesEnPassant:
10648       case BlackCapturesEnPassant:
10649       case WhitePromotion:
10650       case BlackPromotion:
10651       case WhiteNonPromotion:
10652       case BlackNonPromotion:
10653       case NormalMove:
10654       case WhiteKingSideCastle:
10655       case WhiteQueenSideCastle:
10656       case BlackKingSideCastle:
10657       case BlackQueenSideCastle:
10658       case WhiteKingSideCastleWild:
10659       case WhiteQueenSideCastleWild:
10660       case BlackKingSideCastleWild:
10661       case BlackQueenSideCastleWild:
10662       /* PUSH Fabien */
10663       case WhiteHSideCastleFR:
10664       case WhiteASideCastleFR:
10665       case BlackHSideCastleFR:
10666       case BlackASideCastleFR:
10667       /* POP Fabien */
10668         if (appData.debugMode)
10669           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10670         fromX = currentMoveString[0] - AAA;
10671         fromY = currentMoveString[1] - ONE;
10672         toX = currentMoveString[2] - AAA;
10673         toY = currentMoveString[3] - ONE;
10674         promoChar = currentMoveString[4];
10675         break;
10676
10677       case WhiteDrop:
10678       case BlackDrop:
10679         if (appData.debugMode)
10680           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10681         fromX = moveType == WhiteDrop ?
10682           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10683         (int) CharToPiece(ToLower(currentMoveString[0]));
10684         fromY = DROP_RANK;
10685         toX = currentMoveString[2] - AAA;
10686         toY = currentMoveString[3] - ONE;
10687         break;
10688
10689       case WhiteWins:
10690       case BlackWins:
10691       case GameIsDrawn:
10692       case GameUnfinished:
10693         if (appData.debugMode)
10694           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10695         p = strchr(yy_text, '{');
10696         if (p == NULL) p = strchr(yy_text, '(');
10697         if (p == NULL) {
10698             p = yy_text;
10699             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10700         } else {
10701             q = strchr(p, *p == '{' ? '}' : ')');
10702             if (q != NULL) *q = NULLCHAR;
10703             p++;
10704         }
10705         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10706         GameEnds(moveType, p, GE_FILE);
10707         done = TRUE;
10708         if (cmailMsgLoaded) {
10709             ClearHighlights();
10710             flipView = WhiteOnMove(currentMove);
10711             if (moveType == GameUnfinished) flipView = !flipView;
10712             if (appData.debugMode)
10713               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10714         }
10715         break;
10716
10717       case EndOfFile:
10718         if (appData.debugMode)
10719           fprintf(debugFP, "Parser hit end of file\n");
10720         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10721           case MT_NONE:
10722           case MT_CHECK:
10723             break;
10724           case MT_CHECKMATE:
10725           case MT_STAINMATE:
10726             if (WhiteOnMove(currentMove)) {
10727                 GameEnds(BlackWins, "Black mates", GE_FILE);
10728             } else {
10729                 GameEnds(WhiteWins, "White mates", GE_FILE);
10730             }
10731             break;
10732           case MT_STALEMATE:
10733             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10734             break;
10735         }
10736         done = TRUE;
10737         break;
10738
10739       case MoveNumberOne:
10740         if (lastLoadGameStart == GNUChessGame) {
10741             /* GNUChessGames have numbers, but they aren't move numbers */
10742             if (appData.debugMode)
10743               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10744                       yy_text, (int) moveType);
10745             return LoadGameOneMove(EndOfFile); /* tail recursion */
10746         }
10747         /* else fall thru */
10748
10749       case XBoardGame:
10750       case GNUChessGame:
10751       case PGNTag:
10752         /* Reached start of next game in file */
10753         if (appData.debugMode)
10754           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10755         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10756           case MT_NONE:
10757           case MT_CHECK:
10758             break;
10759           case MT_CHECKMATE:
10760           case MT_STAINMATE:
10761             if (WhiteOnMove(currentMove)) {
10762                 GameEnds(BlackWins, "Black mates", GE_FILE);
10763             } else {
10764                 GameEnds(WhiteWins, "White mates", GE_FILE);
10765             }
10766             break;
10767           case MT_STALEMATE:
10768             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10769             break;
10770         }
10771         done = TRUE;
10772         break;
10773
10774       case PositionDiagram:     /* should not happen; ignore */
10775       case ElapsedTime:         /* ignore */
10776       case NAG:                 /* ignore */
10777         if (appData.debugMode)
10778           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10779                   yy_text, (int) moveType);
10780         return LoadGameOneMove(EndOfFile); /* tail recursion */
10781
10782       case IllegalMove:
10783         if (appData.testLegality) {
10784             if (appData.debugMode)
10785               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10786             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10787                     (forwardMostMove / 2) + 1,
10788                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10789             DisplayError(move, 0);
10790             done = TRUE;
10791         } else {
10792             if (appData.debugMode)
10793               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10794                       yy_text, currentMoveString);
10795             fromX = currentMoveString[0] - AAA;
10796             fromY = currentMoveString[1] - ONE;
10797             toX = currentMoveString[2] - AAA;
10798             toY = currentMoveString[3] - ONE;
10799             promoChar = currentMoveString[4];
10800         }
10801         break;
10802
10803       case AmbiguousMove:
10804         if (appData.debugMode)
10805           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10806         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10807                 (forwardMostMove / 2) + 1,
10808                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10809         DisplayError(move, 0);
10810         done = TRUE;
10811         break;
10812
10813       default:
10814       case ImpossibleMove:
10815         if (appData.debugMode)
10816           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10817         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10818                 (forwardMostMove / 2) + 1,
10819                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10820         DisplayError(move, 0);
10821         done = TRUE;
10822         break;
10823     }
10824
10825     if (done) {
10826         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10827             DrawPosition(FALSE, boards[currentMove]);
10828             DisplayBothClocks();
10829             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10830               DisplayComment(currentMove - 1, commentList[currentMove]);
10831         }
10832         (void) StopLoadGameTimer();
10833         gameFileFP = NULL;
10834         cmailOldMove = forwardMostMove;
10835         return FALSE;
10836     } else {
10837         /* currentMoveString is set as a side-effect of yylex */
10838
10839         thinkOutput[0] = NULLCHAR;
10840         MakeMove(fromX, fromY, toX, toY, promoChar);
10841         currentMove = forwardMostMove;
10842         return TRUE;
10843     }
10844 }
10845
10846 /* Load the nth game from the given file */
10847 int
10848 LoadGameFromFile(filename, n, title, useList)
10849      char *filename;
10850      int n;
10851      char *title;
10852      /*Boolean*/ int useList;
10853 {
10854     FILE *f;
10855     char buf[MSG_SIZ];
10856
10857     if (strcmp(filename, "-") == 0) {
10858         f = stdin;
10859         title = "stdin";
10860     } else {
10861         f = fopen(filename, "rb");
10862         if (f == NULL) {
10863           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10864             DisplayError(buf, errno);
10865             return FALSE;
10866         }
10867     }
10868     if (fseek(f, 0, 0) == -1) {
10869         /* f is not seekable; probably a pipe */
10870         useList = FALSE;
10871     }
10872     if (useList && n == 0) {
10873         int error = GameListBuild(f);
10874         if (error) {
10875             DisplayError(_("Cannot build game list"), error);
10876         } else if (!ListEmpty(&gameList) &&
10877                    ((ListGame *) gameList.tailPred)->number > 1) {
10878             GameListPopUp(f, title);
10879             return TRUE;
10880         }
10881         GameListDestroy();
10882         n = 1;
10883     }
10884     if (n == 0) n = 1;
10885     return LoadGame(f, n, title, FALSE);
10886 }
10887
10888
10889 void
10890 MakeRegisteredMove()
10891 {
10892     int fromX, fromY, toX, toY;
10893     char promoChar;
10894     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10895         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10896           case CMAIL_MOVE:
10897           case CMAIL_DRAW:
10898             if (appData.debugMode)
10899               fprintf(debugFP, "Restoring %s for game %d\n",
10900                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10901
10902             thinkOutput[0] = NULLCHAR;
10903             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10904             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10905             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10906             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10907             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10908             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10909             MakeMove(fromX, fromY, toX, toY, promoChar);
10910             ShowMove(fromX, fromY, toX, toY);
10911
10912             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10913               case MT_NONE:
10914               case MT_CHECK:
10915                 break;
10916
10917               case MT_CHECKMATE:
10918               case MT_STAINMATE:
10919                 if (WhiteOnMove(currentMove)) {
10920                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10921                 } else {
10922                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10923                 }
10924                 break;
10925
10926               case MT_STALEMATE:
10927                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10928                 break;
10929             }
10930
10931             break;
10932
10933           case CMAIL_RESIGN:
10934             if (WhiteOnMove(currentMove)) {
10935                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10936             } else {
10937                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10938             }
10939             break;
10940
10941           case CMAIL_ACCEPT:
10942             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10943             break;
10944
10945           default:
10946             break;
10947         }
10948     }
10949
10950     return;
10951 }
10952
10953 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10954 int
10955 CmailLoadGame(f, gameNumber, title, useList)
10956      FILE *f;
10957      int gameNumber;
10958      char *title;
10959      int useList;
10960 {
10961     int retVal;
10962
10963     if (gameNumber > nCmailGames) {
10964         DisplayError(_("No more games in this message"), 0);
10965         return FALSE;
10966     }
10967     if (f == lastLoadGameFP) {
10968         int offset = gameNumber - lastLoadGameNumber;
10969         if (offset == 0) {
10970             cmailMsg[0] = NULLCHAR;
10971             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10972                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10973                 nCmailMovesRegistered--;
10974             }
10975             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10976             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10977                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10978             }
10979         } else {
10980             if (! RegisterMove()) return FALSE;
10981         }
10982     }
10983
10984     retVal = LoadGame(f, gameNumber, title, useList);
10985
10986     /* Make move registered during previous look at this game, if any */
10987     MakeRegisteredMove();
10988
10989     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10990         commentList[currentMove]
10991           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10992         DisplayComment(currentMove - 1, commentList[currentMove]);
10993     }
10994
10995     return retVal;
10996 }
10997
10998 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10999 int
11000 ReloadGame(offset)
11001      int offset;
11002 {
11003     int gameNumber = lastLoadGameNumber + offset;
11004     if (lastLoadGameFP == NULL) {
11005         DisplayError(_("No game has been loaded yet"), 0);
11006         return FALSE;
11007     }
11008     if (gameNumber <= 0) {
11009         DisplayError(_("Can't back up any further"), 0);
11010         return FALSE;
11011     }
11012     if (cmailMsgLoaded) {
11013         return CmailLoadGame(lastLoadGameFP, gameNumber,
11014                              lastLoadGameTitle, lastLoadGameUseList);
11015     } else {
11016         return LoadGame(lastLoadGameFP, gameNumber,
11017                         lastLoadGameTitle, lastLoadGameUseList);
11018     }
11019 }
11020
11021
11022
11023 /* Load the nth game from open file f */
11024 int
11025 LoadGame(f, gameNumber, title, useList)
11026      FILE *f;
11027      int gameNumber;
11028      char *title;
11029      int useList;
11030 {
11031     ChessMove cm;
11032     char buf[MSG_SIZ];
11033     int gn = gameNumber;
11034     ListGame *lg = NULL;
11035     int numPGNTags = 0;
11036     int err;
11037     GameMode oldGameMode;
11038     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11039
11040     if (appData.debugMode)
11041         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11042
11043     if (gameMode == Training )
11044         SetTrainingModeOff();
11045
11046     oldGameMode = gameMode;
11047     if (gameMode != BeginningOfGame) {
11048       Reset(FALSE, TRUE);
11049     }
11050
11051     gameFileFP = f;
11052     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11053         fclose(lastLoadGameFP);
11054     }
11055
11056     if (useList) {
11057         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11058
11059         if (lg) {
11060             fseek(f, lg->offset, 0);
11061             GameListHighlight(gameNumber);
11062             gn = 1;
11063         }
11064         else {
11065             DisplayError(_("Game number out of range"), 0);
11066             return FALSE;
11067         }
11068     } else {
11069         GameListDestroy();
11070         if (fseek(f, 0, 0) == -1) {
11071             if (f == lastLoadGameFP ?
11072                 gameNumber == lastLoadGameNumber + 1 :
11073                 gameNumber == 1) {
11074                 gn = 1;
11075             } else {
11076                 DisplayError(_("Can't seek on game file"), 0);
11077                 return FALSE;
11078             }
11079         }
11080     }
11081     lastLoadGameFP = f;
11082     lastLoadGameNumber = gameNumber;
11083     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11084     lastLoadGameUseList = useList;
11085
11086     yynewfile(f);
11087
11088     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11089       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11090                 lg->gameInfo.black);
11091             DisplayTitle(buf);
11092     } else if (*title != NULLCHAR) {
11093         if (gameNumber > 1) {
11094           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11095             DisplayTitle(buf);
11096         } else {
11097             DisplayTitle(title);
11098         }
11099     }
11100
11101     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11102         gameMode = PlayFromGameFile;
11103         ModeHighlight();
11104     }
11105
11106     currentMove = forwardMostMove = backwardMostMove = 0;
11107     CopyBoard(boards[0], initialPosition);
11108     StopClocks();
11109
11110     /*
11111      * Skip the first gn-1 games in the file.
11112      * Also skip over anything that precedes an identifiable
11113      * start of game marker, to avoid being confused by
11114      * garbage at the start of the file.  Currently
11115      * recognized start of game markers are the move number "1",
11116      * the pattern "gnuchess .* game", the pattern
11117      * "^[#;%] [^ ]* game file", and a PGN tag block.
11118      * A game that starts with one of the latter two patterns
11119      * will also have a move number 1, possibly
11120      * following a position diagram.
11121      * 5-4-02: Let's try being more lenient and allowing a game to
11122      * start with an unnumbered move.  Does that break anything?
11123      */
11124     cm = lastLoadGameStart = EndOfFile;
11125     while (gn > 0) {
11126         yyboardindex = forwardMostMove;
11127         cm = (ChessMove) Myylex();
11128         switch (cm) {
11129           case EndOfFile:
11130             if (cmailMsgLoaded) {
11131                 nCmailGames = CMAIL_MAX_GAMES - gn;
11132             } else {
11133                 Reset(TRUE, TRUE);
11134                 DisplayError(_("Game not found in file"), 0);
11135             }
11136             return FALSE;
11137
11138           case GNUChessGame:
11139           case XBoardGame:
11140             gn--;
11141             lastLoadGameStart = cm;
11142             break;
11143
11144           case MoveNumberOne:
11145             switch (lastLoadGameStart) {
11146               case GNUChessGame:
11147               case XBoardGame:
11148               case PGNTag:
11149                 break;
11150               case MoveNumberOne:
11151               case EndOfFile:
11152                 gn--;           /* count this game */
11153                 lastLoadGameStart = cm;
11154                 break;
11155               default:
11156                 /* impossible */
11157                 break;
11158             }
11159             break;
11160
11161           case PGNTag:
11162             switch (lastLoadGameStart) {
11163               case GNUChessGame:
11164               case PGNTag:
11165               case MoveNumberOne:
11166               case EndOfFile:
11167                 gn--;           /* count this game */
11168                 lastLoadGameStart = cm;
11169                 break;
11170               case XBoardGame:
11171                 lastLoadGameStart = cm; /* game counted already */
11172                 break;
11173               default:
11174                 /* impossible */
11175                 break;
11176             }
11177             if (gn > 0) {
11178                 do {
11179                     yyboardindex = forwardMostMove;
11180                     cm = (ChessMove) Myylex();
11181                 } while (cm == PGNTag || cm == Comment);
11182             }
11183             break;
11184
11185           case WhiteWins:
11186           case BlackWins:
11187           case GameIsDrawn:
11188             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11189                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11190                     != CMAIL_OLD_RESULT) {
11191                     nCmailResults ++ ;
11192                     cmailResult[  CMAIL_MAX_GAMES
11193                                 - gn - 1] = CMAIL_OLD_RESULT;
11194                 }
11195             }
11196             break;
11197
11198           case NormalMove:
11199             /* Only a NormalMove can be at the start of a game
11200              * without a position diagram. */
11201             if (lastLoadGameStart == EndOfFile ) {
11202               gn--;
11203               lastLoadGameStart = MoveNumberOne;
11204             }
11205             break;
11206
11207           default:
11208             break;
11209         }
11210     }
11211
11212     if (appData.debugMode)
11213       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11214
11215     if (cm == XBoardGame) {
11216         /* Skip any header junk before position diagram and/or move 1 */
11217         for (;;) {
11218             yyboardindex = forwardMostMove;
11219             cm = (ChessMove) Myylex();
11220
11221             if (cm == EndOfFile ||
11222                 cm == GNUChessGame || cm == XBoardGame) {
11223                 /* Empty game; pretend end-of-file and handle later */
11224                 cm = EndOfFile;
11225                 break;
11226             }
11227
11228             if (cm == MoveNumberOne || cm == PositionDiagram ||
11229                 cm == PGNTag || cm == Comment)
11230               break;
11231         }
11232     } else if (cm == GNUChessGame) {
11233         if (gameInfo.event != NULL) {
11234             free(gameInfo.event);
11235         }
11236         gameInfo.event = StrSave(yy_text);
11237     }
11238
11239     startedFromSetupPosition = FALSE;
11240     while (cm == PGNTag) {
11241         if (appData.debugMode)
11242           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11243         err = ParsePGNTag(yy_text, &gameInfo);
11244         if (!err) numPGNTags++;
11245
11246         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11247         if(gameInfo.variant != oldVariant) {
11248             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11249             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11250             InitPosition(TRUE);
11251             oldVariant = gameInfo.variant;
11252             if (appData.debugMode)
11253               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11254         }
11255
11256
11257         if (gameInfo.fen != NULL) {
11258           Board initial_position;
11259           startedFromSetupPosition = TRUE;
11260           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11261             Reset(TRUE, TRUE);
11262             DisplayError(_("Bad FEN position in file"), 0);
11263             return FALSE;
11264           }
11265           CopyBoard(boards[0], initial_position);
11266           if (blackPlaysFirst) {
11267             currentMove = forwardMostMove = backwardMostMove = 1;
11268             CopyBoard(boards[1], initial_position);
11269             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11270             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11271             timeRemaining[0][1] = whiteTimeRemaining;
11272             timeRemaining[1][1] = blackTimeRemaining;
11273             if (commentList[0] != NULL) {
11274               commentList[1] = commentList[0];
11275               commentList[0] = NULL;
11276             }
11277           } else {
11278             currentMove = forwardMostMove = backwardMostMove = 0;
11279           }
11280           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11281           {   int i;
11282               initialRulePlies = FENrulePlies;
11283               for( i=0; i< nrCastlingRights; i++ )
11284                   initialRights[i] = initial_position[CASTLING][i];
11285           }
11286           yyboardindex = forwardMostMove;
11287           free(gameInfo.fen);
11288           gameInfo.fen = NULL;
11289         }
11290
11291         yyboardindex = forwardMostMove;
11292         cm = (ChessMove) Myylex();
11293
11294         /* Handle comments interspersed among the tags */
11295         while (cm == Comment) {
11296             char *p;
11297             if (appData.debugMode)
11298               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11299             p = yy_text;
11300             AppendComment(currentMove, p, FALSE);
11301             yyboardindex = forwardMostMove;
11302             cm = (ChessMove) Myylex();
11303         }
11304     }
11305
11306     /* don't rely on existence of Event tag since if game was
11307      * pasted from clipboard the Event tag may not exist
11308      */
11309     if (numPGNTags > 0){
11310         char *tags;
11311         if (gameInfo.variant == VariantNormal) {
11312           VariantClass v = StringToVariant(gameInfo.event);
11313           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11314           if(v < VariantShogi) gameInfo.variant = v;
11315         }
11316         if (!matchMode) {
11317           if( appData.autoDisplayTags ) {
11318             tags = PGNTags(&gameInfo);
11319             TagsPopUp(tags, CmailMsg());
11320             free(tags);
11321           }
11322         }
11323     } else {
11324         /* Make something up, but don't display it now */
11325         SetGameInfo();
11326         TagsPopDown();
11327     }
11328
11329     if (cm == PositionDiagram) {
11330         int i, j;
11331         char *p;
11332         Board initial_position;
11333
11334         if (appData.debugMode)
11335           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11336
11337         if (!startedFromSetupPosition) {
11338             p = yy_text;
11339             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11340               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11341                 switch (*p) {
11342                   case '{':
11343                   case '[':
11344                   case '-':
11345                   case ' ':
11346                   case '\t':
11347                   case '\n':
11348                   case '\r':
11349                     break;
11350                   default:
11351                     initial_position[i][j++] = CharToPiece(*p);
11352                     break;
11353                 }
11354             while (*p == ' ' || *p == '\t' ||
11355                    *p == '\n' || *p == '\r') p++;
11356
11357             if (strncmp(p, "black", strlen("black"))==0)
11358               blackPlaysFirst = TRUE;
11359             else
11360               blackPlaysFirst = FALSE;
11361             startedFromSetupPosition = TRUE;
11362
11363             CopyBoard(boards[0], initial_position);
11364             if (blackPlaysFirst) {
11365                 currentMove = forwardMostMove = backwardMostMove = 1;
11366                 CopyBoard(boards[1], initial_position);
11367                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11368                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11369                 timeRemaining[0][1] = whiteTimeRemaining;
11370                 timeRemaining[1][1] = blackTimeRemaining;
11371                 if (commentList[0] != NULL) {
11372                     commentList[1] = commentList[0];
11373                     commentList[0] = NULL;
11374                 }
11375             } else {
11376                 currentMove = forwardMostMove = backwardMostMove = 0;
11377             }
11378         }
11379         yyboardindex = forwardMostMove;
11380         cm = (ChessMove) Myylex();
11381     }
11382
11383     if (first.pr == NoProc) {
11384         StartChessProgram(&first);
11385     }
11386     InitChessProgram(&first, FALSE);
11387     SendToProgram("force\n", &first);
11388     if (startedFromSetupPosition) {
11389         SendBoard(&first, forwardMostMove);
11390     if (appData.debugMode) {
11391         fprintf(debugFP, "Load Game\n");
11392     }
11393         DisplayBothClocks();
11394     }
11395
11396     /* [HGM] server: flag to write setup moves in broadcast file as one */
11397     loadFlag = appData.suppressLoadMoves;
11398
11399     while (cm == Comment) {
11400         char *p;
11401         if (appData.debugMode)
11402           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11403         p = yy_text;
11404         AppendComment(currentMove, p, FALSE);
11405         yyboardindex = forwardMostMove;
11406         cm = (ChessMove) Myylex();
11407     }
11408
11409     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11410         cm == WhiteWins || cm == BlackWins ||
11411         cm == GameIsDrawn || cm == GameUnfinished) {
11412         DisplayMessage("", _("No moves in game"));
11413         if (cmailMsgLoaded) {
11414             if (appData.debugMode)
11415               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11416             ClearHighlights();
11417             flipView = FALSE;
11418         }
11419         DrawPosition(FALSE, boards[currentMove]);
11420         DisplayBothClocks();
11421         gameMode = EditGame;
11422         ModeHighlight();
11423         gameFileFP = NULL;
11424         cmailOldMove = 0;
11425         return TRUE;
11426     }
11427
11428     // [HGM] PV info: routine tests if comment empty
11429     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11430         DisplayComment(currentMove - 1, commentList[currentMove]);
11431     }
11432     if (!matchMode && appData.timeDelay != 0)
11433       DrawPosition(FALSE, boards[currentMove]);
11434
11435     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11436       programStats.ok_to_send = 1;
11437     }
11438
11439     /* if the first token after the PGN tags is a move
11440      * and not move number 1, retrieve it from the parser
11441      */
11442     if (cm != MoveNumberOne)
11443         LoadGameOneMove(cm);
11444
11445     /* load the remaining moves from the file */
11446     while (LoadGameOneMove(EndOfFile)) {
11447       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11448       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11449     }
11450
11451     /* rewind to the start of the game */
11452     currentMove = backwardMostMove;
11453
11454     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11455
11456     if (oldGameMode == AnalyzeFile ||
11457         oldGameMode == AnalyzeMode) {
11458       AnalyzeFileEvent();
11459     }
11460
11461     if (matchMode || appData.timeDelay == 0) {
11462       ToEndEvent();
11463       gameMode = EditGame;
11464       ModeHighlight();
11465     } else if (appData.timeDelay > 0) {
11466       AutoPlayGameLoop();
11467     }
11468
11469     if (appData.debugMode)
11470         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11471
11472     loadFlag = 0; /* [HGM] true game starts */
11473     return TRUE;
11474 }
11475
11476 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11477 int
11478 ReloadPosition(offset)
11479      int offset;
11480 {
11481     int positionNumber = lastLoadPositionNumber + offset;
11482     if (lastLoadPositionFP == NULL) {
11483         DisplayError(_("No position has been loaded yet"), 0);
11484         return FALSE;
11485     }
11486     if (positionNumber <= 0) {
11487         DisplayError(_("Can't back up any further"), 0);
11488         return FALSE;
11489     }
11490     return LoadPosition(lastLoadPositionFP, positionNumber,
11491                         lastLoadPositionTitle);
11492 }
11493
11494 /* Load the nth position from the given file */
11495 int
11496 LoadPositionFromFile(filename, n, title)
11497      char *filename;
11498      int n;
11499      char *title;
11500 {
11501     FILE *f;
11502     char buf[MSG_SIZ];
11503
11504     if (strcmp(filename, "-") == 0) {
11505         return LoadPosition(stdin, n, "stdin");
11506     } else {
11507         f = fopen(filename, "rb");
11508         if (f == NULL) {
11509             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11510             DisplayError(buf, errno);
11511             return FALSE;
11512         } else {
11513             return LoadPosition(f, n, title);
11514         }
11515     }
11516 }
11517
11518 /* Load the nth position from the given open file, and close it */
11519 int
11520 LoadPosition(f, positionNumber, title)
11521      FILE *f;
11522      int positionNumber;
11523      char *title;
11524 {
11525     char *p, line[MSG_SIZ];
11526     Board initial_position;
11527     int i, j, fenMode, pn;
11528
11529     if (gameMode == Training )
11530         SetTrainingModeOff();
11531
11532     if (gameMode != BeginningOfGame) {
11533         Reset(FALSE, TRUE);
11534     }
11535     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11536         fclose(lastLoadPositionFP);
11537     }
11538     if (positionNumber == 0) positionNumber = 1;
11539     lastLoadPositionFP = f;
11540     lastLoadPositionNumber = positionNumber;
11541     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11542     if (first.pr == NoProc) {
11543       StartChessProgram(&first);
11544       InitChessProgram(&first, FALSE);
11545     }
11546     pn = positionNumber;
11547     if (positionNumber < 0) {
11548         /* Negative position number means to seek to that byte offset */
11549         if (fseek(f, -positionNumber, 0) == -1) {
11550             DisplayError(_("Can't seek on position file"), 0);
11551             return FALSE;
11552         };
11553         pn = 1;
11554     } else {
11555         if (fseek(f, 0, 0) == -1) {
11556             if (f == lastLoadPositionFP ?
11557                 positionNumber == lastLoadPositionNumber + 1 :
11558                 positionNumber == 1) {
11559                 pn = 1;
11560             } else {
11561                 DisplayError(_("Can't seek on position file"), 0);
11562                 return FALSE;
11563             }
11564         }
11565     }
11566     /* See if this file is FEN or old-style xboard */
11567     if (fgets(line, MSG_SIZ, f) == NULL) {
11568         DisplayError(_("Position not found in file"), 0);
11569         return FALSE;
11570     }
11571     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11572     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11573
11574     if (pn >= 2) {
11575         if (fenMode || line[0] == '#') pn--;
11576         while (pn > 0) {
11577             /* skip positions before number pn */
11578             if (fgets(line, MSG_SIZ, f) == NULL) {
11579                 Reset(TRUE, TRUE);
11580                 DisplayError(_("Position not found in file"), 0);
11581                 return FALSE;
11582             }
11583             if (fenMode || line[0] == '#') pn--;
11584         }
11585     }
11586
11587     if (fenMode) {
11588         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11589             DisplayError(_("Bad FEN position in file"), 0);
11590             return FALSE;
11591         }
11592     } else {
11593         (void) fgets(line, MSG_SIZ, f);
11594         (void) fgets(line, MSG_SIZ, f);
11595
11596         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11597             (void) fgets(line, MSG_SIZ, f);
11598             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11599                 if (*p == ' ')
11600                   continue;
11601                 initial_position[i][j++] = CharToPiece(*p);
11602             }
11603         }
11604
11605         blackPlaysFirst = FALSE;
11606         if (!feof(f)) {
11607             (void) fgets(line, MSG_SIZ, f);
11608             if (strncmp(line, "black", strlen("black"))==0)
11609               blackPlaysFirst = TRUE;
11610         }
11611     }
11612     startedFromSetupPosition = TRUE;
11613
11614     SendToProgram("force\n", &first);
11615     CopyBoard(boards[0], initial_position);
11616     if (blackPlaysFirst) {
11617         currentMove = forwardMostMove = backwardMostMove = 1;
11618         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11619         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11620         CopyBoard(boards[1], initial_position);
11621         DisplayMessage("", _("Black to play"));
11622     } else {
11623         currentMove = forwardMostMove = backwardMostMove = 0;
11624         DisplayMessage("", _("White to play"));
11625     }
11626     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11627     SendBoard(&first, forwardMostMove);
11628     if (appData.debugMode) {
11629 int i, j;
11630   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11631   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11632         fprintf(debugFP, "Load Position\n");
11633     }
11634
11635     if (positionNumber > 1) {
11636       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11637         DisplayTitle(line);
11638     } else {
11639         DisplayTitle(title);
11640     }
11641     gameMode = EditGame;
11642     ModeHighlight();
11643     ResetClocks();
11644     timeRemaining[0][1] = whiteTimeRemaining;
11645     timeRemaining[1][1] = blackTimeRemaining;
11646     DrawPosition(FALSE, boards[currentMove]);
11647
11648     return TRUE;
11649 }
11650
11651
11652 void
11653 CopyPlayerNameIntoFileName(dest, src)
11654      char **dest, *src;
11655 {
11656     while (*src != NULLCHAR && *src != ',') {
11657         if (*src == ' ') {
11658             *(*dest)++ = '_';
11659             src++;
11660         } else {
11661             *(*dest)++ = *src++;
11662         }
11663     }
11664 }
11665
11666 char *DefaultFileName(ext)
11667      char *ext;
11668 {
11669     static char def[MSG_SIZ];
11670     char *p;
11671
11672     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11673         p = def;
11674         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11675         *p++ = '-';
11676         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11677         *p++ = '.';
11678         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11679     } else {
11680         def[0] = NULLCHAR;
11681     }
11682     return def;
11683 }
11684
11685 /* Save the current game to the given file */
11686 int
11687 SaveGameToFile(filename, append)
11688      char *filename;
11689      int append;
11690 {
11691     FILE *f;
11692     char buf[MSG_SIZ];
11693     int result;
11694
11695     if (strcmp(filename, "-") == 0) {
11696         return SaveGame(stdout, 0, NULL);
11697     } else {
11698         f = fopen(filename, append ? "a" : "w");
11699         if (f == NULL) {
11700             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11701             DisplayError(buf, errno);
11702             return FALSE;
11703         } else {
11704             safeStrCpy(buf, lastMsg, MSG_SIZ);
11705             DisplayMessage(_("Waiting for access to save file"), "");
11706             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11707             DisplayMessage(_("Saving game"), "");
11708             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11709             result = SaveGame(f, 0, NULL);
11710             DisplayMessage(buf, "");
11711             return result;
11712         }
11713     }
11714 }
11715
11716 char *
11717 SavePart(str)
11718      char *str;
11719 {
11720     static char buf[MSG_SIZ];
11721     char *p;
11722
11723     p = strchr(str, ' ');
11724     if (p == NULL) return str;
11725     strncpy(buf, str, p - str);
11726     buf[p - str] = NULLCHAR;
11727     return buf;
11728 }
11729
11730 #define PGN_MAX_LINE 75
11731
11732 #define PGN_SIDE_WHITE  0
11733 #define PGN_SIDE_BLACK  1
11734
11735 /* [AS] */
11736 static int FindFirstMoveOutOfBook( int side )
11737 {
11738     int result = -1;
11739
11740     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11741         int index = backwardMostMove;
11742         int has_book_hit = 0;
11743
11744         if( (index % 2) != side ) {
11745             index++;
11746         }
11747
11748         while( index < forwardMostMove ) {
11749             /* Check to see if engine is in book */
11750             int depth = pvInfoList[index].depth;
11751             int score = pvInfoList[index].score;
11752             int in_book = 0;
11753
11754             if( depth <= 2 ) {
11755                 in_book = 1;
11756             }
11757             else if( score == 0 && depth == 63 ) {
11758                 in_book = 1; /* Zappa */
11759             }
11760             else if( score == 2 && depth == 99 ) {
11761                 in_book = 1; /* Abrok */
11762             }
11763
11764             has_book_hit += in_book;
11765
11766             if( ! in_book ) {
11767                 result = index;
11768
11769                 break;
11770             }
11771
11772             index += 2;
11773         }
11774     }
11775
11776     return result;
11777 }
11778
11779 /* [AS] */
11780 void GetOutOfBookInfo( char * buf )
11781 {
11782     int oob[2];
11783     int i;
11784     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11785
11786     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11787     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11788
11789     *buf = '\0';
11790
11791     if( oob[0] >= 0 || oob[1] >= 0 ) {
11792         for( i=0; i<2; i++ ) {
11793             int idx = oob[i];
11794
11795             if( idx >= 0 ) {
11796                 if( i > 0 && oob[0] >= 0 ) {
11797                     strcat( buf, "   " );
11798                 }
11799
11800                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11801                 sprintf( buf+strlen(buf), "%s%.2f",
11802                     pvInfoList[idx].score >= 0 ? "+" : "",
11803                     pvInfoList[idx].score / 100.0 );
11804             }
11805         }
11806     }
11807 }
11808
11809 /* Save game in PGN style and close the file */
11810 int
11811 SaveGamePGN(f)
11812      FILE *f;
11813 {
11814     int i, offset, linelen, newblock;
11815     time_t tm;
11816 //    char *movetext;
11817     char numtext[32];
11818     int movelen, numlen, blank;
11819     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11820
11821     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11822
11823     tm = time((time_t *) NULL);
11824
11825     PrintPGNTags(f, &gameInfo);
11826
11827     if (backwardMostMove > 0 || startedFromSetupPosition) {
11828         char *fen = PositionToFEN(backwardMostMove, NULL);
11829         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11830         fprintf(f, "\n{--------------\n");
11831         PrintPosition(f, backwardMostMove);
11832         fprintf(f, "--------------}\n");
11833         free(fen);
11834     }
11835     else {
11836         /* [AS] Out of book annotation */
11837         if( appData.saveOutOfBookInfo ) {
11838             char buf[64];
11839
11840             GetOutOfBookInfo( buf );
11841
11842             if( buf[0] != '\0' ) {
11843                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11844             }
11845         }
11846
11847         fprintf(f, "\n");
11848     }
11849
11850     i = backwardMostMove;
11851     linelen = 0;
11852     newblock = TRUE;
11853
11854     while (i < forwardMostMove) {
11855         /* Print comments preceding this move */
11856         if (commentList[i] != NULL) {
11857             if (linelen > 0) fprintf(f, "\n");
11858             fprintf(f, "%s", commentList[i]);
11859             linelen = 0;
11860             newblock = TRUE;
11861         }
11862
11863         /* Format move number */
11864         if ((i % 2) == 0)
11865           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11866         else
11867           if (newblock)
11868             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11869           else
11870             numtext[0] = NULLCHAR;
11871
11872         numlen = strlen(numtext);
11873         newblock = FALSE;
11874
11875         /* Print move number */
11876         blank = linelen > 0 && numlen > 0;
11877         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11878             fprintf(f, "\n");
11879             linelen = 0;
11880             blank = 0;
11881         }
11882         if (blank) {
11883             fprintf(f, " ");
11884             linelen++;
11885         }
11886         fprintf(f, "%s", numtext);
11887         linelen += numlen;
11888
11889         /* Get move */
11890         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11891         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11892
11893         /* Print move */
11894         blank = linelen > 0 && movelen > 0;
11895         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11896             fprintf(f, "\n");
11897             linelen = 0;
11898             blank = 0;
11899         }
11900         if (blank) {
11901             fprintf(f, " ");
11902             linelen++;
11903         }
11904         fprintf(f, "%s", move_buffer);
11905         linelen += movelen;
11906
11907         /* [AS] Add PV info if present */
11908         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11909             /* [HGM] add time */
11910             char buf[MSG_SIZ]; int seconds;
11911
11912             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11913
11914             if( seconds <= 0)
11915               buf[0] = 0;
11916             else
11917               if( seconds < 30 )
11918                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11919               else
11920                 {
11921                   seconds = (seconds + 4)/10; // round to full seconds
11922                   if( seconds < 60 )
11923                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11924                   else
11925                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11926                 }
11927
11928             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11929                       pvInfoList[i].score >= 0 ? "+" : "",
11930                       pvInfoList[i].score / 100.0,
11931                       pvInfoList[i].depth,
11932                       buf );
11933
11934             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11935
11936             /* Print score/depth */
11937             blank = linelen > 0 && movelen > 0;
11938             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11939                 fprintf(f, "\n");
11940                 linelen = 0;
11941                 blank = 0;
11942             }
11943             if (blank) {
11944                 fprintf(f, " ");
11945                 linelen++;
11946             }
11947             fprintf(f, "%s", move_buffer);
11948             linelen += movelen;
11949         }
11950
11951         i++;
11952     }
11953
11954     /* Start a new line */
11955     if (linelen > 0) fprintf(f, "\n");
11956
11957     /* Print comments after last move */
11958     if (commentList[i] != NULL) {
11959         fprintf(f, "%s\n", commentList[i]);
11960     }
11961
11962     /* Print result */
11963     if (gameInfo.resultDetails != NULL &&
11964         gameInfo.resultDetails[0] != NULLCHAR) {
11965         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11966                 PGNResult(gameInfo.result));
11967     } else {
11968         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11969     }
11970
11971     fclose(f);
11972     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11973     return TRUE;
11974 }
11975
11976 /* Save game in old style and close the file */
11977 int
11978 SaveGameOldStyle(f)
11979      FILE *f;
11980 {
11981     int i, offset;
11982     time_t tm;
11983
11984     tm = time((time_t *) NULL);
11985
11986     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11987     PrintOpponents(f);
11988
11989     if (backwardMostMove > 0 || startedFromSetupPosition) {
11990         fprintf(f, "\n[--------------\n");
11991         PrintPosition(f, backwardMostMove);
11992         fprintf(f, "--------------]\n");
11993     } else {
11994         fprintf(f, "\n");
11995     }
11996
11997     i = backwardMostMove;
11998     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11999
12000     while (i < forwardMostMove) {
12001         if (commentList[i] != NULL) {
12002             fprintf(f, "[%s]\n", commentList[i]);
12003         }
12004
12005         if ((i % 2) == 1) {
12006             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12007             i++;
12008         } else {
12009             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12010             i++;
12011             if (commentList[i] != NULL) {
12012                 fprintf(f, "\n");
12013                 continue;
12014             }
12015             if (i >= forwardMostMove) {
12016                 fprintf(f, "\n");
12017                 break;
12018             }
12019             fprintf(f, "%s\n", parseList[i]);
12020             i++;
12021         }
12022     }
12023
12024     if (commentList[i] != NULL) {
12025         fprintf(f, "[%s]\n", commentList[i]);
12026     }
12027
12028     /* This isn't really the old style, but it's close enough */
12029     if (gameInfo.resultDetails != NULL &&
12030         gameInfo.resultDetails[0] != NULLCHAR) {
12031         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12032                 gameInfo.resultDetails);
12033     } else {
12034         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12035     }
12036
12037     fclose(f);
12038     return TRUE;
12039 }
12040
12041 /* Save the current game to open file f and close the file */
12042 int
12043 SaveGame(f, dummy, dummy2)
12044      FILE *f;
12045      int dummy;
12046      char *dummy2;
12047 {
12048     if (gameMode == EditPosition) EditPositionDone(TRUE);
12049     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12050     if (appData.oldSaveStyle)
12051       return SaveGameOldStyle(f);
12052     else
12053       return SaveGamePGN(f);
12054 }
12055
12056 /* Save the current position to the given file */
12057 int
12058 SavePositionToFile(filename)
12059      char *filename;
12060 {
12061     FILE *f;
12062     char buf[MSG_SIZ];
12063
12064     if (strcmp(filename, "-") == 0) {
12065         return SavePosition(stdout, 0, NULL);
12066     } else {
12067         f = fopen(filename, "a");
12068         if (f == NULL) {
12069             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12070             DisplayError(buf, errno);
12071             return FALSE;
12072         } else {
12073             safeStrCpy(buf, lastMsg, MSG_SIZ);
12074             DisplayMessage(_("Waiting for access to save file"), "");
12075             flock(fileno(f), LOCK_EX); // [HGM] lock
12076             DisplayMessage(_("Saving position"), "");
12077             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12078             SavePosition(f, 0, NULL);
12079             DisplayMessage(buf, "");
12080             return TRUE;
12081         }
12082     }
12083 }
12084
12085 /* Save the current position to the given open file and close the file */
12086 int
12087 SavePosition(f, dummy, dummy2)
12088      FILE *f;
12089      int dummy;
12090      char *dummy2;
12091 {
12092     time_t tm;
12093     char *fen;
12094
12095     if (gameMode == EditPosition) EditPositionDone(TRUE);
12096     if (appData.oldSaveStyle) {
12097         tm = time((time_t *) NULL);
12098
12099         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12100         PrintOpponents(f);
12101         fprintf(f, "[--------------\n");
12102         PrintPosition(f, currentMove);
12103         fprintf(f, "--------------]\n");
12104     } else {
12105         fen = PositionToFEN(currentMove, NULL);
12106         fprintf(f, "%s\n", fen);
12107         free(fen);
12108     }
12109     fclose(f);
12110     return TRUE;
12111 }
12112
12113 void
12114 ReloadCmailMsgEvent(unregister)
12115      int unregister;
12116 {
12117 #if !WIN32
12118     static char *inFilename = NULL;
12119     static char *outFilename;
12120     int i;
12121     struct stat inbuf, outbuf;
12122     int status;
12123
12124     /* Any registered moves are unregistered if unregister is set, */
12125     /* i.e. invoked by the signal handler */
12126     if (unregister) {
12127         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12128             cmailMoveRegistered[i] = FALSE;
12129             if (cmailCommentList[i] != NULL) {
12130                 free(cmailCommentList[i]);
12131                 cmailCommentList[i] = NULL;
12132             }
12133         }
12134         nCmailMovesRegistered = 0;
12135     }
12136
12137     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12138         cmailResult[i] = CMAIL_NOT_RESULT;
12139     }
12140     nCmailResults = 0;
12141
12142     if (inFilename == NULL) {
12143         /* Because the filenames are static they only get malloced once  */
12144         /* and they never get freed                                      */
12145         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12146         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12147
12148         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12149         sprintf(outFilename, "%s.out", appData.cmailGameName);
12150     }
12151
12152     status = stat(outFilename, &outbuf);
12153     if (status < 0) {
12154         cmailMailedMove = FALSE;
12155     } else {
12156         status = stat(inFilename, &inbuf);
12157         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12158     }
12159
12160     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12161        counts the games, notes how each one terminated, etc.
12162
12163        It would be nice to remove this kludge and instead gather all
12164        the information while building the game list.  (And to keep it
12165        in the game list nodes instead of having a bunch of fixed-size
12166        parallel arrays.)  Note this will require getting each game's
12167        termination from the PGN tags, as the game list builder does
12168        not process the game moves.  --mann
12169        */
12170     cmailMsgLoaded = TRUE;
12171     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12172
12173     /* Load first game in the file or popup game menu */
12174     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12175
12176 #endif /* !WIN32 */
12177     return;
12178 }
12179
12180 int
12181 RegisterMove()
12182 {
12183     FILE *f;
12184     char string[MSG_SIZ];
12185
12186     if (   cmailMailedMove
12187         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12188         return TRUE;            /* Allow free viewing  */
12189     }
12190
12191     /* Unregister move to ensure that we don't leave RegisterMove        */
12192     /* with the move registered when the conditions for registering no   */
12193     /* longer hold                                                       */
12194     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12195         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12196         nCmailMovesRegistered --;
12197
12198         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12199           {
12200               free(cmailCommentList[lastLoadGameNumber - 1]);
12201               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12202           }
12203     }
12204
12205     if (cmailOldMove == -1) {
12206         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12207         return FALSE;
12208     }
12209
12210     if (currentMove > cmailOldMove + 1) {
12211         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12212         return FALSE;
12213     }
12214
12215     if (currentMove < cmailOldMove) {
12216         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12217         return FALSE;
12218     }
12219
12220     if (forwardMostMove > currentMove) {
12221         /* Silently truncate extra moves */
12222         TruncateGame();
12223     }
12224
12225     if (   (currentMove == cmailOldMove + 1)
12226         || (   (currentMove == cmailOldMove)
12227             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12228                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12229         if (gameInfo.result != GameUnfinished) {
12230             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12231         }
12232
12233         if (commentList[currentMove] != NULL) {
12234             cmailCommentList[lastLoadGameNumber - 1]
12235               = StrSave(commentList[currentMove]);
12236         }
12237         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12238
12239         if (appData.debugMode)
12240           fprintf(debugFP, "Saving %s for game %d\n",
12241                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12242
12243         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12244
12245         f = fopen(string, "w");
12246         if (appData.oldSaveStyle) {
12247             SaveGameOldStyle(f); /* also closes the file */
12248
12249             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12250             f = fopen(string, "w");
12251             SavePosition(f, 0, NULL); /* also closes the file */
12252         } else {
12253             fprintf(f, "{--------------\n");
12254             PrintPosition(f, currentMove);
12255             fprintf(f, "--------------}\n\n");
12256
12257             SaveGame(f, 0, NULL); /* also closes the file*/
12258         }
12259
12260         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12261         nCmailMovesRegistered ++;
12262     } else if (nCmailGames == 1) {
12263         DisplayError(_("You have not made a move yet"), 0);
12264         return FALSE;
12265     }
12266
12267     return TRUE;
12268 }
12269
12270 void
12271 MailMoveEvent()
12272 {
12273 #if !WIN32
12274     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12275     FILE *commandOutput;
12276     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12277     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12278     int nBuffers;
12279     int i;
12280     int archived;
12281     char *arcDir;
12282
12283     if (! cmailMsgLoaded) {
12284         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12285         return;
12286     }
12287
12288     if (nCmailGames == nCmailResults) {
12289         DisplayError(_("No unfinished games"), 0);
12290         return;
12291     }
12292
12293 #if CMAIL_PROHIBIT_REMAIL
12294     if (cmailMailedMove) {
12295       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);
12296         DisplayError(msg, 0);
12297         return;
12298     }
12299 #endif
12300
12301     if (! (cmailMailedMove || RegisterMove())) return;
12302
12303     if (   cmailMailedMove
12304         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12305       snprintf(string, MSG_SIZ, partCommandString,
12306                appData.debugMode ? " -v" : "", appData.cmailGameName);
12307         commandOutput = popen(string, "r");
12308
12309         if (commandOutput == NULL) {
12310             DisplayError(_("Failed to invoke cmail"), 0);
12311         } else {
12312             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12313                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12314             }
12315             if (nBuffers > 1) {
12316                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12317                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12318                 nBytes = MSG_SIZ - 1;
12319             } else {
12320                 (void) memcpy(msg, buffer, nBytes);
12321             }
12322             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12323
12324             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12325                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12326
12327                 archived = TRUE;
12328                 for (i = 0; i < nCmailGames; i ++) {
12329                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12330                         archived = FALSE;
12331                     }
12332                 }
12333                 if (   archived
12334                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12335                         != NULL)) {
12336                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12337                            arcDir,
12338                            appData.cmailGameName,
12339                            gameInfo.date);
12340                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12341                     cmailMsgLoaded = FALSE;
12342                 }
12343             }
12344
12345             DisplayInformation(msg);
12346             pclose(commandOutput);
12347         }
12348     } else {
12349         if ((*cmailMsg) != '\0') {
12350             DisplayInformation(cmailMsg);
12351         }
12352     }
12353
12354     return;
12355 #endif /* !WIN32 */
12356 }
12357
12358 char *
12359 CmailMsg()
12360 {
12361 #if WIN32
12362     return NULL;
12363 #else
12364     int  prependComma = 0;
12365     char number[5];
12366     char string[MSG_SIZ];       /* Space for game-list */
12367     int  i;
12368
12369     if (!cmailMsgLoaded) return "";
12370
12371     if (cmailMailedMove) {
12372       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12373     } else {
12374         /* Create a list of games left */
12375       snprintf(string, MSG_SIZ, "[");
12376         for (i = 0; i < nCmailGames; i ++) {
12377             if (! (   cmailMoveRegistered[i]
12378                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12379                 if (prependComma) {
12380                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12381                 } else {
12382                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12383                     prependComma = 1;
12384                 }
12385
12386                 strcat(string, number);
12387             }
12388         }
12389         strcat(string, "]");
12390
12391         if (nCmailMovesRegistered + nCmailResults == 0) {
12392             switch (nCmailGames) {
12393               case 1:
12394                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12395                 break;
12396
12397               case 2:
12398                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12399                 break;
12400
12401               default:
12402                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12403                          nCmailGames);
12404                 break;
12405             }
12406         } else {
12407             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12408               case 1:
12409                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12410                          string);
12411                 break;
12412
12413               case 0:
12414                 if (nCmailResults == nCmailGames) {
12415                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12416                 } else {
12417                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12418                 }
12419                 break;
12420
12421               default:
12422                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12423                          string);
12424             }
12425         }
12426     }
12427     return cmailMsg;
12428 #endif /* WIN32 */
12429 }
12430
12431 void
12432 ResetGameEvent()
12433 {
12434     if (gameMode == Training)
12435       SetTrainingModeOff();
12436
12437     Reset(TRUE, TRUE);
12438     cmailMsgLoaded = FALSE;
12439     if (appData.icsActive) {
12440       SendToICS(ics_prefix);
12441       SendToICS("refresh\n");
12442     }
12443 }
12444
12445 void
12446 ExitEvent(status)
12447      int status;
12448 {
12449     exiting++;
12450     if (exiting > 2) {
12451       /* Give up on clean exit */
12452       exit(status);
12453     }
12454     if (exiting > 1) {
12455       /* Keep trying for clean exit */
12456       return;
12457     }
12458
12459     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12460
12461     if (telnetISR != NULL) {
12462       RemoveInputSource(telnetISR);
12463     }
12464     if (icsPR != NoProc) {
12465       DestroyChildProcess(icsPR, TRUE);
12466     }
12467
12468     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12469     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12470
12471     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12472     /* make sure this other one finishes before killing it!                  */
12473     if(endingGame) { int count = 0;
12474         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12475         while(endingGame && count++ < 10) DoSleep(1);
12476         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12477     }
12478
12479     /* Kill off chess programs */
12480     if (first.pr != NoProc) {
12481         ExitAnalyzeMode();
12482
12483         DoSleep( appData.delayBeforeQuit );
12484         SendToProgram("quit\n", &first);
12485         DoSleep( appData.delayAfterQuit );
12486         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12487     }
12488     if (second.pr != NoProc) {
12489         DoSleep( appData.delayBeforeQuit );
12490         SendToProgram("quit\n", &second);
12491         DoSleep( appData.delayAfterQuit );
12492         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12493     }
12494     if (first.isr != NULL) {
12495         RemoveInputSource(first.isr);
12496     }
12497     if (second.isr != NULL) {
12498         RemoveInputSource(second.isr);
12499     }
12500
12501     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12502     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12503
12504     ShutDownFrontEnd();
12505     exit(status);
12506 }
12507
12508 void
12509 PauseEvent()
12510 {
12511     if (appData.debugMode)
12512         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12513     if (pausing) {
12514         pausing = FALSE;
12515         ModeHighlight();
12516         if (gameMode == MachinePlaysWhite ||
12517             gameMode == MachinePlaysBlack) {
12518             StartClocks();
12519         } else {
12520             DisplayBothClocks();
12521         }
12522         if (gameMode == PlayFromGameFile) {
12523             if (appData.timeDelay >= 0)
12524                 AutoPlayGameLoop();
12525         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12526             Reset(FALSE, TRUE);
12527             SendToICS(ics_prefix);
12528             SendToICS("refresh\n");
12529         } else if (currentMove < forwardMostMove) {
12530             ForwardInner(forwardMostMove);
12531         }
12532         pauseExamInvalid = FALSE;
12533     } else {
12534         switch (gameMode) {
12535           default:
12536             return;
12537           case IcsExamining:
12538             pauseExamForwardMostMove = forwardMostMove;
12539             pauseExamInvalid = FALSE;
12540             /* fall through */
12541           case IcsObserving:
12542           case IcsPlayingWhite:
12543           case IcsPlayingBlack:
12544             pausing = TRUE;
12545             ModeHighlight();
12546             return;
12547           case PlayFromGameFile:
12548             (void) StopLoadGameTimer();
12549             pausing = TRUE;
12550             ModeHighlight();
12551             break;
12552           case BeginningOfGame:
12553             if (appData.icsActive) return;
12554             /* else fall through */
12555           case MachinePlaysWhite:
12556           case MachinePlaysBlack:
12557           case TwoMachinesPlay:
12558             if (forwardMostMove == 0)
12559               return;           /* don't pause if no one has moved */
12560             if ((gameMode == MachinePlaysWhite &&
12561                  !WhiteOnMove(forwardMostMove)) ||
12562                 (gameMode == MachinePlaysBlack &&
12563                  WhiteOnMove(forwardMostMove))) {
12564                 StopClocks();
12565             }
12566             pausing = TRUE;
12567             ModeHighlight();
12568             break;
12569         }
12570     }
12571 }
12572
12573 void
12574 EditCommentEvent()
12575 {
12576     char title[MSG_SIZ];
12577
12578     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12579       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12580     } else {
12581       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12582                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12583                parseList[currentMove - 1]);
12584     }
12585
12586     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12587 }
12588
12589
12590 void
12591 EditTagsEvent()
12592 {
12593     char *tags = PGNTags(&gameInfo);
12594     bookUp = FALSE;
12595     EditTagsPopUp(tags, NULL);
12596     free(tags);
12597 }
12598
12599 void
12600 AnalyzeModeEvent()
12601 {
12602     if (appData.noChessProgram || gameMode == AnalyzeMode)
12603       return;
12604
12605     if (gameMode != AnalyzeFile) {
12606         if (!appData.icsEngineAnalyze) {
12607                EditGameEvent();
12608                if (gameMode != EditGame) return;
12609         }
12610         ResurrectChessProgram();
12611         SendToProgram("analyze\n", &first);
12612         first.analyzing = TRUE;
12613         /*first.maybeThinking = TRUE;*/
12614         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12615         EngineOutputPopUp();
12616     }
12617     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12618     pausing = FALSE;
12619     ModeHighlight();
12620     SetGameInfo();
12621
12622     StartAnalysisClock();
12623     GetTimeMark(&lastNodeCountTime);
12624     lastNodeCount = 0;
12625 }
12626
12627 void
12628 AnalyzeFileEvent()
12629 {
12630     if (appData.noChessProgram || gameMode == AnalyzeFile)
12631       return;
12632
12633     if (gameMode != AnalyzeMode) {
12634         EditGameEvent();
12635         if (gameMode != EditGame) return;
12636         ResurrectChessProgram();
12637         SendToProgram("analyze\n", &first);
12638         first.analyzing = TRUE;
12639         /*first.maybeThinking = TRUE;*/
12640         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12641         EngineOutputPopUp();
12642     }
12643     gameMode = AnalyzeFile;
12644     pausing = FALSE;
12645     ModeHighlight();
12646     SetGameInfo();
12647
12648     StartAnalysisClock();
12649     GetTimeMark(&lastNodeCountTime);
12650     lastNodeCount = 0;
12651 }
12652
12653 void
12654 MachineWhiteEvent()
12655 {
12656     char buf[MSG_SIZ];
12657     char *bookHit = NULL;
12658
12659     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12660       return;
12661
12662
12663     if (gameMode == PlayFromGameFile ||
12664         gameMode == TwoMachinesPlay  ||
12665         gameMode == Training         ||
12666         gameMode == AnalyzeMode      ||
12667         gameMode == EndOfGame)
12668         EditGameEvent();
12669
12670     if (gameMode == EditPosition)
12671         EditPositionDone(TRUE);
12672
12673     if (!WhiteOnMove(currentMove)) {
12674         DisplayError(_("It is not White's turn"), 0);
12675         return;
12676     }
12677
12678     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12679       ExitAnalyzeMode();
12680
12681     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12682         gameMode == AnalyzeFile)
12683         TruncateGame();
12684
12685     ResurrectChessProgram();    /* in case it isn't running */
12686     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12687         gameMode = MachinePlaysWhite;
12688         ResetClocks();
12689     } else
12690     gameMode = MachinePlaysWhite;
12691     pausing = FALSE;
12692     ModeHighlight();
12693     SetGameInfo();
12694     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12695     DisplayTitle(buf);
12696     if (first.sendName) {
12697       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12698       SendToProgram(buf, &first);
12699     }
12700     if (first.sendTime) {
12701       if (first.useColors) {
12702         SendToProgram("black\n", &first); /*gnu kludge*/
12703       }
12704       SendTimeRemaining(&first, TRUE);
12705     }
12706     if (first.useColors) {
12707       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12708     }
12709     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12710     SetMachineThinkingEnables();
12711     first.maybeThinking = TRUE;
12712     StartClocks();
12713     firstMove = FALSE;
12714
12715     if (appData.autoFlipView && !flipView) {
12716       flipView = !flipView;
12717       DrawPosition(FALSE, NULL);
12718       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12719     }
12720
12721     if(bookHit) { // [HGM] book: simulate book reply
12722         static char bookMove[MSG_SIZ]; // a bit generous?
12723
12724         programStats.nodes = programStats.depth = programStats.time =
12725         programStats.score = programStats.got_only_move = 0;
12726         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12727
12728         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12729         strcat(bookMove, bookHit);
12730         HandleMachineMove(bookMove, &first);
12731     }
12732 }
12733
12734 void
12735 MachineBlackEvent()
12736 {
12737   char buf[MSG_SIZ];
12738   char *bookHit = NULL;
12739
12740     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12741         return;
12742
12743
12744     if (gameMode == PlayFromGameFile ||
12745         gameMode == TwoMachinesPlay  ||
12746         gameMode == Training         ||
12747         gameMode == AnalyzeMode      ||
12748         gameMode == EndOfGame)
12749         EditGameEvent();
12750
12751     if (gameMode == EditPosition)
12752         EditPositionDone(TRUE);
12753
12754     if (WhiteOnMove(currentMove)) {
12755         DisplayError(_("It is not Black's turn"), 0);
12756         return;
12757     }
12758
12759     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12760       ExitAnalyzeMode();
12761
12762     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12763         gameMode == AnalyzeFile)
12764         TruncateGame();
12765
12766     ResurrectChessProgram();    /* in case it isn't running */
12767     gameMode = MachinePlaysBlack;
12768     pausing = FALSE;
12769     ModeHighlight();
12770     SetGameInfo();
12771     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12772     DisplayTitle(buf);
12773     if (first.sendName) {
12774       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12775       SendToProgram(buf, &first);
12776     }
12777     if (first.sendTime) {
12778       if (first.useColors) {
12779         SendToProgram("white\n", &first); /*gnu kludge*/
12780       }
12781       SendTimeRemaining(&first, FALSE);
12782     }
12783     if (first.useColors) {
12784       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12785     }
12786     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12787     SetMachineThinkingEnables();
12788     first.maybeThinking = TRUE;
12789     StartClocks();
12790
12791     if (appData.autoFlipView && flipView) {
12792       flipView = !flipView;
12793       DrawPosition(FALSE, NULL);
12794       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12795     }
12796     if(bookHit) { // [HGM] book: simulate book reply
12797         static char bookMove[MSG_SIZ]; // a bit generous?
12798
12799         programStats.nodes = programStats.depth = programStats.time =
12800         programStats.score = programStats.got_only_move = 0;
12801         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12802
12803         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12804         strcat(bookMove, bookHit);
12805         HandleMachineMove(bookMove, &first);
12806     }
12807 }
12808
12809
12810 void
12811 DisplayTwoMachinesTitle()
12812 {
12813     char buf[MSG_SIZ];
12814     if (appData.matchGames > 0) {
12815         if(appData.tourneyFile[0]) {
12816           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12817                    gameInfo.white, gameInfo.black,
12818                    nextGame+1, appData.matchGames+1,
12819                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12820         } else 
12821         if (first.twoMachinesColor[0] == 'w') {
12822           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12823                    gameInfo.white, gameInfo.black,
12824                    first.matchWins, second.matchWins,
12825                    matchGame - 1 - (first.matchWins + second.matchWins));
12826         } else {
12827           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12828                    gameInfo.white, gameInfo.black,
12829                    second.matchWins, first.matchWins,
12830                    matchGame - 1 - (first.matchWins + second.matchWins));
12831         }
12832     } else {
12833       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12834     }
12835     DisplayTitle(buf);
12836 }
12837
12838 void
12839 SettingsMenuIfReady()
12840 {
12841   if (second.lastPing != second.lastPong) {
12842     DisplayMessage("", _("Waiting for second chess program"));
12843     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12844     return;
12845   }
12846   ThawUI();
12847   DisplayMessage("", "");
12848   SettingsPopUp(&second);
12849 }
12850
12851 int
12852 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12853 {
12854     char buf[MSG_SIZ];
12855     if (cps->pr == NULL) {
12856         StartChessProgram(cps);
12857         if (cps->protocolVersion == 1) {
12858           retry();
12859         } else {
12860           /* kludge: allow timeout for initial "feature" command */
12861           FreezeUI();
12862           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12863           DisplayMessage("", buf);
12864           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12865         }
12866         return 1;
12867     }
12868     return 0;
12869 }
12870
12871 void
12872 TwoMachinesEvent P((void))
12873 {
12874     int i;
12875     char buf[MSG_SIZ];
12876     ChessProgramState *onmove;
12877     char *bookHit = NULL;
12878     static int stalling = 0;
12879     TimeMark now;
12880     long wait;
12881
12882     if (appData.noChessProgram) return;
12883
12884     switch (gameMode) {
12885       case TwoMachinesPlay:
12886         return;
12887       case MachinePlaysWhite:
12888       case MachinePlaysBlack:
12889         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12890             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12891             return;
12892         }
12893         /* fall through */
12894       case BeginningOfGame:
12895       case PlayFromGameFile:
12896       case EndOfGame:
12897         EditGameEvent();
12898         if (gameMode != EditGame) return;
12899         break;
12900       case EditPosition:
12901         EditPositionDone(TRUE);
12902         break;
12903       case AnalyzeMode:
12904       case AnalyzeFile:
12905         ExitAnalyzeMode();
12906         break;
12907       case EditGame:
12908       default:
12909         break;
12910     }
12911
12912 //    forwardMostMove = currentMove;
12913     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12914
12915     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12916
12917     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12918     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12919       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12920       return;
12921     }
12922     if(!stalling) {
12923       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12924       SendToProgram("force\n", &second);
12925       stalling = 1;
12926       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12927       return;
12928     }
12929     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12930     if(appData.matchPause>10000 || appData.matchPause<10)
12931                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12932     wait = SubtractTimeMarks(&now, &pauseStart);
12933     if(wait < appData.matchPause) {
12934         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12935         return;
12936     }
12937     stalling = 0;
12938     DisplayMessage("", "");
12939     if (startedFromSetupPosition) {
12940         SendBoard(&second, backwardMostMove);
12941     if (appData.debugMode) {
12942         fprintf(debugFP, "Two Machines\n");
12943     }
12944     }
12945     for (i = backwardMostMove; i < forwardMostMove; i++) {
12946         SendMoveToProgram(i, &second);
12947     }
12948
12949     gameMode = TwoMachinesPlay;
12950     pausing = FALSE;
12951     ModeHighlight(); // [HGM] logo: this triggers display update of logos
12952     SetGameInfo();
12953     DisplayTwoMachinesTitle();
12954     firstMove = TRUE;
12955     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12956         onmove = &first;
12957     } else {
12958         onmove = &second;
12959     }
12960     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12961     SendToProgram(first.computerString, &first);
12962     if (first.sendName) {
12963       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12964       SendToProgram(buf, &first);
12965     }
12966     SendToProgram(second.computerString, &second);
12967     if (second.sendName) {
12968       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12969       SendToProgram(buf, &second);
12970     }
12971
12972     ResetClocks();
12973     if (!first.sendTime || !second.sendTime) {
12974         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12975         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12976     }
12977     if (onmove->sendTime) {
12978       if (onmove->useColors) {
12979         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12980       }
12981       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12982     }
12983     if (onmove->useColors) {
12984       SendToProgram(onmove->twoMachinesColor, onmove);
12985     }
12986     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12987 //    SendToProgram("go\n", onmove);
12988     onmove->maybeThinking = TRUE;
12989     SetMachineThinkingEnables();
12990
12991     StartClocks();
12992
12993     if(bookHit) { // [HGM] book: simulate book reply
12994         static char bookMove[MSG_SIZ]; // a bit generous?
12995
12996         programStats.nodes = programStats.depth = programStats.time =
12997         programStats.score = programStats.got_only_move = 0;
12998         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12999
13000         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13001         strcat(bookMove, bookHit);
13002         savedMessage = bookMove; // args for deferred call
13003         savedState = onmove;
13004         ScheduleDelayedEvent(DeferredBookMove, 1);
13005     }
13006 }
13007
13008 void
13009 TrainingEvent()
13010 {
13011     if (gameMode == Training) {
13012       SetTrainingModeOff();
13013       gameMode = PlayFromGameFile;
13014       DisplayMessage("", _("Training mode off"));
13015     } else {
13016       gameMode = Training;
13017       animateTraining = appData.animate;
13018
13019       /* make sure we are not already at the end of the game */
13020       if (currentMove < forwardMostMove) {
13021         SetTrainingModeOn();
13022         DisplayMessage("", _("Training mode on"));
13023       } else {
13024         gameMode = PlayFromGameFile;
13025         DisplayError(_("Already at end of game"), 0);
13026       }
13027     }
13028     ModeHighlight();
13029 }
13030
13031 void
13032 IcsClientEvent()
13033 {
13034     if (!appData.icsActive) return;
13035     switch (gameMode) {
13036       case IcsPlayingWhite:
13037       case IcsPlayingBlack:
13038       case IcsObserving:
13039       case IcsIdle:
13040       case BeginningOfGame:
13041       case IcsExamining:
13042         return;
13043
13044       case EditGame:
13045         break;
13046
13047       case EditPosition:
13048         EditPositionDone(TRUE);
13049         break;
13050
13051       case AnalyzeMode:
13052       case AnalyzeFile:
13053         ExitAnalyzeMode();
13054         break;
13055
13056       default:
13057         EditGameEvent();
13058         break;
13059     }
13060
13061     gameMode = IcsIdle;
13062     ModeHighlight();
13063     return;
13064 }
13065
13066
13067 void
13068 EditGameEvent()
13069 {
13070     int i;
13071
13072     switch (gameMode) {
13073       case Training:
13074         SetTrainingModeOff();
13075         break;
13076       case MachinePlaysWhite:
13077       case MachinePlaysBlack:
13078       case BeginningOfGame:
13079         SendToProgram("force\n", &first);
13080         SetUserThinkingEnables();
13081         break;
13082       case PlayFromGameFile:
13083         (void) StopLoadGameTimer();
13084         if (gameFileFP != NULL) {
13085             gameFileFP = NULL;
13086         }
13087         break;
13088       case EditPosition:
13089         EditPositionDone(TRUE);
13090         break;
13091       case AnalyzeMode:
13092       case AnalyzeFile:
13093         ExitAnalyzeMode();
13094         SendToProgram("force\n", &first);
13095         break;
13096       case TwoMachinesPlay:
13097         GameEnds(EndOfFile, NULL, GE_PLAYER);
13098         ResurrectChessProgram();
13099         SetUserThinkingEnables();
13100         break;
13101       case EndOfGame:
13102         ResurrectChessProgram();
13103         break;
13104       case IcsPlayingBlack:
13105       case IcsPlayingWhite:
13106         DisplayError(_("Warning: You are still playing a game"), 0);
13107         break;
13108       case IcsObserving:
13109         DisplayError(_("Warning: You are still observing a game"), 0);
13110         break;
13111       case IcsExamining:
13112         DisplayError(_("Warning: You are still examining a game"), 0);
13113         break;
13114       case IcsIdle:
13115         break;
13116       case EditGame:
13117       default:
13118         return;
13119     }
13120
13121     pausing = FALSE;
13122     StopClocks();
13123     first.offeredDraw = second.offeredDraw = 0;
13124
13125     if (gameMode == PlayFromGameFile) {
13126         whiteTimeRemaining = timeRemaining[0][currentMove];
13127         blackTimeRemaining = timeRemaining[1][currentMove];
13128         DisplayTitle("");
13129     }
13130
13131     if (gameMode == MachinePlaysWhite ||
13132         gameMode == MachinePlaysBlack ||
13133         gameMode == TwoMachinesPlay ||
13134         gameMode == EndOfGame) {
13135         i = forwardMostMove;
13136         while (i > currentMove) {
13137             SendToProgram("undo\n", &first);
13138             i--;
13139         }
13140         whiteTimeRemaining = timeRemaining[0][currentMove];
13141         blackTimeRemaining = timeRemaining[1][currentMove];
13142         DisplayBothClocks();
13143         if (whiteFlag || blackFlag) {
13144             whiteFlag = blackFlag = 0;
13145         }
13146         DisplayTitle("");
13147     }
13148
13149     gameMode = EditGame;
13150     ModeHighlight();
13151     SetGameInfo();
13152 }
13153
13154
13155 void
13156 EditPositionEvent()
13157 {
13158     if (gameMode == EditPosition) {
13159         EditGameEvent();
13160         return;
13161     }
13162
13163     EditGameEvent();
13164     if (gameMode != EditGame) return;
13165
13166     gameMode = EditPosition;
13167     ModeHighlight();
13168     SetGameInfo();
13169     if (currentMove > 0)
13170       CopyBoard(boards[0], boards[currentMove]);
13171
13172     blackPlaysFirst = !WhiteOnMove(currentMove);
13173     ResetClocks();
13174     currentMove = forwardMostMove = backwardMostMove = 0;
13175     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13176     DisplayMove(-1);
13177 }
13178
13179 void
13180 ExitAnalyzeMode()
13181 {
13182     /* [DM] icsEngineAnalyze - possible call from other functions */
13183     if (appData.icsEngineAnalyze) {
13184         appData.icsEngineAnalyze = FALSE;
13185
13186         DisplayMessage("",_("Close ICS engine analyze..."));
13187     }
13188     if (first.analysisSupport && first.analyzing) {
13189       SendToProgram("exit\n", &first);
13190       first.analyzing = FALSE;
13191     }
13192     thinkOutput[0] = NULLCHAR;
13193 }
13194
13195 void
13196 EditPositionDone(Boolean fakeRights)
13197 {
13198     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13199
13200     startedFromSetupPosition = TRUE;
13201     InitChessProgram(&first, FALSE);
13202     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13203       boards[0][EP_STATUS] = EP_NONE;
13204       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13205     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13206         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13207         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13208       } else boards[0][CASTLING][2] = NoRights;
13209     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13210         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13211         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13212       } else boards[0][CASTLING][5] = NoRights;
13213     }
13214     SendToProgram("force\n", &first);
13215     if (blackPlaysFirst) {
13216         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13217         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13218         currentMove = forwardMostMove = backwardMostMove = 1;
13219         CopyBoard(boards[1], boards[0]);
13220     } else {
13221         currentMove = forwardMostMove = backwardMostMove = 0;
13222     }
13223     SendBoard(&first, forwardMostMove);
13224     if (appData.debugMode) {
13225         fprintf(debugFP, "EditPosDone\n");
13226     }
13227     DisplayTitle("");
13228     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13229     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13230     gameMode = EditGame;
13231     ModeHighlight();
13232     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13233     ClearHighlights(); /* [AS] */
13234 }
13235
13236 /* Pause for `ms' milliseconds */
13237 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13238 void
13239 TimeDelay(ms)
13240      long ms;
13241 {
13242     TimeMark m1, m2;
13243
13244     GetTimeMark(&m1);
13245     do {
13246         GetTimeMark(&m2);
13247     } while (SubtractTimeMarks(&m2, &m1) < ms);
13248 }
13249
13250 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13251 void
13252 SendMultiLineToICS(buf)
13253      char *buf;
13254 {
13255     char temp[MSG_SIZ+1], *p;
13256     int len;
13257
13258     len = strlen(buf);
13259     if (len > MSG_SIZ)
13260       len = MSG_SIZ;
13261
13262     strncpy(temp, buf, len);
13263     temp[len] = 0;
13264
13265     p = temp;
13266     while (*p) {
13267         if (*p == '\n' || *p == '\r')
13268           *p = ' ';
13269         ++p;
13270     }
13271
13272     strcat(temp, "\n");
13273     SendToICS(temp);
13274     SendToPlayer(temp, strlen(temp));
13275 }
13276
13277 void
13278 SetWhiteToPlayEvent()
13279 {
13280     if (gameMode == EditPosition) {
13281         blackPlaysFirst = FALSE;
13282         DisplayBothClocks();    /* works because currentMove is 0 */
13283     } else if (gameMode == IcsExamining) {
13284         SendToICS(ics_prefix);
13285         SendToICS("tomove white\n");
13286     }
13287 }
13288
13289 void
13290 SetBlackToPlayEvent()
13291 {
13292     if (gameMode == EditPosition) {
13293         blackPlaysFirst = TRUE;
13294         currentMove = 1;        /* kludge */
13295         DisplayBothClocks();
13296         currentMove = 0;
13297     } else if (gameMode == IcsExamining) {
13298         SendToICS(ics_prefix);
13299         SendToICS("tomove black\n");
13300     }
13301 }
13302
13303 void
13304 EditPositionMenuEvent(selection, x, y)
13305      ChessSquare selection;
13306      int x, y;
13307 {
13308     char buf[MSG_SIZ];
13309     ChessSquare piece = boards[0][y][x];
13310
13311     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13312
13313     switch (selection) {
13314       case ClearBoard:
13315         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13316             SendToICS(ics_prefix);
13317             SendToICS("bsetup clear\n");
13318         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13319             SendToICS(ics_prefix);
13320             SendToICS("clearboard\n");
13321         } else {
13322             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13323                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13324                 for (y = 0; y < BOARD_HEIGHT; y++) {
13325                     if (gameMode == IcsExamining) {
13326                         if (boards[currentMove][y][x] != EmptySquare) {
13327                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13328                                     AAA + x, ONE + y);
13329                             SendToICS(buf);
13330                         }
13331                     } else {
13332                         boards[0][y][x] = p;
13333                     }
13334                 }
13335             }
13336         }
13337         if (gameMode == EditPosition) {
13338             DrawPosition(FALSE, boards[0]);
13339         }
13340         break;
13341
13342       case WhitePlay:
13343         SetWhiteToPlayEvent();
13344         break;
13345
13346       case BlackPlay:
13347         SetBlackToPlayEvent();
13348         break;
13349
13350       case EmptySquare:
13351         if (gameMode == IcsExamining) {
13352             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13353             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13354             SendToICS(buf);
13355         } else {
13356             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13357                 if(x == BOARD_LEFT-2) {
13358                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13359                     boards[0][y][1] = 0;
13360                 } else
13361                 if(x == BOARD_RGHT+1) {
13362                     if(y >= gameInfo.holdingsSize) break;
13363                     boards[0][y][BOARD_WIDTH-2] = 0;
13364                 } else break;
13365             }
13366             boards[0][y][x] = EmptySquare;
13367             DrawPosition(FALSE, boards[0]);
13368         }
13369         break;
13370
13371       case PromotePiece:
13372         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13373            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13374             selection = (ChessSquare) (PROMOTED piece);
13375         } else if(piece == EmptySquare) selection = WhiteSilver;
13376         else selection = (ChessSquare)((int)piece - 1);
13377         goto defaultlabel;
13378
13379       case DemotePiece:
13380         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13381            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13382             selection = (ChessSquare) (DEMOTED piece);
13383         } else if(piece == EmptySquare) selection = BlackSilver;
13384         else selection = (ChessSquare)((int)piece + 1);
13385         goto defaultlabel;
13386
13387       case WhiteQueen:
13388       case BlackQueen:
13389         if(gameInfo.variant == VariantShatranj ||
13390            gameInfo.variant == VariantXiangqi  ||
13391            gameInfo.variant == VariantCourier  ||
13392            gameInfo.variant == VariantMakruk     )
13393             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13394         goto defaultlabel;
13395
13396       case WhiteKing:
13397       case BlackKing:
13398         if(gameInfo.variant == VariantXiangqi)
13399             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13400         if(gameInfo.variant == VariantKnightmate)
13401             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13402       default:
13403         defaultlabel:
13404         if (gameMode == IcsExamining) {
13405             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13406             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13407                      PieceToChar(selection), AAA + x, ONE + y);
13408             SendToICS(buf);
13409         } else {
13410             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13411                 int n;
13412                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13413                     n = PieceToNumber(selection - BlackPawn);
13414                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13415                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13416                     boards[0][BOARD_HEIGHT-1-n][1]++;
13417                 } else
13418                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13419                     n = PieceToNumber(selection);
13420                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13421                     boards[0][n][BOARD_WIDTH-1] = selection;
13422                     boards[0][n][BOARD_WIDTH-2]++;
13423                 }
13424             } else
13425             boards[0][y][x] = selection;
13426             DrawPosition(TRUE, boards[0]);
13427         }
13428         break;
13429     }
13430 }
13431
13432
13433 void
13434 DropMenuEvent(selection, x, y)
13435      ChessSquare selection;
13436      int x, y;
13437 {
13438     ChessMove moveType;
13439
13440     switch (gameMode) {
13441       case IcsPlayingWhite:
13442       case MachinePlaysBlack:
13443         if (!WhiteOnMove(currentMove)) {
13444             DisplayMoveError(_("It is Black's turn"));
13445             return;
13446         }
13447         moveType = WhiteDrop;
13448         break;
13449       case IcsPlayingBlack:
13450       case MachinePlaysWhite:
13451         if (WhiteOnMove(currentMove)) {
13452             DisplayMoveError(_("It is White's turn"));
13453             return;
13454         }
13455         moveType = BlackDrop;
13456         break;
13457       case EditGame:
13458         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13459         break;
13460       default:
13461         return;
13462     }
13463
13464     if (moveType == BlackDrop && selection < BlackPawn) {
13465       selection = (ChessSquare) ((int) selection
13466                                  + (int) BlackPawn - (int) WhitePawn);
13467     }
13468     if (boards[currentMove][y][x] != EmptySquare) {
13469         DisplayMoveError(_("That square is occupied"));
13470         return;
13471     }
13472
13473     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13474 }
13475
13476 void
13477 AcceptEvent()
13478 {
13479     /* Accept a pending offer of any kind from opponent */
13480
13481     if (appData.icsActive) {
13482         SendToICS(ics_prefix);
13483         SendToICS("accept\n");
13484     } else if (cmailMsgLoaded) {
13485         if (currentMove == cmailOldMove &&
13486             commentList[cmailOldMove] != NULL &&
13487             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13488                    "Black offers a draw" : "White offers a draw")) {
13489             TruncateGame();
13490             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13491             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13492         } else {
13493             DisplayError(_("There is no pending offer on this move"), 0);
13494             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13495         }
13496     } else {
13497         /* Not used for offers from chess program */
13498     }
13499 }
13500
13501 void
13502 DeclineEvent()
13503 {
13504     /* Decline a pending offer of any kind from opponent */
13505
13506     if (appData.icsActive) {
13507         SendToICS(ics_prefix);
13508         SendToICS("decline\n");
13509     } else if (cmailMsgLoaded) {
13510         if (currentMove == cmailOldMove &&
13511             commentList[cmailOldMove] != NULL &&
13512             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13513                    "Black offers a draw" : "White offers a draw")) {
13514 #ifdef NOTDEF
13515             AppendComment(cmailOldMove, "Draw declined", TRUE);
13516             DisplayComment(cmailOldMove - 1, "Draw declined");
13517 #endif /*NOTDEF*/
13518         } else {
13519             DisplayError(_("There is no pending offer on this move"), 0);
13520         }
13521     } else {
13522         /* Not used for offers from chess program */
13523     }
13524 }
13525
13526 void
13527 RematchEvent()
13528 {
13529     /* Issue ICS rematch command */
13530     if (appData.icsActive) {
13531         SendToICS(ics_prefix);
13532         SendToICS("rematch\n");
13533     }
13534 }
13535
13536 void
13537 CallFlagEvent()
13538 {
13539     /* Call your opponent's flag (claim a win on time) */
13540     if (appData.icsActive) {
13541         SendToICS(ics_prefix);
13542         SendToICS("flag\n");
13543     } else {
13544         switch (gameMode) {
13545           default:
13546             return;
13547           case MachinePlaysWhite:
13548             if (whiteFlag) {
13549                 if (blackFlag)
13550                   GameEnds(GameIsDrawn, "Both players ran out of time",
13551                            GE_PLAYER);
13552                 else
13553                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13554             } else {
13555                 DisplayError(_("Your opponent is not out of time"), 0);
13556             }
13557             break;
13558           case MachinePlaysBlack:
13559             if (blackFlag) {
13560                 if (whiteFlag)
13561                   GameEnds(GameIsDrawn, "Both players ran out of time",
13562                            GE_PLAYER);
13563                 else
13564                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13565             } else {
13566                 DisplayError(_("Your opponent is not out of time"), 0);
13567             }
13568             break;
13569         }
13570     }
13571 }
13572
13573 void
13574 ClockClick(int which)
13575 {       // [HGM] code moved to back-end from winboard.c
13576         if(which) { // black clock
13577           if (gameMode == EditPosition || gameMode == IcsExamining) {
13578             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13579             SetBlackToPlayEvent();
13580           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13581           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13582           } else if (shiftKey) {
13583             AdjustClock(which, -1);
13584           } else if (gameMode == IcsPlayingWhite ||
13585                      gameMode == MachinePlaysBlack) {
13586             CallFlagEvent();
13587           }
13588         } else { // white clock
13589           if (gameMode == EditPosition || gameMode == IcsExamining) {
13590             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13591             SetWhiteToPlayEvent();
13592           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13593           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13594           } else if (shiftKey) {
13595             AdjustClock(which, -1);
13596           } else if (gameMode == IcsPlayingBlack ||
13597                    gameMode == MachinePlaysWhite) {
13598             CallFlagEvent();
13599           }
13600         }
13601 }
13602
13603 void
13604 DrawEvent()
13605 {
13606     /* Offer draw or accept pending draw offer from opponent */
13607
13608     if (appData.icsActive) {
13609         /* Note: tournament rules require draw offers to be
13610            made after you make your move but before you punch
13611            your clock.  Currently ICS doesn't let you do that;
13612            instead, you immediately punch your clock after making
13613            a move, but you can offer a draw at any time. */
13614
13615         SendToICS(ics_prefix);
13616         SendToICS("draw\n");
13617         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13618     } else if (cmailMsgLoaded) {
13619         if (currentMove == cmailOldMove &&
13620             commentList[cmailOldMove] != NULL &&
13621             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13622                    "Black offers a draw" : "White offers a draw")) {
13623             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13624             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13625         } else if (currentMove == cmailOldMove + 1) {
13626             char *offer = WhiteOnMove(cmailOldMove) ?
13627               "White offers a draw" : "Black offers a draw";
13628             AppendComment(currentMove, offer, TRUE);
13629             DisplayComment(currentMove - 1, offer);
13630             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13631         } else {
13632             DisplayError(_("You must make your move before offering a draw"), 0);
13633             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13634         }
13635     } else if (first.offeredDraw) {
13636         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13637     } else {
13638         if (first.sendDrawOffers) {
13639             SendToProgram("draw\n", &first);
13640             userOfferedDraw = TRUE;
13641         }
13642     }
13643 }
13644
13645 void
13646 AdjournEvent()
13647 {
13648     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13649
13650     if (appData.icsActive) {
13651         SendToICS(ics_prefix);
13652         SendToICS("adjourn\n");
13653     } else {
13654         /* Currently GNU Chess doesn't offer or accept Adjourns */
13655     }
13656 }
13657
13658
13659 void
13660 AbortEvent()
13661 {
13662     /* Offer Abort or accept pending Abort offer from opponent */
13663
13664     if (appData.icsActive) {
13665         SendToICS(ics_prefix);
13666         SendToICS("abort\n");
13667     } else {
13668         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13669     }
13670 }
13671
13672 void
13673 ResignEvent()
13674 {
13675     /* Resign.  You can do this even if it's not your turn. */
13676
13677     if (appData.icsActive) {
13678         SendToICS(ics_prefix);
13679         SendToICS("resign\n");
13680     } else {
13681         switch (gameMode) {
13682           case MachinePlaysWhite:
13683             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13684             break;
13685           case MachinePlaysBlack:
13686             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13687             break;
13688           case EditGame:
13689             if (cmailMsgLoaded) {
13690                 TruncateGame();
13691                 if (WhiteOnMove(cmailOldMove)) {
13692                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13693                 } else {
13694                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13695                 }
13696                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13697             }
13698             break;
13699           default:
13700             break;
13701         }
13702     }
13703 }
13704
13705
13706 void
13707 StopObservingEvent()
13708 {
13709     /* Stop observing current games */
13710     SendToICS(ics_prefix);
13711     SendToICS("unobserve\n");
13712 }
13713
13714 void
13715 StopExaminingEvent()
13716 {
13717     /* Stop observing current game */
13718     SendToICS(ics_prefix);
13719     SendToICS("unexamine\n");
13720 }
13721
13722 void
13723 ForwardInner(target)
13724      int target;
13725 {
13726     int limit;
13727
13728     if (appData.debugMode)
13729         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13730                 target, currentMove, forwardMostMove);
13731
13732     if (gameMode == EditPosition)
13733       return;
13734
13735     if (gameMode == PlayFromGameFile && !pausing)
13736       PauseEvent();
13737
13738     if (gameMode == IcsExamining && pausing)
13739       limit = pauseExamForwardMostMove;
13740     else
13741       limit = forwardMostMove;
13742
13743     if (target > limit) target = limit;
13744
13745     if (target > 0 && moveList[target - 1][0]) {
13746         int fromX, fromY, toX, toY;
13747         toX = moveList[target - 1][2] - AAA;
13748         toY = moveList[target - 1][3] - ONE;
13749         if (moveList[target - 1][1] == '@') {
13750             if (appData.highlightLastMove) {
13751                 SetHighlights(-1, -1, toX, toY);
13752             }
13753         } else {
13754             fromX = moveList[target - 1][0] - AAA;
13755             fromY = moveList[target - 1][1] - ONE;
13756             if (target == currentMove + 1) {
13757                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13758             }
13759             if (appData.highlightLastMove) {
13760                 SetHighlights(fromX, fromY, toX, toY);
13761             }
13762         }
13763     }
13764     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13765         gameMode == Training || gameMode == PlayFromGameFile ||
13766         gameMode == AnalyzeFile) {
13767         while (currentMove < target) {
13768             SendMoveToProgram(currentMove++, &first);
13769         }
13770     } else {
13771         currentMove = target;
13772     }
13773
13774     if (gameMode == EditGame || gameMode == EndOfGame) {
13775         whiteTimeRemaining = timeRemaining[0][currentMove];
13776         blackTimeRemaining = timeRemaining[1][currentMove];
13777     }
13778     DisplayBothClocks();
13779     DisplayMove(currentMove - 1);
13780     DrawPosition(FALSE, boards[currentMove]);
13781     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13782     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13783         DisplayComment(currentMove - 1, commentList[currentMove]);
13784     }
13785     DisplayBook(currentMove);
13786 }
13787
13788
13789 void
13790 ForwardEvent()
13791 {
13792     if (gameMode == IcsExamining && !pausing) {
13793         SendToICS(ics_prefix);
13794         SendToICS("forward\n");
13795     } else {
13796         ForwardInner(currentMove + 1);
13797     }
13798 }
13799
13800 void
13801 ToEndEvent()
13802 {
13803     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13804         /* to optimze, we temporarily turn off analysis mode while we feed
13805          * the remaining moves to the engine. Otherwise we get analysis output
13806          * after each move.
13807          */
13808         if (first.analysisSupport) {
13809           SendToProgram("exit\nforce\n", &first);
13810           first.analyzing = FALSE;
13811         }
13812     }
13813
13814     if (gameMode == IcsExamining && !pausing) {
13815         SendToICS(ics_prefix);
13816         SendToICS("forward 999999\n");
13817     } else {
13818         ForwardInner(forwardMostMove);
13819     }
13820
13821     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13822         /* we have fed all the moves, so reactivate analysis mode */
13823         SendToProgram("analyze\n", &first);
13824         first.analyzing = TRUE;
13825         /*first.maybeThinking = TRUE;*/
13826         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13827     }
13828 }
13829
13830 void
13831 BackwardInner(target)
13832      int target;
13833 {
13834     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13835
13836     if (appData.debugMode)
13837         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13838                 target, currentMove, forwardMostMove);
13839
13840     if (gameMode == EditPosition) return;
13841     if (currentMove <= backwardMostMove) {
13842         ClearHighlights();
13843         DrawPosition(full_redraw, boards[currentMove]);
13844         return;
13845     }
13846     if (gameMode == PlayFromGameFile && !pausing)
13847       PauseEvent();
13848
13849     if (moveList[target][0]) {
13850         int fromX, fromY, toX, toY;
13851         toX = moveList[target][2] - AAA;
13852         toY = moveList[target][3] - ONE;
13853         if (moveList[target][1] == '@') {
13854             if (appData.highlightLastMove) {
13855                 SetHighlights(-1, -1, toX, toY);
13856             }
13857         } else {
13858             fromX = moveList[target][0] - AAA;
13859             fromY = moveList[target][1] - ONE;
13860             if (target == currentMove - 1) {
13861                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13862             }
13863             if (appData.highlightLastMove) {
13864                 SetHighlights(fromX, fromY, toX, toY);
13865             }
13866         }
13867     }
13868     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13869         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13870         while (currentMove > target) {
13871             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
13872                 // null move cannot be undone. Reload program with move history before it.
13873                 int i;
13874                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
13875                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
13876                 }
13877                 SendBoard(&first, i); 
13878                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
13879                 break;
13880             }
13881             SendToProgram("undo\n", &first);
13882             currentMove--;
13883         }
13884     } else {
13885         currentMove = target;
13886     }
13887
13888     if (gameMode == EditGame || gameMode == EndOfGame) {
13889         whiteTimeRemaining = timeRemaining[0][currentMove];
13890         blackTimeRemaining = timeRemaining[1][currentMove];
13891     }
13892     DisplayBothClocks();
13893     DisplayMove(currentMove - 1);
13894     DrawPosition(full_redraw, boards[currentMove]);
13895     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13896     // [HGM] PV info: routine tests if comment empty
13897     DisplayComment(currentMove - 1, commentList[currentMove]);
13898     DisplayBook(currentMove);
13899 }
13900
13901 void
13902 BackwardEvent()
13903 {
13904     if (gameMode == IcsExamining && !pausing) {
13905         SendToICS(ics_prefix);
13906         SendToICS("backward\n");
13907     } else {
13908         BackwardInner(currentMove - 1);
13909     }
13910 }
13911
13912 void
13913 ToStartEvent()
13914 {
13915     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13916         /* to optimize, we temporarily turn off analysis mode while we undo
13917          * all the moves. Otherwise we get analysis output after each undo.
13918          */
13919         if (first.analysisSupport) {
13920           SendToProgram("exit\nforce\n", &first);
13921           first.analyzing = FALSE;
13922         }
13923     }
13924
13925     if (gameMode == IcsExamining && !pausing) {
13926         SendToICS(ics_prefix);
13927         SendToICS("backward 999999\n");
13928     } else {
13929         BackwardInner(backwardMostMove);
13930     }
13931
13932     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13933         /* we have fed all the moves, so reactivate analysis mode */
13934         SendToProgram("analyze\n", &first);
13935         first.analyzing = TRUE;
13936         /*first.maybeThinking = TRUE;*/
13937         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13938     }
13939 }
13940
13941 void
13942 ToNrEvent(int to)
13943 {
13944   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13945   if (to >= forwardMostMove) to = forwardMostMove;
13946   if (to <= backwardMostMove) to = backwardMostMove;
13947   if (to < currentMove) {
13948     BackwardInner(to);
13949   } else {
13950     ForwardInner(to);
13951   }
13952 }
13953
13954 void
13955 RevertEvent(Boolean annotate)
13956 {
13957     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13958         return;
13959     }
13960     if (gameMode != IcsExamining) {
13961         DisplayError(_("You are not examining a game"), 0);
13962         return;
13963     }
13964     if (pausing) {
13965         DisplayError(_("You can't revert while pausing"), 0);
13966         return;
13967     }
13968     SendToICS(ics_prefix);
13969     SendToICS("revert\n");
13970 }
13971
13972 void
13973 RetractMoveEvent()
13974 {
13975     switch (gameMode) {
13976       case MachinePlaysWhite:
13977       case MachinePlaysBlack:
13978         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13979             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13980             return;
13981         }
13982         if (forwardMostMove < 2) return;
13983         currentMove = forwardMostMove = forwardMostMove - 2;
13984         whiteTimeRemaining = timeRemaining[0][currentMove];
13985         blackTimeRemaining = timeRemaining[1][currentMove];
13986         DisplayBothClocks();
13987         DisplayMove(currentMove - 1);
13988         ClearHighlights();/*!! could figure this out*/
13989         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13990         SendToProgram("remove\n", &first);
13991         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13992         break;
13993
13994       case BeginningOfGame:
13995       default:
13996         break;
13997
13998       case IcsPlayingWhite:
13999       case IcsPlayingBlack:
14000         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14001             SendToICS(ics_prefix);
14002             SendToICS("takeback 2\n");
14003         } else {
14004             SendToICS(ics_prefix);
14005             SendToICS("takeback 1\n");
14006         }
14007         break;
14008     }
14009 }
14010
14011 void
14012 MoveNowEvent()
14013 {
14014     ChessProgramState *cps;
14015
14016     switch (gameMode) {
14017       case MachinePlaysWhite:
14018         if (!WhiteOnMove(forwardMostMove)) {
14019             DisplayError(_("It is your turn"), 0);
14020             return;
14021         }
14022         cps = &first;
14023         break;
14024       case MachinePlaysBlack:
14025         if (WhiteOnMove(forwardMostMove)) {
14026             DisplayError(_("It is your turn"), 0);
14027             return;
14028         }
14029         cps = &first;
14030         break;
14031       case TwoMachinesPlay:
14032         if (WhiteOnMove(forwardMostMove) ==
14033             (first.twoMachinesColor[0] == 'w')) {
14034             cps = &first;
14035         } else {
14036             cps = &second;
14037         }
14038         break;
14039       case BeginningOfGame:
14040       default:
14041         return;
14042     }
14043     SendToProgram("?\n", cps);
14044 }
14045
14046 void
14047 TruncateGameEvent()
14048 {
14049     EditGameEvent();
14050     if (gameMode != EditGame) return;
14051     TruncateGame();
14052 }
14053
14054 void
14055 TruncateGame()
14056 {
14057     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14058     if (forwardMostMove > currentMove) {
14059         if (gameInfo.resultDetails != NULL) {
14060             free(gameInfo.resultDetails);
14061             gameInfo.resultDetails = NULL;
14062             gameInfo.result = GameUnfinished;
14063         }
14064         forwardMostMove = currentMove;
14065         HistorySet(parseList, backwardMostMove, forwardMostMove,
14066                    currentMove-1);
14067     }
14068 }
14069
14070 void
14071 HintEvent()
14072 {
14073     if (appData.noChessProgram) return;
14074     switch (gameMode) {
14075       case MachinePlaysWhite:
14076         if (WhiteOnMove(forwardMostMove)) {
14077             DisplayError(_("Wait until your turn"), 0);
14078             return;
14079         }
14080         break;
14081       case BeginningOfGame:
14082       case MachinePlaysBlack:
14083         if (!WhiteOnMove(forwardMostMove)) {
14084             DisplayError(_("Wait until your turn"), 0);
14085             return;
14086         }
14087         break;
14088       default:
14089         DisplayError(_("No hint available"), 0);
14090         return;
14091     }
14092     SendToProgram("hint\n", &first);
14093     hintRequested = TRUE;
14094 }
14095
14096 void
14097 BookEvent()
14098 {
14099     if (appData.noChessProgram) return;
14100     switch (gameMode) {
14101       case MachinePlaysWhite:
14102         if (WhiteOnMove(forwardMostMove)) {
14103             DisplayError(_("Wait until your turn"), 0);
14104             return;
14105         }
14106         break;
14107       case BeginningOfGame:
14108       case MachinePlaysBlack:
14109         if (!WhiteOnMove(forwardMostMove)) {
14110             DisplayError(_("Wait until your turn"), 0);
14111             return;
14112         }
14113         break;
14114       case EditPosition:
14115         EditPositionDone(TRUE);
14116         break;
14117       case TwoMachinesPlay:
14118         return;
14119       default:
14120         break;
14121     }
14122     SendToProgram("bk\n", &first);
14123     bookOutput[0] = NULLCHAR;
14124     bookRequested = TRUE;
14125 }
14126
14127 void
14128 AboutGameEvent()
14129 {
14130     char *tags = PGNTags(&gameInfo);
14131     TagsPopUp(tags, CmailMsg());
14132     free(tags);
14133 }
14134
14135 /* end button procedures */
14136
14137 void
14138 PrintPosition(fp, move)
14139      FILE *fp;
14140      int move;
14141 {
14142     int i, j;
14143
14144     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14145         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14146             char c = PieceToChar(boards[move][i][j]);
14147             fputc(c == 'x' ? '.' : c, fp);
14148             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14149         }
14150     }
14151     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14152       fprintf(fp, "white to play\n");
14153     else
14154       fprintf(fp, "black to play\n");
14155 }
14156
14157 void
14158 PrintOpponents(fp)
14159      FILE *fp;
14160 {
14161     if (gameInfo.white != NULL) {
14162         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14163     } else {
14164         fprintf(fp, "\n");
14165     }
14166 }
14167
14168 /* Find last component of program's own name, using some heuristics */
14169 void
14170 TidyProgramName(prog, host, buf)
14171      char *prog, *host, buf[MSG_SIZ];
14172 {
14173     char *p, *q;
14174     int local = (strcmp(host, "localhost") == 0);
14175     while (!local && (p = strchr(prog, ';')) != NULL) {
14176         p++;
14177         while (*p == ' ') p++;
14178         prog = p;
14179     }
14180     if (*prog == '"' || *prog == '\'') {
14181         q = strchr(prog + 1, *prog);
14182     } else {
14183         q = strchr(prog, ' ');
14184     }
14185     if (q == NULL) q = prog + strlen(prog);
14186     p = q;
14187     while (p >= prog && *p != '/' && *p != '\\') p--;
14188     p++;
14189     if(p == prog && *p == '"') p++;
14190     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14191     memcpy(buf, p, q - p);
14192     buf[q - p] = NULLCHAR;
14193     if (!local) {
14194         strcat(buf, "@");
14195         strcat(buf, host);
14196     }
14197 }
14198
14199 char *
14200 TimeControlTagValue()
14201 {
14202     char buf[MSG_SIZ];
14203     if (!appData.clockMode) {
14204       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14205     } else if (movesPerSession > 0) {
14206       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14207     } else if (timeIncrement == 0) {
14208       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14209     } else {
14210       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14211     }
14212     return StrSave(buf);
14213 }
14214
14215 void
14216 SetGameInfo()
14217 {
14218     /* This routine is used only for certain modes */
14219     VariantClass v = gameInfo.variant;
14220     ChessMove r = GameUnfinished;
14221     char *p = NULL;
14222
14223     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14224         r = gameInfo.result;
14225         p = gameInfo.resultDetails;
14226         gameInfo.resultDetails = NULL;
14227     }
14228     ClearGameInfo(&gameInfo);
14229     gameInfo.variant = v;
14230
14231     switch (gameMode) {
14232       case MachinePlaysWhite:
14233         gameInfo.event = StrSave( appData.pgnEventHeader );
14234         gameInfo.site = StrSave(HostName());
14235         gameInfo.date = PGNDate();
14236         gameInfo.round = StrSave("-");
14237         gameInfo.white = StrSave(first.tidy);
14238         gameInfo.black = StrSave(UserName());
14239         gameInfo.timeControl = TimeControlTagValue();
14240         break;
14241
14242       case MachinePlaysBlack:
14243         gameInfo.event = StrSave( appData.pgnEventHeader );
14244         gameInfo.site = StrSave(HostName());
14245         gameInfo.date = PGNDate();
14246         gameInfo.round = StrSave("-");
14247         gameInfo.white = StrSave(UserName());
14248         gameInfo.black = StrSave(first.tidy);
14249         gameInfo.timeControl = TimeControlTagValue();
14250         break;
14251
14252       case TwoMachinesPlay:
14253         gameInfo.event = StrSave( appData.pgnEventHeader );
14254         gameInfo.site = StrSave(HostName());
14255         gameInfo.date = PGNDate();
14256         if (roundNr > 0) {
14257             char buf[MSG_SIZ];
14258             snprintf(buf, MSG_SIZ, "%d", roundNr);
14259             gameInfo.round = StrSave(buf);
14260         } else {
14261             gameInfo.round = StrSave("-");
14262         }
14263         if (first.twoMachinesColor[0] == 'w') {
14264             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14265             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14266         } else {
14267             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14268             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14269         }
14270         gameInfo.timeControl = TimeControlTagValue();
14271         break;
14272
14273       case EditGame:
14274         gameInfo.event = StrSave("Edited game");
14275         gameInfo.site = StrSave(HostName());
14276         gameInfo.date = PGNDate();
14277         gameInfo.round = StrSave("-");
14278         gameInfo.white = StrSave("-");
14279         gameInfo.black = StrSave("-");
14280         gameInfo.result = r;
14281         gameInfo.resultDetails = p;
14282         break;
14283
14284       case EditPosition:
14285         gameInfo.event = StrSave("Edited position");
14286         gameInfo.site = StrSave(HostName());
14287         gameInfo.date = PGNDate();
14288         gameInfo.round = StrSave("-");
14289         gameInfo.white = StrSave("-");
14290         gameInfo.black = StrSave("-");
14291         break;
14292
14293       case IcsPlayingWhite:
14294       case IcsPlayingBlack:
14295       case IcsObserving:
14296       case IcsExamining:
14297         break;
14298
14299       case PlayFromGameFile:
14300         gameInfo.event = StrSave("Game from non-PGN file");
14301         gameInfo.site = StrSave(HostName());
14302         gameInfo.date = PGNDate();
14303         gameInfo.round = StrSave("-");
14304         gameInfo.white = StrSave("?");
14305         gameInfo.black = StrSave("?");
14306         break;
14307
14308       default:
14309         break;
14310     }
14311 }
14312
14313 void
14314 ReplaceComment(index, text)
14315      int index;
14316      char *text;
14317 {
14318     int len;
14319     char *p;
14320     float score;
14321
14322     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14323        pvInfoList[index-1].depth == len &&
14324        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14325        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14326     while (*text == '\n') text++;
14327     len = strlen(text);
14328     while (len > 0 && text[len - 1] == '\n') len--;
14329
14330     if (commentList[index] != NULL)
14331       free(commentList[index]);
14332
14333     if (len == 0) {
14334         commentList[index] = NULL;
14335         return;
14336     }
14337   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14338       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14339       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14340     commentList[index] = (char *) malloc(len + 2);
14341     strncpy(commentList[index], text, len);
14342     commentList[index][len] = '\n';
14343     commentList[index][len + 1] = NULLCHAR;
14344   } else {
14345     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14346     char *p;
14347     commentList[index] = (char *) malloc(len + 7);
14348     safeStrCpy(commentList[index], "{\n", 3);
14349     safeStrCpy(commentList[index]+2, text, len+1);
14350     commentList[index][len+2] = NULLCHAR;
14351     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14352     strcat(commentList[index], "\n}\n");
14353   }
14354 }
14355
14356 void
14357 CrushCRs(text)
14358      char *text;
14359 {
14360   char *p = text;
14361   char *q = text;
14362   char ch;
14363
14364   do {
14365     ch = *p++;
14366     if (ch == '\r') continue;
14367     *q++ = ch;
14368   } while (ch != '\0');
14369 }
14370
14371 void
14372 AppendComment(index, text, addBraces)
14373      int index;
14374      char *text;
14375      Boolean addBraces; // [HGM] braces: tells if we should add {}
14376 {
14377     int oldlen, len;
14378     char *old;
14379
14380 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14381     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14382
14383     CrushCRs(text);
14384     while (*text == '\n') text++;
14385     len = strlen(text);
14386     while (len > 0 && text[len - 1] == '\n') len--;
14387
14388     if (len == 0) return;
14389
14390     if (commentList[index] != NULL) {
14391         old = commentList[index];
14392         oldlen = strlen(old);
14393         while(commentList[index][oldlen-1] ==  '\n')
14394           commentList[index][--oldlen] = NULLCHAR;
14395         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14396         safeStrCpy(commentList[index], old, oldlen + len + 6);
14397         free(old);
14398         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14399         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14400           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14401           while (*text == '\n') { text++; len--; }
14402           commentList[index][--oldlen] = NULLCHAR;
14403       }
14404         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14405         else          strcat(commentList[index], "\n");
14406         strcat(commentList[index], text);
14407         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14408         else          strcat(commentList[index], "\n");
14409     } else {
14410         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14411         if(addBraces)
14412           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14413         else commentList[index][0] = NULLCHAR;
14414         strcat(commentList[index], text);
14415         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14416         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14417     }
14418 }
14419
14420 static char * FindStr( char * text, char * sub_text )
14421 {
14422     char * result = strstr( text, sub_text );
14423
14424     if( result != NULL ) {
14425         result += strlen( sub_text );
14426     }
14427
14428     return result;
14429 }
14430
14431 /* [AS] Try to extract PV info from PGN comment */
14432 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14433 char *GetInfoFromComment( int index, char * text )
14434 {
14435     char * sep = text, *p;
14436
14437     if( text != NULL && index > 0 ) {
14438         int score = 0;
14439         int depth = 0;
14440         int time = -1, sec = 0, deci;
14441         char * s_eval = FindStr( text, "[%eval " );
14442         char * s_emt = FindStr( text, "[%emt " );
14443
14444         if( s_eval != NULL || s_emt != NULL ) {
14445             /* New style */
14446             char delim;
14447
14448             if( s_eval != NULL ) {
14449                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14450                     return text;
14451                 }
14452
14453                 if( delim != ']' ) {
14454                     return text;
14455                 }
14456             }
14457
14458             if( s_emt != NULL ) {
14459             }
14460                 return text;
14461         }
14462         else {
14463             /* We expect something like: [+|-]nnn.nn/dd */
14464             int score_lo = 0;
14465
14466             if(*text != '{') return text; // [HGM] braces: must be normal comment
14467
14468             sep = strchr( text, '/' );
14469             if( sep == NULL || sep < (text+4) ) {
14470                 return text;
14471             }
14472
14473             p = text;
14474             if(p[1] == '(') { // comment starts with PV
14475                p = strchr(p, ')'); // locate end of PV
14476                if(p == NULL || sep < p+5) return text;
14477                // at this point we have something like "{(.*) +0.23/6 ..."
14478                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14479                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14480                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14481             }
14482             time = -1; sec = -1; deci = -1;
14483             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14484                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14485                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14486                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14487                 return text;
14488             }
14489
14490             if( score_lo < 0 || score_lo >= 100 ) {
14491                 return text;
14492             }
14493
14494             if(sec >= 0) time = 600*time + 10*sec; else
14495             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14496
14497             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14498
14499             /* [HGM] PV time: now locate end of PV info */
14500             while( *++sep >= '0' && *sep <= '9'); // strip depth
14501             if(time >= 0)
14502             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14503             if(sec >= 0)
14504             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14505             if(deci >= 0)
14506             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14507             while(*sep == ' ') sep++;
14508         }
14509
14510         if( depth <= 0 ) {
14511             return text;
14512         }
14513
14514         if( time < 0 ) {
14515             time = -1;
14516         }
14517
14518         pvInfoList[index-1].depth = depth;
14519         pvInfoList[index-1].score = score;
14520         pvInfoList[index-1].time  = 10*time; // centi-sec
14521         if(*sep == '}') *sep = 0; else *--sep = '{';
14522         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14523     }
14524     return sep;
14525 }
14526
14527 void
14528 SendToProgram(message, cps)
14529      char *message;
14530      ChessProgramState *cps;
14531 {
14532     int count, outCount, error;
14533     char buf[MSG_SIZ];
14534
14535     if (cps->pr == NULL) return;
14536     Attention(cps);
14537
14538     if (appData.debugMode) {
14539         TimeMark now;
14540         GetTimeMark(&now);
14541         fprintf(debugFP, "%ld >%-6s: %s",
14542                 SubtractTimeMarks(&now, &programStartTime),
14543                 cps->which, message);
14544     }
14545
14546     count = strlen(message);
14547     outCount = OutputToProcess(cps->pr, message, count, &error);
14548     if (outCount < count && !exiting
14549                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14550       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14551       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14552         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14553             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14554                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14555                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14556                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14557             } else {
14558                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14559                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14560                 gameInfo.result = res;
14561             }
14562             gameInfo.resultDetails = StrSave(buf);
14563         }
14564         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14565         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14566     }
14567 }
14568
14569 void
14570 ReceiveFromProgram(isr, closure, message, count, error)
14571      InputSourceRef isr;
14572      VOIDSTAR closure;
14573      char *message;
14574      int count;
14575      int error;
14576 {
14577     char *end_str;
14578     char buf[MSG_SIZ];
14579     ChessProgramState *cps = (ChessProgramState *)closure;
14580
14581     if (isr != cps->isr) return; /* Killed intentionally */
14582     if (count <= 0) {
14583         if (count == 0) {
14584             RemoveInputSource(cps->isr);
14585             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14586             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14587                     _(cps->which), cps->program);
14588         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14589                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14590                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14591                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14592                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14593                 } else {
14594                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14595                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14596                     gameInfo.result = res;
14597                 }
14598                 gameInfo.resultDetails = StrSave(buf);
14599             }
14600             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14601             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14602         } else {
14603             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14604                     _(cps->which), cps->program);
14605             RemoveInputSource(cps->isr);
14606
14607             /* [AS] Program is misbehaving badly... kill it */
14608             if( count == -2 ) {
14609                 DestroyChildProcess( cps->pr, 9 );
14610                 cps->pr = NoProc;
14611             }
14612
14613             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14614         }
14615         return;
14616     }
14617
14618     if ((end_str = strchr(message, '\r')) != NULL)
14619       *end_str = NULLCHAR;
14620     if ((end_str = strchr(message, '\n')) != NULL)
14621       *end_str = NULLCHAR;
14622
14623     if (appData.debugMode) {
14624         TimeMark now; int print = 1;
14625         char *quote = ""; char c; int i;
14626
14627         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14628                 char start = message[0];
14629                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14630                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14631                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14632                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14633                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14634                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14635                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14636                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14637                    sscanf(message, "hint: %c", &c)!=1 && 
14638                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14639                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14640                     print = (appData.engineComments >= 2);
14641                 }
14642                 message[0] = start; // restore original message
14643         }
14644         if(print) {
14645                 GetTimeMark(&now);
14646                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14647                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14648                         quote,
14649                         message);
14650         }
14651     }
14652
14653     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14654     if (appData.icsEngineAnalyze) {
14655         if (strstr(message, "whisper") != NULL ||
14656              strstr(message, "kibitz") != NULL ||
14657             strstr(message, "tellics") != NULL) return;
14658     }
14659
14660     HandleMachineMove(message, cps);
14661 }
14662
14663
14664 void
14665 SendTimeControl(cps, mps, tc, inc, sd, st)
14666      ChessProgramState *cps;
14667      int mps, inc, sd, st;
14668      long tc;
14669 {
14670     char buf[MSG_SIZ];
14671     int seconds;
14672
14673     if( timeControl_2 > 0 ) {
14674         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14675             tc = timeControl_2;
14676         }
14677     }
14678     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14679     inc /= cps->timeOdds;
14680     st  /= cps->timeOdds;
14681
14682     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14683
14684     if (st > 0) {
14685       /* Set exact time per move, normally using st command */
14686       if (cps->stKludge) {
14687         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14688         seconds = st % 60;
14689         if (seconds == 0) {
14690           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14691         } else {
14692           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14693         }
14694       } else {
14695         snprintf(buf, MSG_SIZ, "st %d\n", st);
14696       }
14697     } else {
14698       /* Set conventional or incremental time control, using level command */
14699       if (seconds == 0) {
14700         /* Note old gnuchess bug -- minutes:seconds used to not work.
14701            Fixed in later versions, but still avoid :seconds
14702            when seconds is 0. */
14703         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14704       } else {
14705         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14706                  seconds, inc/1000.);
14707       }
14708     }
14709     SendToProgram(buf, cps);
14710
14711     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14712     /* Orthogonally, limit search to given depth */
14713     if (sd > 0) {
14714       if (cps->sdKludge) {
14715         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14716       } else {
14717         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14718       }
14719       SendToProgram(buf, cps);
14720     }
14721
14722     if(cps->nps >= 0) { /* [HGM] nps */
14723         if(cps->supportsNPS == FALSE)
14724           cps->nps = -1; // don't use if engine explicitly says not supported!
14725         else {
14726           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14727           SendToProgram(buf, cps);
14728         }
14729     }
14730 }
14731
14732 ChessProgramState *WhitePlayer()
14733 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14734 {
14735     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14736        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14737         return &second;
14738     return &first;
14739 }
14740
14741 void
14742 SendTimeRemaining(cps, machineWhite)
14743      ChessProgramState *cps;
14744      int /*boolean*/ machineWhite;
14745 {
14746     char message[MSG_SIZ];
14747     long time, otime;
14748
14749     /* Note: this routine must be called when the clocks are stopped
14750        or when they have *just* been set or switched; otherwise
14751        it will be off by the time since the current tick started.
14752     */
14753     if (machineWhite) {
14754         time = whiteTimeRemaining / 10;
14755         otime = blackTimeRemaining / 10;
14756     } else {
14757         time = blackTimeRemaining / 10;
14758         otime = whiteTimeRemaining / 10;
14759     }
14760     /* [HGM] translate opponent's time by time-odds factor */
14761     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14762     if (appData.debugMode) {
14763         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14764     }
14765
14766     if (time <= 0) time = 1;
14767     if (otime <= 0) otime = 1;
14768
14769     snprintf(message, MSG_SIZ, "time %ld\n", time);
14770     SendToProgram(message, cps);
14771
14772     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14773     SendToProgram(message, cps);
14774 }
14775
14776 int
14777 BoolFeature(p, name, loc, cps)
14778      char **p;
14779      char *name;
14780      int *loc;
14781      ChessProgramState *cps;
14782 {
14783   char buf[MSG_SIZ];
14784   int len = strlen(name);
14785   int val;
14786
14787   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14788     (*p) += len + 1;
14789     sscanf(*p, "%d", &val);
14790     *loc = (val != 0);
14791     while (**p && **p != ' ')
14792       (*p)++;
14793     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14794     SendToProgram(buf, cps);
14795     return TRUE;
14796   }
14797   return FALSE;
14798 }
14799
14800 int
14801 IntFeature(p, name, loc, cps)
14802      char **p;
14803      char *name;
14804      int *loc;
14805      ChessProgramState *cps;
14806 {
14807   char buf[MSG_SIZ];
14808   int len = strlen(name);
14809   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14810     (*p) += len + 1;
14811     sscanf(*p, "%d", loc);
14812     while (**p && **p != ' ') (*p)++;
14813     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14814     SendToProgram(buf, cps);
14815     return TRUE;
14816   }
14817   return FALSE;
14818 }
14819
14820 int
14821 StringFeature(p, name, loc, cps)
14822      char **p;
14823      char *name;
14824      char loc[];
14825      ChessProgramState *cps;
14826 {
14827   char buf[MSG_SIZ];
14828   int len = strlen(name);
14829   if (strncmp((*p), name, len) == 0
14830       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14831     (*p) += len + 2;
14832     sscanf(*p, "%[^\"]", loc);
14833     while (**p && **p != '\"') (*p)++;
14834     if (**p == '\"') (*p)++;
14835     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14836     SendToProgram(buf, cps);
14837     return TRUE;
14838   }
14839   return FALSE;
14840 }
14841
14842 int
14843 ParseOption(Option *opt, ChessProgramState *cps)
14844 // [HGM] options: process the string that defines an engine option, and determine
14845 // name, type, default value, and allowed value range
14846 {
14847         char *p, *q, buf[MSG_SIZ];
14848         int n, min = (-1)<<31, max = 1<<31, def;
14849
14850         if(p = strstr(opt->name, " -spin ")) {
14851             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14852             if(max < min) max = min; // enforce consistency
14853             if(def < min) def = min;
14854             if(def > max) def = max;
14855             opt->value = def;
14856             opt->min = min;
14857             opt->max = max;
14858             opt->type = Spin;
14859         } else if((p = strstr(opt->name, " -slider "))) {
14860             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14861             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14862             if(max < min) max = min; // enforce consistency
14863             if(def < min) def = min;
14864             if(def > max) def = max;
14865             opt->value = def;
14866             opt->min = min;
14867             opt->max = max;
14868             opt->type = Spin; // Slider;
14869         } else if((p = strstr(opt->name, " -string "))) {
14870             opt->textValue = p+9;
14871             opt->type = TextBox;
14872         } else if((p = strstr(opt->name, " -file "))) {
14873             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14874             opt->textValue = p+7;
14875             opt->type = FileName; // FileName;
14876         } else if((p = strstr(opt->name, " -path "))) {
14877             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14878             opt->textValue = p+7;
14879             opt->type = PathName; // PathName;
14880         } else if(p = strstr(opt->name, " -check ")) {
14881             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14882             opt->value = (def != 0);
14883             opt->type = CheckBox;
14884         } else if(p = strstr(opt->name, " -combo ")) {
14885             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14886             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14887             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14888             opt->value = n = 0;
14889             while(q = StrStr(q, " /// ")) {
14890                 n++; *q = 0;    // count choices, and null-terminate each of them
14891                 q += 5;
14892                 if(*q == '*') { // remember default, which is marked with * prefix
14893                     q++;
14894                     opt->value = n;
14895                 }
14896                 cps->comboList[cps->comboCnt++] = q;
14897             }
14898             cps->comboList[cps->comboCnt++] = NULL;
14899             opt->max = n + 1;
14900             opt->type = ComboBox;
14901         } else if(p = strstr(opt->name, " -button")) {
14902             opt->type = Button;
14903         } else if(p = strstr(opt->name, " -save")) {
14904             opt->type = SaveButton;
14905         } else return FALSE;
14906         *p = 0; // terminate option name
14907         // now look if the command-line options define a setting for this engine option.
14908         if(cps->optionSettings && cps->optionSettings[0])
14909             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14910         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14911           snprintf(buf, MSG_SIZ, "option %s", p);
14912                 if(p = strstr(buf, ",")) *p = 0;
14913                 if(q = strchr(buf, '=')) switch(opt->type) {
14914                     case ComboBox:
14915                         for(n=0; n<opt->max; n++)
14916                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14917                         break;
14918                     case TextBox:
14919                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14920                         break;
14921                     case Spin:
14922                     case CheckBox:
14923                         opt->value = atoi(q+1);
14924                     default:
14925                         break;
14926                 }
14927                 strcat(buf, "\n");
14928                 SendToProgram(buf, cps);
14929         }
14930         return TRUE;
14931 }
14932
14933 void
14934 FeatureDone(cps, val)
14935      ChessProgramState* cps;
14936      int val;
14937 {
14938   DelayedEventCallback cb = GetDelayedEvent();
14939   if ((cb == InitBackEnd3 && cps == &first) ||
14940       (cb == SettingsMenuIfReady && cps == &second) ||
14941       (cb == LoadEngine) ||
14942       (cb == TwoMachinesEventIfReady)) {
14943     CancelDelayedEvent();
14944     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14945   }
14946   cps->initDone = val;
14947 }
14948
14949 /* Parse feature command from engine */
14950 void
14951 ParseFeatures(args, cps)
14952      char* args;
14953      ChessProgramState *cps;
14954 {
14955   char *p = args;
14956   char *q;
14957   int val;
14958   char buf[MSG_SIZ];
14959
14960   for (;;) {
14961     while (*p == ' ') p++;
14962     if (*p == NULLCHAR) return;
14963
14964     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14965     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14966     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14967     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14968     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14969     if (BoolFeature(&p, "reuse", &val, cps)) {
14970       /* Engine can disable reuse, but can't enable it if user said no */
14971       if (!val) cps->reuse = FALSE;
14972       continue;
14973     }
14974     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14975     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14976       if (gameMode == TwoMachinesPlay) {
14977         DisplayTwoMachinesTitle();
14978       } else {
14979         DisplayTitle("");
14980       }
14981       continue;
14982     }
14983     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14984     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14985     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14986     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14987     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14988     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14989     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14990     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14991     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14992     if (IntFeature(&p, "done", &val, cps)) {
14993       FeatureDone(cps, val);
14994       continue;
14995     }
14996     /* Added by Tord: */
14997     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14998     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14999     /* End of additions by Tord */
15000
15001     /* [HGM] added features: */
15002     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15003     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15004     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15005     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15006     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15007     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15008     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15009         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15010           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15011             SendToProgram(buf, cps);
15012             continue;
15013         }
15014         if(cps->nrOptions >= MAX_OPTIONS) {
15015             cps->nrOptions--;
15016             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15017             DisplayError(buf, 0);
15018         }
15019         continue;
15020     }
15021     /* End of additions by HGM */
15022
15023     /* unknown feature: complain and skip */
15024     q = p;
15025     while (*q && *q != '=') q++;
15026     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15027     SendToProgram(buf, cps);
15028     p = q;
15029     if (*p == '=') {
15030       p++;
15031       if (*p == '\"') {
15032         p++;
15033         while (*p && *p != '\"') p++;
15034         if (*p == '\"') p++;
15035       } else {
15036         while (*p && *p != ' ') p++;
15037       }
15038     }
15039   }
15040
15041 }
15042
15043 void
15044 PeriodicUpdatesEvent(newState)
15045      int newState;
15046 {
15047     if (newState == appData.periodicUpdates)
15048       return;
15049
15050     appData.periodicUpdates=newState;
15051
15052     /* Display type changes, so update it now */
15053 //    DisplayAnalysis();
15054
15055     /* Get the ball rolling again... */
15056     if (newState) {
15057         AnalysisPeriodicEvent(1);
15058         StartAnalysisClock();
15059     }
15060 }
15061
15062 void
15063 PonderNextMoveEvent(newState)
15064      int newState;
15065 {
15066     if (newState == appData.ponderNextMove) return;
15067     if (gameMode == EditPosition) EditPositionDone(TRUE);
15068     if (newState) {
15069         SendToProgram("hard\n", &first);
15070         if (gameMode == TwoMachinesPlay) {
15071             SendToProgram("hard\n", &second);
15072         }
15073     } else {
15074         SendToProgram("easy\n", &first);
15075         thinkOutput[0] = NULLCHAR;
15076         if (gameMode == TwoMachinesPlay) {
15077             SendToProgram("easy\n", &second);
15078         }
15079     }
15080     appData.ponderNextMove = newState;
15081 }
15082
15083 void
15084 NewSettingEvent(option, feature, command, value)
15085      char *command;
15086      int option, value, *feature;
15087 {
15088     char buf[MSG_SIZ];
15089
15090     if (gameMode == EditPosition) EditPositionDone(TRUE);
15091     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15092     if(feature == NULL || *feature) SendToProgram(buf, &first);
15093     if (gameMode == TwoMachinesPlay) {
15094         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15095     }
15096 }
15097
15098 void
15099 ShowThinkingEvent()
15100 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15101 {
15102     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15103     int newState = appData.showThinking
15104         // [HGM] thinking: other features now need thinking output as well
15105         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15106
15107     if (oldState == newState) return;
15108     oldState = newState;
15109     if (gameMode == EditPosition) EditPositionDone(TRUE);
15110     if (oldState) {
15111         SendToProgram("post\n", &first);
15112         if (gameMode == TwoMachinesPlay) {
15113             SendToProgram("post\n", &second);
15114         }
15115     } else {
15116         SendToProgram("nopost\n", &first);
15117         thinkOutput[0] = NULLCHAR;
15118         if (gameMode == TwoMachinesPlay) {
15119             SendToProgram("nopost\n", &second);
15120         }
15121     }
15122 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15123 }
15124
15125 void
15126 AskQuestionEvent(title, question, replyPrefix, which)
15127      char *title; char *question; char *replyPrefix; char *which;
15128 {
15129   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15130   if (pr == NoProc) return;
15131   AskQuestion(title, question, replyPrefix, pr);
15132 }
15133
15134 void
15135 TypeInEvent(char firstChar)
15136 {
15137     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15138         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15139         gameMode == AnalyzeMode || gameMode == EditGame || 
15140         gameMode == EditPosition || gameMode == IcsExamining ||
15141         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15142         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15143                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15144                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15145         gameMode == Training) PopUpMoveDialog(firstChar);
15146 }
15147
15148 void
15149 TypeInDoneEvent(char *move)
15150 {
15151         Board board;
15152         int n, fromX, fromY, toX, toY;
15153         char promoChar;
15154         ChessMove moveType;
15155
15156         // [HGM] FENedit
15157         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15158                 EditPositionPasteFEN(move);
15159                 return;
15160         }
15161         // [HGM] movenum: allow move number to be typed in any mode
15162         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15163           ToNrEvent(2*n-1);
15164           return;
15165         }
15166
15167       if (gameMode != EditGame && currentMove != forwardMostMove && 
15168         gameMode != Training) {
15169         DisplayMoveError(_("Displayed move is not current"));
15170       } else {
15171         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15172           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15173         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15174         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15175           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15176           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15177         } else {
15178           DisplayMoveError(_("Could not parse move"));
15179         }
15180       }
15181 }
15182
15183 void
15184 DisplayMove(moveNumber)
15185      int moveNumber;
15186 {
15187     char message[MSG_SIZ];
15188     char res[MSG_SIZ];
15189     char cpThinkOutput[MSG_SIZ];
15190
15191     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15192
15193     if (moveNumber == forwardMostMove - 1 ||
15194         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15195
15196         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15197
15198         if (strchr(cpThinkOutput, '\n')) {
15199             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15200         }
15201     } else {
15202         *cpThinkOutput = NULLCHAR;
15203     }
15204
15205     /* [AS] Hide thinking from human user */
15206     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15207         *cpThinkOutput = NULLCHAR;
15208         if( thinkOutput[0] != NULLCHAR ) {
15209             int i;
15210
15211             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15212                 cpThinkOutput[i] = '.';
15213             }
15214             cpThinkOutput[i] = NULLCHAR;
15215             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15216         }
15217     }
15218
15219     if (moveNumber == forwardMostMove - 1 &&
15220         gameInfo.resultDetails != NULL) {
15221         if (gameInfo.resultDetails[0] == NULLCHAR) {
15222           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15223         } else {
15224           snprintf(res, MSG_SIZ, " {%s} %s",
15225                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15226         }
15227     } else {
15228         res[0] = NULLCHAR;
15229     }
15230
15231     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15232         DisplayMessage(res, cpThinkOutput);
15233     } else {
15234       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15235                 WhiteOnMove(moveNumber) ? " " : ".. ",
15236                 parseList[moveNumber], res);
15237         DisplayMessage(message, cpThinkOutput);
15238     }
15239 }
15240
15241 void
15242 DisplayComment(moveNumber, text)
15243      int moveNumber;
15244      char *text;
15245 {
15246     char title[MSG_SIZ];
15247
15248     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15249       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15250     } else {
15251       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15252               WhiteOnMove(moveNumber) ? " " : ".. ",
15253               parseList[moveNumber]);
15254     }
15255     if (text != NULL && (appData.autoDisplayComment || commentUp))
15256         CommentPopUp(title, text);
15257 }
15258
15259 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15260  * might be busy thinking or pondering.  It can be omitted if your
15261  * gnuchess is configured to stop thinking immediately on any user
15262  * input.  However, that gnuchess feature depends on the FIONREAD
15263  * ioctl, which does not work properly on some flavors of Unix.
15264  */
15265 void
15266 Attention(cps)
15267      ChessProgramState *cps;
15268 {
15269 #if ATTENTION
15270     if (!cps->useSigint) return;
15271     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15272     switch (gameMode) {
15273       case MachinePlaysWhite:
15274       case MachinePlaysBlack:
15275       case TwoMachinesPlay:
15276       case IcsPlayingWhite:
15277       case IcsPlayingBlack:
15278       case AnalyzeMode:
15279       case AnalyzeFile:
15280         /* Skip if we know it isn't thinking */
15281         if (!cps->maybeThinking) return;
15282         if (appData.debugMode)
15283           fprintf(debugFP, "Interrupting %s\n", cps->which);
15284         InterruptChildProcess(cps->pr);
15285         cps->maybeThinking = FALSE;
15286         break;
15287       default:
15288         break;
15289     }
15290 #endif /*ATTENTION*/
15291 }
15292
15293 int
15294 CheckFlags()
15295 {
15296     if (whiteTimeRemaining <= 0) {
15297         if (!whiteFlag) {
15298             whiteFlag = TRUE;
15299             if (appData.icsActive) {
15300                 if (appData.autoCallFlag &&
15301                     gameMode == IcsPlayingBlack && !blackFlag) {
15302                   SendToICS(ics_prefix);
15303                   SendToICS("flag\n");
15304                 }
15305             } else {
15306                 if (blackFlag) {
15307                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15308                 } else {
15309                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15310                     if (appData.autoCallFlag) {
15311                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15312                         return TRUE;
15313                     }
15314                 }
15315             }
15316         }
15317     }
15318     if (blackTimeRemaining <= 0) {
15319         if (!blackFlag) {
15320             blackFlag = TRUE;
15321             if (appData.icsActive) {
15322                 if (appData.autoCallFlag &&
15323                     gameMode == IcsPlayingWhite && !whiteFlag) {
15324                   SendToICS(ics_prefix);
15325                   SendToICS("flag\n");
15326                 }
15327             } else {
15328                 if (whiteFlag) {
15329                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15330                 } else {
15331                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15332                     if (appData.autoCallFlag) {
15333                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15334                         return TRUE;
15335                     }
15336                 }
15337             }
15338         }
15339     }
15340     return FALSE;
15341 }
15342
15343 void
15344 CheckTimeControl()
15345 {
15346     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15347         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15348
15349     /*
15350      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15351      */
15352     if ( !WhiteOnMove(forwardMostMove) ) {
15353         /* White made time control */
15354         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15355         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15356         /* [HGM] time odds: correct new time quota for time odds! */
15357                                             / WhitePlayer()->timeOdds;
15358         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15359     } else {
15360         lastBlack -= blackTimeRemaining;
15361         /* Black made time control */
15362         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15363                                             / WhitePlayer()->other->timeOdds;
15364         lastWhite = whiteTimeRemaining;
15365     }
15366 }
15367
15368 void
15369 DisplayBothClocks()
15370 {
15371     int wom = gameMode == EditPosition ?
15372       !blackPlaysFirst : WhiteOnMove(currentMove);
15373     DisplayWhiteClock(whiteTimeRemaining, wom);
15374     DisplayBlackClock(blackTimeRemaining, !wom);
15375 }
15376
15377
15378 /* Timekeeping seems to be a portability nightmare.  I think everyone
15379    has ftime(), but I'm really not sure, so I'm including some ifdefs
15380    to use other calls if you don't.  Clocks will be less accurate if
15381    you have neither ftime nor gettimeofday.
15382 */
15383
15384 /* VS 2008 requires the #include outside of the function */
15385 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15386 #include <sys/timeb.h>
15387 #endif
15388
15389 /* Get the current time as a TimeMark */
15390 void
15391 GetTimeMark(tm)
15392      TimeMark *tm;
15393 {
15394 #if HAVE_GETTIMEOFDAY
15395
15396     struct timeval timeVal;
15397     struct timezone timeZone;
15398
15399     gettimeofday(&timeVal, &timeZone);
15400     tm->sec = (long) timeVal.tv_sec;
15401     tm->ms = (int) (timeVal.tv_usec / 1000L);
15402
15403 #else /*!HAVE_GETTIMEOFDAY*/
15404 #if HAVE_FTIME
15405
15406 // include <sys/timeb.h> / moved to just above start of function
15407     struct timeb timeB;
15408
15409     ftime(&timeB);
15410     tm->sec = (long) timeB.time;
15411     tm->ms = (int) timeB.millitm;
15412
15413 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15414     tm->sec = (long) time(NULL);
15415     tm->ms = 0;
15416 #endif
15417 #endif
15418 }
15419
15420 /* Return the difference in milliseconds between two
15421    time marks.  We assume the difference will fit in a long!
15422 */
15423 long
15424 SubtractTimeMarks(tm2, tm1)
15425      TimeMark *tm2, *tm1;
15426 {
15427     return 1000L*(tm2->sec - tm1->sec) +
15428            (long) (tm2->ms - tm1->ms);
15429 }
15430
15431
15432 /*
15433  * Code to manage the game clocks.
15434  *
15435  * In tournament play, black starts the clock and then white makes a move.
15436  * We give the human user a slight advantage if he is playing white---the
15437  * clocks don't run until he makes his first move, so it takes zero time.
15438  * Also, we don't account for network lag, so we could get out of sync
15439  * with GNU Chess's clock -- but then, referees are always right.
15440  */
15441
15442 static TimeMark tickStartTM;
15443 static long intendedTickLength;
15444
15445 long
15446 NextTickLength(timeRemaining)
15447      long timeRemaining;
15448 {
15449     long nominalTickLength, nextTickLength;
15450
15451     if (timeRemaining > 0L && timeRemaining <= 10000L)
15452       nominalTickLength = 100L;
15453     else
15454       nominalTickLength = 1000L;
15455     nextTickLength = timeRemaining % nominalTickLength;
15456     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15457
15458     return nextTickLength;
15459 }
15460
15461 /* Adjust clock one minute up or down */
15462 void
15463 AdjustClock(Boolean which, int dir)
15464 {
15465     if(which) blackTimeRemaining += 60000*dir;
15466     else      whiteTimeRemaining += 60000*dir;
15467     DisplayBothClocks();
15468 }
15469
15470 /* Stop clocks and reset to a fresh time control */
15471 void
15472 ResetClocks()
15473 {
15474     (void) StopClockTimer();
15475     if (appData.icsActive) {
15476         whiteTimeRemaining = blackTimeRemaining = 0;
15477     } else if (searchTime) {
15478         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15479         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15480     } else { /* [HGM] correct new time quote for time odds */
15481         whiteTC = blackTC = fullTimeControlString;
15482         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15483         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15484     }
15485     if (whiteFlag || blackFlag) {
15486         DisplayTitle("");
15487         whiteFlag = blackFlag = FALSE;
15488     }
15489     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15490     DisplayBothClocks();
15491 }
15492
15493 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15494
15495 /* Decrement running clock by amount of time that has passed */
15496 void
15497 DecrementClocks()
15498 {
15499     long timeRemaining;
15500     long lastTickLength, fudge;
15501     TimeMark now;
15502
15503     if (!appData.clockMode) return;
15504     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15505
15506     GetTimeMark(&now);
15507
15508     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15509
15510     /* Fudge if we woke up a little too soon */
15511     fudge = intendedTickLength - lastTickLength;
15512     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15513
15514     if (WhiteOnMove(forwardMostMove)) {
15515         if(whiteNPS >= 0) lastTickLength = 0;
15516         timeRemaining = whiteTimeRemaining -= lastTickLength;
15517         if(timeRemaining < 0 && !appData.icsActive) {
15518             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15519             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15520                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15521                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15522             }
15523         }
15524         DisplayWhiteClock(whiteTimeRemaining - fudge,
15525                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15526     } else {
15527         if(blackNPS >= 0) lastTickLength = 0;
15528         timeRemaining = blackTimeRemaining -= lastTickLength;
15529         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15530             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15531             if(suddenDeath) {
15532                 blackStartMove = forwardMostMove;
15533                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15534             }
15535         }
15536         DisplayBlackClock(blackTimeRemaining - fudge,
15537                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15538     }
15539     if (CheckFlags()) return;
15540
15541     tickStartTM = now;
15542     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15543     StartClockTimer(intendedTickLength);
15544
15545     /* if the time remaining has fallen below the alarm threshold, sound the
15546      * alarm. if the alarm has sounded and (due to a takeback or time control
15547      * with increment) the time remaining has increased to a level above the
15548      * threshold, reset the alarm so it can sound again.
15549      */
15550
15551     if (appData.icsActive && appData.icsAlarm) {
15552
15553         /* make sure we are dealing with the user's clock */
15554         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15555                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15556            )) return;
15557
15558         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15559             alarmSounded = FALSE;
15560         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15561             PlayAlarmSound();
15562             alarmSounded = TRUE;
15563         }
15564     }
15565 }
15566
15567
15568 /* A player has just moved, so stop the previously running
15569    clock and (if in clock mode) start the other one.
15570    We redisplay both clocks in case we're in ICS mode, because
15571    ICS gives us an update to both clocks after every move.
15572    Note that this routine is called *after* forwardMostMove
15573    is updated, so the last fractional tick must be subtracted
15574    from the color that is *not* on move now.
15575 */
15576 void
15577 SwitchClocks(int newMoveNr)
15578 {
15579     long lastTickLength;
15580     TimeMark now;
15581     int flagged = FALSE;
15582
15583     GetTimeMark(&now);
15584
15585     if (StopClockTimer() && appData.clockMode) {
15586         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15587         if (!WhiteOnMove(forwardMostMove)) {
15588             if(blackNPS >= 0) lastTickLength = 0;
15589             blackTimeRemaining -= lastTickLength;
15590            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15591 //         if(pvInfoList[forwardMostMove].time == -1)
15592                  pvInfoList[forwardMostMove].time =               // use GUI time
15593                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15594         } else {
15595            if(whiteNPS >= 0) lastTickLength = 0;
15596            whiteTimeRemaining -= lastTickLength;
15597            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15598 //         if(pvInfoList[forwardMostMove].time == -1)
15599                  pvInfoList[forwardMostMove].time =
15600                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15601         }
15602         flagged = CheckFlags();
15603     }
15604     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15605     CheckTimeControl();
15606
15607     if (flagged || !appData.clockMode) return;
15608
15609     switch (gameMode) {
15610       case MachinePlaysBlack:
15611       case MachinePlaysWhite:
15612       case BeginningOfGame:
15613         if (pausing) return;
15614         break;
15615
15616       case EditGame:
15617       case PlayFromGameFile:
15618       case IcsExamining:
15619         return;
15620
15621       default:
15622         break;
15623     }
15624
15625     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15626         if(WhiteOnMove(forwardMostMove))
15627              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15628         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15629     }
15630
15631     tickStartTM = now;
15632     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15633       whiteTimeRemaining : blackTimeRemaining);
15634     StartClockTimer(intendedTickLength);
15635 }
15636
15637
15638 /* Stop both clocks */
15639 void
15640 StopClocks()
15641 {
15642     long lastTickLength;
15643     TimeMark now;
15644
15645     if (!StopClockTimer()) return;
15646     if (!appData.clockMode) return;
15647
15648     GetTimeMark(&now);
15649
15650     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15651     if (WhiteOnMove(forwardMostMove)) {
15652         if(whiteNPS >= 0) lastTickLength = 0;
15653         whiteTimeRemaining -= lastTickLength;
15654         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15655     } else {
15656         if(blackNPS >= 0) lastTickLength = 0;
15657         blackTimeRemaining -= lastTickLength;
15658         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15659     }
15660     CheckFlags();
15661 }
15662
15663 /* Start clock of player on move.  Time may have been reset, so
15664    if clock is already running, stop and restart it. */
15665 void
15666 StartClocks()
15667 {
15668     (void) StopClockTimer(); /* in case it was running already */
15669     DisplayBothClocks();
15670     if (CheckFlags()) return;
15671
15672     if (!appData.clockMode) return;
15673     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15674
15675     GetTimeMark(&tickStartTM);
15676     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15677       whiteTimeRemaining : blackTimeRemaining);
15678
15679    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15680     whiteNPS = blackNPS = -1;
15681     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15682        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15683         whiteNPS = first.nps;
15684     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15685        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15686         blackNPS = first.nps;
15687     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15688         whiteNPS = second.nps;
15689     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15690         blackNPS = second.nps;
15691     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15692
15693     StartClockTimer(intendedTickLength);
15694 }
15695
15696 char *
15697 TimeString(ms)
15698      long ms;
15699 {
15700     long second, minute, hour, day;
15701     char *sign = "";
15702     static char buf[32];
15703
15704     if (ms > 0 && ms <= 9900) {
15705       /* convert milliseconds to tenths, rounding up */
15706       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15707
15708       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15709       return buf;
15710     }
15711
15712     /* convert milliseconds to seconds, rounding up */
15713     /* use floating point to avoid strangeness of integer division
15714        with negative dividends on many machines */
15715     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15716
15717     if (second < 0) {
15718         sign = "-";
15719         second = -second;
15720     }
15721
15722     day = second / (60 * 60 * 24);
15723     second = second % (60 * 60 * 24);
15724     hour = second / (60 * 60);
15725     second = second % (60 * 60);
15726     minute = second / 60;
15727     second = second % 60;
15728
15729     if (day > 0)
15730       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15731               sign, day, hour, minute, second);
15732     else if (hour > 0)
15733       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15734     else
15735       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15736
15737     return buf;
15738 }
15739
15740
15741 /*
15742  * This is necessary because some C libraries aren't ANSI C compliant yet.
15743  */
15744 char *
15745 StrStr(string, match)
15746      char *string, *match;
15747 {
15748     int i, length;
15749
15750     length = strlen(match);
15751
15752     for (i = strlen(string) - length; i >= 0; i--, string++)
15753       if (!strncmp(match, string, length))
15754         return string;
15755
15756     return NULL;
15757 }
15758
15759 char *
15760 StrCaseStr(string, match)
15761      char *string, *match;
15762 {
15763     int i, j, length;
15764
15765     length = strlen(match);
15766
15767     for (i = strlen(string) - length; i >= 0; i--, string++) {
15768         for (j = 0; j < length; j++) {
15769             if (ToLower(match[j]) != ToLower(string[j]))
15770               break;
15771         }
15772         if (j == length) return string;
15773     }
15774
15775     return NULL;
15776 }
15777
15778 #ifndef _amigados
15779 int
15780 StrCaseCmp(s1, s2)
15781      char *s1, *s2;
15782 {
15783     char c1, c2;
15784
15785     for (;;) {
15786         c1 = ToLower(*s1++);
15787         c2 = ToLower(*s2++);
15788         if (c1 > c2) return 1;
15789         if (c1 < c2) return -1;
15790         if (c1 == NULLCHAR) return 0;
15791     }
15792 }
15793
15794
15795 int
15796 ToLower(c)
15797      int c;
15798 {
15799     return isupper(c) ? tolower(c) : c;
15800 }
15801
15802
15803 int
15804 ToUpper(c)
15805      int c;
15806 {
15807     return islower(c) ? toupper(c) : c;
15808 }
15809 #endif /* !_amigados    */
15810
15811 char *
15812 StrSave(s)
15813      char *s;
15814 {
15815   char *ret;
15816
15817   if ((ret = (char *) malloc(strlen(s) + 1)))
15818     {
15819       safeStrCpy(ret, s, strlen(s)+1);
15820     }
15821   return ret;
15822 }
15823
15824 char *
15825 StrSavePtr(s, savePtr)
15826      char *s, **savePtr;
15827 {
15828     if (*savePtr) {
15829         free(*savePtr);
15830     }
15831     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15832       safeStrCpy(*savePtr, s, strlen(s)+1);
15833     }
15834     return(*savePtr);
15835 }
15836
15837 char *
15838 PGNDate()
15839 {
15840     time_t clock;
15841     struct tm *tm;
15842     char buf[MSG_SIZ];
15843
15844     clock = time((time_t *)NULL);
15845     tm = localtime(&clock);
15846     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15847             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15848     return StrSave(buf);
15849 }
15850
15851
15852 char *
15853 PositionToFEN(move, overrideCastling)
15854      int move;
15855      char *overrideCastling;
15856 {
15857     int i, j, fromX, fromY, toX, toY;
15858     int whiteToPlay;
15859     char buf[MSG_SIZ];
15860     char *p, *q;
15861     int emptycount;
15862     ChessSquare piece;
15863
15864     whiteToPlay = (gameMode == EditPosition) ?
15865       !blackPlaysFirst : (move % 2 == 0);
15866     p = buf;
15867
15868     /* Piece placement data */
15869     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15870         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
15871         emptycount = 0;
15872         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15873             if (boards[move][i][j] == EmptySquare) {
15874                 emptycount++;
15875             } else { ChessSquare piece = boards[move][i][j];
15876                 if (emptycount > 0) {
15877                     if(emptycount<10) /* [HGM] can be >= 10 */
15878                         *p++ = '0' + emptycount;
15879                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15880                     emptycount = 0;
15881                 }
15882                 if(PieceToChar(piece) == '+') {
15883                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15884                     *p++ = '+';
15885                     piece = (ChessSquare)(DEMOTED piece);
15886                 }
15887                 *p++ = PieceToChar(piece);
15888                 if(p[-1] == '~') {
15889                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15890                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15891                     *p++ = '~';
15892                 }
15893             }
15894         }
15895         if (emptycount > 0) {
15896             if(emptycount<10) /* [HGM] can be >= 10 */
15897                 *p++ = '0' + emptycount;
15898             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15899             emptycount = 0;
15900         }
15901         *p++ = '/';
15902     }
15903     *(p - 1) = ' ';
15904
15905     /* [HGM] print Crazyhouse or Shogi holdings */
15906     if( gameInfo.holdingsWidth ) {
15907         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15908         q = p;
15909         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15910             piece = boards[move][i][BOARD_WIDTH-1];
15911             if( piece != EmptySquare )
15912               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15913                   *p++ = PieceToChar(piece);
15914         }
15915         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15916             piece = boards[move][BOARD_HEIGHT-i-1][0];
15917             if( piece != EmptySquare )
15918               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15919                   *p++ = PieceToChar(piece);
15920         }
15921
15922         if( q == p ) *p++ = '-';
15923         *p++ = ']';
15924         *p++ = ' ';
15925     }
15926
15927     /* Active color */
15928     *p++ = whiteToPlay ? 'w' : 'b';
15929     *p++ = ' ';
15930
15931   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15932     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15933   } else {
15934   if(nrCastlingRights) {
15935      q = p;
15936      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15937        /* [HGM] write directly from rights */
15938            if(boards[move][CASTLING][2] != NoRights &&
15939               boards[move][CASTLING][0] != NoRights   )
15940                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15941            if(boards[move][CASTLING][2] != NoRights &&
15942               boards[move][CASTLING][1] != NoRights   )
15943                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15944            if(boards[move][CASTLING][5] != NoRights &&
15945               boards[move][CASTLING][3] != NoRights   )
15946                 *p++ = boards[move][CASTLING][3] + AAA;
15947            if(boards[move][CASTLING][5] != NoRights &&
15948               boards[move][CASTLING][4] != NoRights   )
15949                 *p++ = boards[move][CASTLING][4] + AAA;
15950      } else {
15951
15952         /* [HGM] write true castling rights */
15953         if( nrCastlingRights == 6 ) {
15954             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15955                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15956             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15957                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15958             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15959                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15960             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15961                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15962         }
15963      }
15964      if (q == p) *p++ = '-'; /* No castling rights */
15965      *p++ = ' ';
15966   }
15967
15968   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15969      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15970     /* En passant target square */
15971     if (move > backwardMostMove) {
15972         fromX = moveList[move - 1][0] - AAA;
15973         fromY = moveList[move - 1][1] - ONE;
15974         toX = moveList[move - 1][2] - AAA;
15975         toY = moveList[move - 1][3] - ONE;
15976         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15977             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15978             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15979             fromX == toX) {
15980             /* 2-square pawn move just happened */
15981             *p++ = toX + AAA;
15982             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15983         } else {
15984             *p++ = '-';
15985         }
15986     } else if(move == backwardMostMove) {
15987         // [HGM] perhaps we should always do it like this, and forget the above?
15988         if((signed char)boards[move][EP_STATUS] >= 0) {
15989             *p++ = boards[move][EP_STATUS] + AAA;
15990             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15991         } else {
15992             *p++ = '-';
15993         }
15994     } else {
15995         *p++ = '-';
15996     }
15997     *p++ = ' ';
15998   }
15999   }
16000
16001     /* [HGM] find reversible plies */
16002     {   int i = 0, j=move;
16003
16004         if (appData.debugMode) { int k;
16005             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16006             for(k=backwardMostMove; k<=forwardMostMove; k++)
16007                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16008
16009         }
16010
16011         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16012         if( j == backwardMostMove ) i += initialRulePlies;
16013         sprintf(p, "%d ", i);
16014         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16015     }
16016     /* Fullmove number */
16017     sprintf(p, "%d", (move / 2) + 1);
16018
16019     return StrSave(buf);
16020 }
16021
16022 Boolean
16023 ParseFEN(board, blackPlaysFirst, fen)
16024     Board board;
16025      int *blackPlaysFirst;
16026      char *fen;
16027 {
16028     int i, j;
16029     char *p, c;
16030     int emptycount;
16031     ChessSquare piece;
16032
16033     p = fen;
16034
16035     /* [HGM] by default clear Crazyhouse holdings, if present */
16036     if(gameInfo.holdingsWidth) {
16037        for(i=0; i<BOARD_HEIGHT; i++) {
16038            board[i][0]             = EmptySquare; /* black holdings */
16039            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16040            board[i][1]             = (ChessSquare) 0; /* black counts */
16041            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16042        }
16043     }
16044
16045     /* Piece placement data */
16046     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16047         j = 0;
16048         for (;;) {
16049             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16050                 if (*p == '/') p++;
16051                 emptycount = gameInfo.boardWidth - j;
16052                 while (emptycount--)
16053                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16054                 break;
16055 #if(BOARD_FILES >= 10)
16056             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16057                 p++; emptycount=10;
16058                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16059                 while (emptycount--)
16060                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16061 #endif
16062             } else if (isdigit(*p)) {
16063                 emptycount = *p++ - '0';
16064                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16065                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16066                 while (emptycount--)
16067                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16068             } else if (*p == '+' || isalpha(*p)) {
16069                 if (j >= gameInfo.boardWidth) return FALSE;
16070                 if(*p=='+') {
16071                     piece = CharToPiece(*++p);
16072                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16073                     piece = (ChessSquare) (PROMOTED piece ); p++;
16074                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16075                 } else piece = CharToPiece(*p++);
16076
16077                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16078                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16079                     piece = (ChessSquare) (PROMOTED piece);
16080                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16081                     p++;
16082                 }
16083                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16084             } else {
16085                 return FALSE;
16086             }
16087         }
16088     }
16089     while (*p == '/' || *p == ' ') p++;
16090
16091     /* [HGM] look for Crazyhouse holdings here */
16092     while(*p==' ') p++;
16093     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16094         if(*p == '[') p++;
16095         if(*p == '-' ) p++; /* empty holdings */ else {
16096             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16097             /* if we would allow FEN reading to set board size, we would   */
16098             /* have to add holdings and shift the board read so far here   */
16099             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16100                 p++;
16101                 if((int) piece >= (int) BlackPawn ) {
16102                     i = (int)piece - (int)BlackPawn;
16103                     i = PieceToNumber((ChessSquare)i);
16104                     if( i >= gameInfo.holdingsSize ) return FALSE;
16105                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16106                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16107                 } else {
16108                     i = (int)piece - (int)WhitePawn;
16109                     i = PieceToNumber((ChessSquare)i);
16110                     if( i >= gameInfo.holdingsSize ) return FALSE;
16111                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16112                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16113                 }
16114             }
16115         }
16116         if(*p == ']') p++;
16117     }
16118
16119     while(*p == ' ') p++;
16120
16121     /* Active color */
16122     c = *p++;
16123     if(appData.colorNickNames) {
16124       if( c == appData.colorNickNames[0] ) c = 'w'; else
16125       if( c == appData.colorNickNames[1] ) c = 'b';
16126     }
16127     switch (c) {
16128       case 'w':
16129         *blackPlaysFirst = FALSE;
16130         break;
16131       case 'b':
16132         *blackPlaysFirst = TRUE;
16133         break;
16134       default:
16135         return FALSE;
16136     }
16137
16138     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16139     /* return the extra info in global variiables             */
16140
16141     /* set defaults in case FEN is incomplete */
16142     board[EP_STATUS] = EP_UNKNOWN;
16143     for(i=0; i<nrCastlingRights; i++ ) {
16144         board[CASTLING][i] =
16145             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16146     }   /* assume possible unless obviously impossible */
16147     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16148     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16149     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16150                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16151     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16152     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16153     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16154                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16155     FENrulePlies = 0;
16156
16157     while(*p==' ') p++;
16158     if(nrCastlingRights) {
16159       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16160           /* castling indicator present, so default becomes no castlings */
16161           for(i=0; i<nrCastlingRights; i++ ) {
16162                  board[CASTLING][i] = NoRights;
16163           }
16164       }
16165       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16166              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16167              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16168              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16169         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16170
16171         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16172             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16173             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16174         }
16175         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16176             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16177         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16178                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16179         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16180                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16181         switch(c) {
16182           case'K':
16183               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16184               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16185               board[CASTLING][2] = whiteKingFile;
16186               break;
16187           case'Q':
16188               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16189               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16190               board[CASTLING][2] = whiteKingFile;
16191               break;
16192           case'k':
16193               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16194               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16195               board[CASTLING][5] = blackKingFile;
16196               break;
16197           case'q':
16198               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16199               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16200               board[CASTLING][5] = blackKingFile;
16201           case '-':
16202               break;
16203           default: /* FRC castlings */
16204               if(c >= 'a') { /* black rights */
16205                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16206                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16207                   if(i == BOARD_RGHT) break;
16208                   board[CASTLING][5] = i;
16209                   c -= AAA;
16210                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16211                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16212                   if(c > i)
16213                       board[CASTLING][3] = c;
16214                   else
16215                       board[CASTLING][4] = c;
16216               } else { /* white rights */
16217                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16218                     if(board[0][i] == WhiteKing) break;
16219                   if(i == BOARD_RGHT) break;
16220                   board[CASTLING][2] = i;
16221                   c -= AAA - 'a' + 'A';
16222                   if(board[0][c] >= WhiteKing) break;
16223                   if(c > i)
16224                       board[CASTLING][0] = c;
16225                   else
16226                       board[CASTLING][1] = c;
16227               }
16228         }
16229       }
16230       for(i=0; i<nrCastlingRights; i++)
16231         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16232     if (appData.debugMode) {
16233         fprintf(debugFP, "FEN castling rights:");
16234         for(i=0; i<nrCastlingRights; i++)
16235         fprintf(debugFP, " %d", board[CASTLING][i]);
16236         fprintf(debugFP, "\n");
16237     }
16238
16239       while(*p==' ') p++;
16240     }
16241
16242     /* read e.p. field in games that know e.p. capture */
16243     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16244        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16245       if(*p=='-') {
16246         p++; board[EP_STATUS] = EP_NONE;
16247       } else {
16248          char c = *p++ - AAA;
16249
16250          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16251          if(*p >= '0' && *p <='9') p++;
16252          board[EP_STATUS] = c;
16253       }
16254     }
16255
16256
16257     if(sscanf(p, "%d", &i) == 1) {
16258         FENrulePlies = i; /* 50-move ply counter */
16259         /* (The move number is still ignored)    */
16260     }
16261
16262     return TRUE;
16263 }
16264
16265 void
16266 EditPositionPasteFEN(char *fen)
16267 {
16268   if (fen != NULL) {
16269     Board initial_position;
16270
16271     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16272       DisplayError(_("Bad FEN position in clipboard"), 0);
16273       return ;
16274     } else {
16275       int savedBlackPlaysFirst = blackPlaysFirst;
16276       EditPositionEvent();
16277       blackPlaysFirst = savedBlackPlaysFirst;
16278       CopyBoard(boards[0], initial_position);
16279       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16280       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16281       DisplayBothClocks();
16282       DrawPosition(FALSE, boards[currentMove]);
16283     }
16284   }
16285 }
16286
16287 static char cseq[12] = "\\   ";
16288
16289 Boolean set_cont_sequence(char *new_seq)
16290 {
16291     int len;
16292     Boolean ret;
16293
16294     // handle bad attempts to set the sequence
16295         if (!new_seq)
16296                 return 0; // acceptable error - no debug
16297
16298     len = strlen(new_seq);
16299     ret = (len > 0) && (len < sizeof(cseq));
16300     if (ret)
16301       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16302     else if (appData.debugMode)
16303       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16304     return ret;
16305 }
16306
16307 /*
16308     reformat a source message so words don't cross the width boundary.  internal
16309     newlines are not removed.  returns the wrapped size (no null character unless
16310     included in source message).  If dest is NULL, only calculate the size required
16311     for the dest buffer.  lp argument indicats line position upon entry, and it's
16312     passed back upon exit.
16313 */
16314 int wrap(char *dest, char *src, int count, int width, int *lp)
16315 {
16316     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16317
16318     cseq_len = strlen(cseq);
16319     old_line = line = *lp;
16320     ansi = len = clen = 0;
16321
16322     for (i=0; i < count; i++)
16323     {
16324         if (src[i] == '\033')
16325             ansi = 1;
16326
16327         // if we hit the width, back up
16328         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16329         {
16330             // store i & len in case the word is too long
16331             old_i = i, old_len = len;
16332
16333             // find the end of the last word
16334             while (i && src[i] != ' ' && src[i] != '\n')
16335             {
16336                 i--;
16337                 len--;
16338             }
16339
16340             // word too long?  restore i & len before splitting it
16341             if ((old_i-i+clen) >= width)
16342             {
16343                 i = old_i;
16344                 len = old_len;
16345             }
16346
16347             // extra space?
16348             if (i && src[i-1] == ' ')
16349                 len--;
16350
16351             if (src[i] != ' ' && src[i] != '\n')
16352             {
16353                 i--;
16354                 if (len)
16355                     len--;
16356             }
16357
16358             // now append the newline and continuation sequence
16359             if (dest)
16360                 dest[len] = '\n';
16361             len++;
16362             if (dest)
16363                 strncpy(dest+len, cseq, cseq_len);
16364             len += cseq_len;
16365             line = cseq_len;
16366             clen = cseq_len;
16367             continue;
16368         }
16369
16370         if (dest)
16371             dest[len] = src[i];
16372         len++;
16373         if (!ansi)
16374             line++;
16375         if (src[i] == '\n')
16376             line = 0;
16377         if (src[i] == 'm')
16378             ansi = 0;
16379     }
16380     if (dest && appData.debugMode)
16381     {
16382         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16383             count, width, line, len, *lp);
16384         show_bytes(debugFP, src, count);
16385         fprintf(debugFP, "\ndest: ");
16386         show_bytes(debugFP, dest, len);
16387         fprintf(debugFP, "\n");
16388     }
16389     *lp = dest ? line : old_line;
16390
16391     return len;
16392 }
16393
16394 // [HGM] vari: routines for shelving variations
16395
16396 void
16397 PushInner(int firstMove, int lastMove)
16398 {
16399         int i, j, nrMoves = lastMove - firstMove;
16400
16401         // push current tail of game on stack
16402         savedResult[storedGames] = gameInfo.result;
16403         savedDetails[storedGames] = gameInfo.resultDetails;
16404         gameInfo.resultDetails = NULL;
16405         savedFirst[storedGames] = firstMove;
16406         savedLast [storedGames] = lastMove;
16407         savedFramePtr[storedGames] = framePtr;
16408         framePtr -= nrMoves; // reserve space for the boards
16409         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16410             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16411             for(j=0; j<MOVE_LEN; j++)
16412                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16413             for(j=0; j<2*MOVE_LEN; j++)
16414                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16415             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16416             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16417             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16418             pvInfoList[firstMove+i-1].depth = 0;
16419             commentList[framePtr+i] = commentList[firstMove+i];
16420             commentList[firstMove+i] = NULL;
16421         }
16422
16423         storedGames++;
16424         forwardMostMove = firstMove; // truncate game so we can start variation
16425 }
16426
16427 void
16428 PushTail(int firstMove, int lastMove)
16429 {
16430         if(appData.icsActive) { // only in local mode
16431                 forwardMostMove = currentMove; // mimic old ICS behavior
16432                 return;
16433         }
16434         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16435
16436         PushInner(firstMove, lastMove);
16437         if(storedGames == 1) GreyRevert(FALSE);
16438 }
16439
16440 void
16441 PopInner(Boolean annotate)
16442 {
16443         int i, j, nrMoves;
16444         char buf[8000], moveBuf[20];
16445
16446         storedGames--;
16447         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16448         nrMoves = savedLast[storedGames] - currentMove;
16449         if(annotate) {
16450                 int cnt = 10;
16451                 if(!WhiteOnMove(currentMove))
16452                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16453                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16454                 for(i=currentMove; i<forwardMostMove; i++) {
16455                         if(WhiteOnMove(i))
16456                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16457                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16458                         strcat(buf, moveBuf);
16459                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16460                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16461                 }
16462                 strcat(buf, ")");
16463         }
16464         for(i=1; i<=nrMoves; i++) { // copy last variation back
16465             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16466             for(j=0; j<MOVE_LEN; j++)
16467                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16468             for(j=0; j<2*MOVE_LEN; j++)
16469                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16470             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16471             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16472             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16473             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16474             commentList[currentMove+i] = commentList[framePtr+i];
16475             commentList[framePtr+i] = NULL;
16476         }
16477         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16478         framePtr = savedFramePtr[storedGames];
16479         gameInfo.result = savedResult[storedGames];
16480         if(gameInfo.resultDetails != NULL) {
16481             free(gameInfo.resultDetails);
16482       }
16483         gameInfo.resultDetails = savedDetails[storedGames];
16484         forwardMostMove = currentMove + nrMoves;
16485 }
16486
16487 Boolean
16488 PopTail(Boolean annotate)
16489 {
16490         if(appData.icsActive) return FALSE; // only in local mode
16491         if(!storedGames) return FALSE; // sanity
16492         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16493
16494         PopInner(annotate);
16495
16496         if(storedGames == 0) GreyRevert(TRUE);
16497         return TRUE;
16498 }
16499
16500 void
16501 CleanupTail()
16502 {       // remove all shelved variations
16503         int i;
16504         for(i=0; i<storedGames; i++) {
16505             if(savedDetails[i])
16506                 free(savedDetails[i]);
16507             savedDetails[i] = NULL;
16508         }
16509         for(i=framePtr; i<MAX_MOVES; i++) {
16510                 if(commentList[i]) free(commentList[i]);
16511                 commentList[i] = NULL;
16512         }
16513         framePtr = MAX_MOVES-1;
16514         storedGames = 0;
16515 }
16516
16517 void
16518 LoadVariation(int index, char *text)
16519 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16520         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16521         int level = 0, move;
16522
16523         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16524         // first find outermost bracketing variation
16525         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16526             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16527                 if(*p == '{') wait = '}'; else
16528                 if(*p == '[') wait = ']'; else
16529                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16530                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16531             }
16532             if(*p == wait) wait = NULLCHAR; // closing ]} found
16533             p++;
16534         }
16535         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16536         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16537         end[1] = NULLCHAR; // clip off comment beyond variation
16538         ToNrEvent(currentMove-1);
16539         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16540         // kludge: use ParsePV() to append variation to game
16541         move = currentMove;
16542         ParsePV(start, TRUE, TRUE);
16543         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16544         ClearPremoveHighlights();
16545         CommentPopDown();
16546         ToNrEvent(currentMove+1);
16547 }
16548