Fix crash on typing non-existent enginein Load Engine dialog
[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, FILE *f));
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 && 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(params[0]) {
911         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
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     appData.seedBase = random() + (random()<<15);
985     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
986
987     ClearProgramStats();
988     programStats.ok_to_send = 1;
989     programStats.seen_stat = 0;
990
991     /*
992      * Initialize game list
993      */
994     ListNew(&gameList);
995
996
997     /*
998      * Internet chess server status
999      */
1000     if (appData.icsActive) {
1001         appData.matchMode = FALSE;
1002         appData.matchGames = 0;
1003 #if ZIPPY
1004         appData.noChessProgram = !appData.zippyPlay;
1005 #else
1006         appData.zippyPlay = FALSE;
1007         appData.zippyTalk = FALSE;
1008         appData.noChessProgram = TRUE;
1009 #endif
1010         if (*appData.icsHelper != NULLCHAR) {
1011             appData.useTelnet = TRUE;
1012             appData.telnetProgram = appData.icsHelper;
1013         }
1014     } else {
1015         appData.zippyTalk = appData.zippyPlay = FALSE;
1016     }
1017
1018     /* [AS] Initialize pv info list [HGM] and game state */
1019     {
1020         int i, j;
1021
1022         for( i=0; i<=framePtr; i++ ) {
1023             pvInfoList[i].depth = -1;
1024             boards[i][EP_STATUS] = EP_NONE;
1025             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1026         }
1027     }
1028
1029     InitTimeControls();
1030
1031     /* [AS] Adjudication threshold */
1032     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1033
1034     InitEngine(&first, 0);
1035     InitEngine(&second, 1);
1036     CommonEngineInit();
1037
1038     pairing.which = "pairing"; // pairing engine
1039     pairing.pr = NoProc;
1040     pairing.isr = NULL;
1041     pairing.program = appData.pairingEngine;
1042     pairing.host = "localhost";
1043     pairing.dir = ".";
1044
1045     if (appData.icsActive) {
1046         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1047     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1048         appData.clockMode = FALSE;
1049         first.sendTime = second.sendTime = 0;
1050     }
1051
1052 #if ZIPPY
1053     /* Override some settings from environment variables, for backward
1054        compatibility.  Unfortunately it's not feasible to have the env
1055        vars just set defaults, at least in xboard.  Ugh.
1056     */
1057     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1058       ZippyInit();
1059     }
1060 #endif
1061
1062     if (!appData.icsActive) {
1063       char buf[MSG_SIZ];
1064       int len;
1065
1066       /* Check for variants that are supported only in ICS mode,
1067          or not at all.  Some that are accepted here nevertheless
1068          have bugs; see comments below.
1069       */
1070       VariantClass variant = StringToVariant(appData.variant);
1071       switch (variant) {
1072       case VariantBughouse:     /* need four players and two boards */
1073       case VariantKriegspiel:   /* need to hide pieces and move details */
1074         /* case VariantFischeRandom: (Fabien: moved below) */
1075         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1076         if( (len > MSG_SIZ) && appData.debugMode )
1077           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1078
1079         DisplayFatalError(buf, 0, 2);
1080         return;
1081
1082       case VariantUnknown:
1083       case VariantLoadable:
1084       case Variant29:
1085       case Variant30:
1086       case Variant31:
1087       case Variant32:
1088       case Variant33:
1089       case Variant34:
1090       case Variant35:
1091       case Variant36:
1092       default:
1093         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1094         if( (len > MSG_SIZ) && appData.debugMode )
1095           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1096
1097         DisplayFatalError(buf, 0, 2);
1098         return;
1099
1100       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1101       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1102       case VariantGothic:     /* [HGM] should work */
1103       case VariantCapablanca: /* [HGM] should work */
1104       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1105       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1106       case VariantKnightmate: /* [HGM] should work */
1107       case VariantCylinder:   /* [HGM] untested */
1108       case VariantFalcon:     /* [HGM] untested */
1109       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1110                                  offboard interposition not understood */
1111       case VariantNormal:     /* definitely works! */
1112       case VariantWildCastle: /* pieces not automatically shuffled */
1113       case VariantNoCastle:   /* pieces not automatically shuffled */
1114       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1115       case VariantLosers:     /* should work except for win condition,
1116                                  and doesn't know captures are mandatory */
1117       case VariantSuicide:    /* should work except for win condition,
1118                                  and doesn't know captures are mandatory */
1119       case VariantGiveaway:   /* should work except for win condition,
1120                                  and doesn't know captures are mandatory */
1121       case VariantTwoKings:   /* should work */
1122       case VariantAtomic:     /* should work except for win condition */
1123       case Variant3Check:     /* should work except for win condition */
1124       case VariantShatranj:   /* should work except for all win conditions */
1125       case VariantMakruk:     /* should work except for draw countdown */
1126       case VariantBerolina:   /* might work if TestLegality is off */
1127       case VariantCapaRandom: /* should work */
1128       case VariantJanus:      /* should work */
1129       case VariantSuper:      /* experimental */
1130       case VariantGreat:      /* experimental, requires legality testing to be off */
1131       case VariantSChess:     /* S-Chess, should work */
1132       case VariantGrand:      /* should work */
1133       case VariantSpartan:    /* should work */
1134         break;
1135       }
1136     }
1137
1138 }
1139
1140 int NextIntegerFromString( char ** str, long * value )
1141 {
1142     int result = -1;
1143     char * s = *str;
1144
1145     while( *s == ' ' || *s == '\t' ) {
1146         s++;
1147     }
1148
1149     *value = 0;
1150
1151     if( *s >= '0' && *s <= '9' ) {
1152         while( *s >= '0' && *s <= '9' ) {
1153             *value = *value * 10 + (*s - '0');
1154             s++;
1155         }
1156
1157         result = 0;
1158     }
1159
1160     *str = s;
1161
1162     return result;
1163 }
1164
1165 int NextTimeControlFromString( char ** str, long * value )
1166 {
1167     long temp;
1168     int result = NextIntegerFromString( str, &temp );
1169
1170     if( result == 0 ) {
1171         *value = temp * 60; /* Minutes */
1172         if( **str == ':' ) {
1173             (*str)++;
1174             result = NextIntegerFromString( str, &temp );
1175             *value += temp; /* Seconds */
1176         }
1177     }
1178
1179     return result;
1180 }
1181
1182 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1183 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1184     int result = -1, type = 0; long temp, temp2;
1185
1186     if(**str != ':') return -1; // old params remain in force!
1187     (*str)++;
1188     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1189     if( NextIntegerFromString( str, &temp ) ) return -1;
1190     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1191
1192     if(**str != '/') {
1193         /* time only: incremental or sudden-death time control */
1194         if(**str == '+') { /* increment follows; read it */
1195             (*str)++;
1196             if(**str == '!') type = *(*str)++; // Bronstein TC
1197             if(result = NextIntegerFromString( str, &temp2)) return -1;
1198             *inc = temp2 * 1000;
1199             if(**str == '.') { // read fraction of increment
1200                 char *start = ++(*str);
1201                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1202                 temp2 *= 1000;
1203                 while(start++ < *str) temp2 /= 10;
1204                 *inc += temp2;
1205             }
1206         } else *inc = 0;
1207         *moves = 0; *tc = temp * 1000; *incType = type;
1208         return 0;
1209     }
1210
1211     (*str)++; /* classical time control */
1212     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1213
1214     if(result == 0) {
1215         *moves = temp;
1216         *tc    = temp2 * 1000;
1217         *inc   = 0;
1218         *incType = type;
1219     }
1220     return result;
1221 }
1222
1223 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1224 {   /* [HGM] get time to add from the multi-session time-control string */
1225     int incType, moves=1; /* kludge to force reading of first session */
1226     long time, increment;
1227     char *s = tcString;
1228
1229     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1230     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1231     do {
1232         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1233         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1234         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1235         if(movenr == -1) return time;    /* last move before new session     */
1236         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1237         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1238         if(!moves) return increment;     /* current session is incremental   */
1239         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1240     } while(movenr >= -1);               /* try again for next session       */
1241
1242     return 0; // no new time quota on this move
1243 }
1244
1245 int
1246 ParseTimeControl(tc, ti, mps)
1247      char *tc;
1248      float ti;
1249      int mps;
1250 {
1251   long tc1;
1252   long tc2;
1253   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1254   int min, sec=0;
1255
1256   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1257   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1258       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1259   if(ti > 0) {
1260
1261     if(mps)
1262       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1263     else 
1264       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1265   } else {
1266     if(mps)
1267       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1268     else 
1269       snprintf(buf, MSG_SIZ, ":%s", mytc);
1270   }
1271   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1272   
1273   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1274     return FALSE;
1275   }
1276
1277   if( *tc == '/' ) {
1278     /* Parse second time control */
1279     tc++;
1280
1281     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1282       return FALSE;
1283     }
1284
1285     if( tc2 == 0 ) {
1286       return FALSE;
1287     }
1288
1289     timeControl_2 = tc2 * 1000;
1290   }
1291   else {
1292     timeControl_2 = 0;
1293   }
1294
1295   if( tc1 == 0 ) {
1296     return FALSE;
1297   }
1298
1299   timeControl = tc1 * 1000;
1300
1301   if (ti >= 0) {
1302     timeIncrement = ti * 1000;  /* convert to ms */
1303     movesPerSession = 0;
1304   } else {
1305     timeIncrement = 0;
1306     movesPerSession = mps;
1307   }
1308   return TRUE;
1309 }
1310
1311 void
1312 InitBackEnd2()
1313 {
1314     if (appData.debugMode) {
1315         fprintf(debugFP, "%s\n", programVersion);
1316     }
1317
1318     set_cont_sequence(appData.wrapContSeq);
1319     if (appData.matchGames > 0) {
1320         appData.matchMode = TRUE;
1321     } else if (appData.matchMode) {
1322         appData.matchGames = 1;
1323     }
1324     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1325         appData.matchGames = appData.sameColorGames;
1326     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1327         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1328         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1329     }
1330     Reset(TRUE, FALSE);
1331     if (appData.noChessProgram || first.protocolVersion == 1) {
1332       InitBackEnd3();
1333     } else {
1334       /* kludge: allow timeout for initial "feature" commands */
1335       FreezeUI();
1336       DisplayMessage("", _("Starting chess program"));
1337       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1338     }
1339 }
1340
1341 int
1342 CalculateIndex(int index, int gameNr)
1343 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1344     int res;
1345     if(index > 0) return index; // fixed nmber
1346     if(index == 0) return 1;
1347     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1348     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1349     return res;
1350 }
1351
1352 int
1353 LoadGameOrPosition(int gameNr)
1354 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1355     if (*appData.loadGameFile != NULLCHAR) {
1356         if (!LoadGameFromFile(appData.loadGameFile,
1357                 CalculateIndex(appData.loadGameIndex, gameNr),
1358                               appData.loadGameFile, FALSE)) {
1359             DisplayFatalError(_("Bad game file"), 0, 1);
1360             return 0;
1361         }
1362     } else if (*appData.loadPositionFile != NULLCHAR) {
1363         if (!LoadPositionFromFile(appData.loadPositionFile,
1364                 CalculateIndex(appData.loadPositionIndex, gameNr),
1365                                   appData.loadPositionFile)) {
1366             DisplayFatalError(_("Bad position file"), 0, 1);
1367             return 0;
1368         }
1369     }
1370     return 1;
1371 }
1372
1373 void
1374 ReserveGame(int gameNr, char resChar)
1375 {
1376     FILE *tf = fopen(appData.tourneyFile, "r+");
1377     char *p, *q, c, buf[MSG_SIZ];
1378     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1379     safeStrCpy(buf, lastMsg, MSG_SIZ);
1380     DisplayMessage(_("Pick new game"), "");
1381     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1382     ParseArgsFromFile(tf);
1383     p = q = appData.results;
1384     if(appData.debugMode) {
1385       char *r = appData.participants;
1386       fprintf(debugFP, "results = '%s'\n", p);
1387       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1388       fprintf(debugFP, "\n");
1389     }
1390     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1391     nextGame = q - p;
1392     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1393     safeStrCpy(q, p, strlen(p) + 2);
1394     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1395     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1396     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1397         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1398         q[nextGame] = '*';
1399     }
1400     fseek(tf, -(strlen(p)+4), SEEK_END);
1401     c = fgetc(tf);
1402     if(c != '"') // depending on DOS or Unix line endings we can be one off
1403          fseek(tf, -(strlen(p)+2), SEEK_END);
1404     else fseek(tf, -(strlen(p)+3), SEEK_END);
1405     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1406     DisplayMessage(buf, "");
1407     free(p); appData.results = q;
1408     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1409        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1410         UnloadEngine(&first);  // next game belongs to other pairing;
1411         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1412     }
1413 }
1414
1415 void
1416 MatchEvent(int mode)
1417 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1418         int dummy;
1419         if(matchMode) { // already in match mode: switch it off
1420             abortMatch = TRUE;
1421             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1422             return;
1423         }
1424 //      if(gameMode != BeginningOfGame) {
1425 //          DisplayError(_("You can only start a match from the initial position."), 0);
1426 //          return;
1427 //      }
1428         abortMatch = FALSE;
1429         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1430         /* Set up machine vs. machine match */
1431         nextGame = 0;
1432         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1433         if(appData.tourneyFile[0]) {
1434             ReserveGame(-1, 0);
1435             if(nextGame > appData.matchGames) {
1436                 char buf[MSG_SIZ];
1437                 if(strchr(appData.results, '*') == NULL) {
1438                     FILE *f;
1439                     appData.tourneyCycles++;
1440                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1441                         fclose(f);
1442                         NextTourneyGame(-1, &dummy);
1443                         ReserveGame(-1, 0);
1444                         if(nextGame <= appData.matchGames) {
1445                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1446                             matchMode = mode;
1447                             ScheduleDelayedEvent(NextMatchGame, 10000);
1448                             return;
1449                         }
1450                     }
1451                 }
1452                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1453                 DisplayError(buf, 0);
1454                 appData.tourneyFile[0] = 0;
1455                 return;
1456             }
1457         } else
1458         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1459             DisplayFatalError(_("Can't have a match with no chess programs"),
1460                               0, 2);
1461             return;
1462         }
1463         matchMode = mode;
1464         matchGame = roundNr = 1;
1465         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1466         NextMatchGame();
1467 }
1468
1469 void
1470 InitBackEnd3 P((void))
1471 {
1472     GameMode initialMode;
1473     char buf[MSG_SIZ];
1474     int err, len;
1475
1476     InitChessProgram(&first, startedFromSetupPosition);
1477
1478     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1479         free(programVersion);
1480         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1481         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1482     }
1483
1484     if (appData.icsActive) {
1485 #ifdef WIN32
1486         /* [DM] Make a console window if needed [HGM] merged ifs */
1487         ConsoleCreate();
1488 #endif
1489         err = establish();
1490         if (err != 0)
1491           {
1492             if (*appData.icsCommPort != NULLCHAR)
1493               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1494                              appData.icsCommPort);
1495             else
1496               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1497                         appData.icsHost, appData.icsPort);
1498
1499             if( (len > MSG_SIZ) && appData.debugMode )
1500               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1501
1502             DisplayFatalError(buf, err, 1);
1503             return;
1504         }
1505         SetICSMode();
1506         telnetISR =
1507           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1508         fromUserISR =
1509           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1510         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1511             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1512     } else if (appData.noChessProgram) {
1513         SetNCPMode();
1514     } else {
1515         SetGNUMode();
1516     }
1517
1518     if (*appData.cmailGameName != NULLCHAR) {
1519         SetCmailMode();
1520         OpenLoopback(&cmailPR);
1521         cmailISR =
1522           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1523     }
1524
1525     ThawUI();
1526     DisplayMessage("", "");
1527     if (StrCaseCmp(appData.initialMode, "") == 0) {
1528       initialMode = BeginningOfGame;
1529       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1530         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1531         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1532         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1533         ModeHighlight();
1534       }
1535     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1536       initialMode = TwoMachinesPlay;
1537     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1538       initialMode = AnalyzeFile;
1539     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1540       initialMode = AnalyzeMode;
1541     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1542       initialMode = MachinePlaysWhite;
1543     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1544       initialMode = MachinePlaysBlack;
1545     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1546       initialMode = EditGame;
1547     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1548       initialMode = EditPosition;
1549     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1550       initialMode = Training;
1551     } else {
1552       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1553       if( (len > MSG_SIZ) && appData.debugMode )
1554         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1555
1556       DisplayFatalError(buf, 0, 2);
1557       return;
1558     }
1559
1560     if (appData.matchMode) {
1561         if(appData.tourneyFile[0]) { // start tourney from command line
1562             FILE *f;
1563             if(f = fopen(appData.tourneyFile, "r")) {
1564                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1565                 fclose(f);
1566                 appData.clockMode = TRUE;
1567                 SetGNUMode();
1568             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1569         }
1570         MatchEvent(TRUE);
1571     } else if (*appData.cmailGameName != NULLCHAR) {
1572         /* Set up cmail mode */
1573         ReloadCmailMsgEvent(TRUE);
1574     } else {
1575         /* Set up other modes */
1576         if (initialMode == AnalyzeFile) {
1577           if (*appData.loadGameFile == NULLCHAR) {
1578             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1579             return;
1580           }
1581         }
1582         if (*appData.loadGameFile != NULLCHAR) {
1583             (void) LoadGameFromFile(appData.loadGameFile,
1584                                     appData.loadGameIndex,
1585                                     appData.loadGameFile, TRUE);
1586         } else if (*appData.loadPositionFile != NULLCHAR) {
1587             (void) LoadPositionFromFile(appData.loadPositionFile,
1588                                         appData.loadPositionIndex,
1589                                         appData.loadPositionFile);
1590             /* [HGM] try to make self-starting even after FEN load */
1591             /* to allow automatic setup of fairy variants with wtm */
1592             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1593                 gameMode = BeginningOfGame;
1594                 setboardSpoiledMachineBlack = 1;
1595             }
1596             /* [HGM] loadPos: make that every new game uses the setup */
1597             /* from file as long as we do not switch variant          */
1598             if(!blackPlaysFirst) {
1599                 startedFromPositionFile = TRUE;
1600                 CopyBoard(filePosition, boards[0]);
1601             }
1602         }
1603         if (initialMode == AnalyzeMode) {
1604           if (appData.noChessProgram) {
1605             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1606             return;
1607           }
1608           if (appData.icsActive) {
1609             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1610             return;
1611           }
1612           AnalyzeModeEvent();
1613         } else if (initialMode == AnalyzeFile) {
1614           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1615           ShowThinkingEvent();
1616           AnalyzeFileEvent();
1617           AnalysisPeriodicEvent(1);
1618         } else if (initialMode == MachinePlaysWhite) {
1619           if (appData.noChessProgram) {
1620             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1621                               0, 2);
1622             return;
1623           }
1624           if (appData.icsActive) {
1625             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1626                               0, 2);
1627             return;
1628           }
1629           MachineWhiteEvent();
1630         } else if (initialMode == MachinePlaysBlack) {
1631           if (appData.noChessProgram) {
1632             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1633                               0, 2);
1634             return;
1635           }
1636           if (appData.icsActive) {
1637             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1638                               0, 2);
1639             return;
1640           }
1641           MachineBlackEvent();
1642         } else if (initialMode == TwoMachinesPlay) {
1643           if (appData.noChessProgram) {
1644             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1645                               0, 2);
1646             return;
1647           }
1648           if (appData.icsActive) {
1649             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1650                               0, 2);
1651             return;
1652           }
1653           TwoMachinesEvent();
1654         } else if (initialMode == EditGame) {
1655           EditGameEvent();
1656         } else if (initialMode == EditPosition) {
1657           EditPositionEvent();
1658         } else if (initialMode == Training) {
1659           if (*appData.loadGameFile == NULLCHAR) {
1660             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1661             return;
1662           }
1663           TrainingEvent();
1664         }
1665     }
1666 }
1667
1668 /*
1669  * Establish will establish a contact to a remote host.port.
1670  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1671  *  used to talk to the host.
1672  * Returns 0 if okay, error code if not.
1673  */
1674 int
1675 establish()
1676 {
1677     char buf[MSG_SIZ];
1678
1679     if (*appData.icsCommPort != NULLCHAR) {
1680         /* Talk to the host through a serial comm port */
1681         return OpenCommPort(appData.icsCommPort, &icsPR);
1682
1683     } else if (*appData.gateway != NULLCHAR) {
1684         if (*appData.remoteShell == NULLCHAR) {
1685             /* Use the rcmd protocol to run telnet program on a gateway host */
1686             snprintf(buf, sizeof(buf), "%s %s %s",
1687                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1688             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1689
1690         } else {
1691             /* Use the rsh program to run telnet program on a gateway host */
1692             if (*appData.remoteUser == NULLCHAR) {
1693                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1694                         appData.gateway, appData.telnetProgram,
1695                         appData.icsHost, appData.icsPort);
1696             } else {
1697                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1698                         appData.remoteShell, appData.gateway,
1699                         appData.remoteUser, appData.telnetProgram,
1700                         appData.icsHost, appData.icsPort);
1701             }
1702             return StartChildProcess(buf, "", &icsPR);
1703
1704         }
1705     } else if (appData.useTelnet) {
1706         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1707
1708     } else {
1709         /* TCP socket interface differs somewhat between
1710            Unix and NT; handle details in the front end.
1711            */
1712         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1713     }
1714 }
1715
1716 void EscapeExpand(char *p, char *q)
1717 {       // [HGM] initstring: routine to shape up string arguments
1718         while(*p++ = *q++) if(p[-1] == '\\')
1719             switch(*q++) {
1720                 case 'n': p[-1] = '\n'; break;
1721                 case 'r': p[-1] = '\r'; break;
1722                 case 't': p[-1] = '\t'; break;
1723                 case '\\': p[-1] = '\\'; break;
1724                 case 0: *p = 0; return;
1725                 default: p[-1] = q[-1]; break;
1726             }
1727 }
1728
1729 void
1730 show_bytes(fp, buf, count)
1731      FILE *fp;
1732      char *buf;
1733      int count;
1734 {
1735     while (count--) {
1736         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1737             fprintf(fp, "\\%03o", *buf & 0xff);
1738         } else {
1739             putc(*buf, fp);
1740         }
1741         buf++;
1742     }
1743     fflush(fp);
1744 }
1745
1746 /* Returns an errno value */
1747 int
1748 OutputMaybeTelnet(pr, message, count, outError)
1749      ProcRef pr;
1750      char *message;
1751      int count;
1752      int *outError;
1753 {
1754     char buf[8192], *p, *q, *buflim;
1755     int left, newcount, outcount;
1756
1757     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1758         *appData.gateway != NULLCHAR) {
1759         if (appData.debugMode) {
1760             fprintf(debugFP, ">ICS: ");
1761             show_bytes(debugFP, message, count);
1762             fprintf(debugFP, "\n");
1763         }
1764         return OutputToProcess(pr, message, count, outError);
1765     }
1766
1767     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1768     p = message;
1769     q = buf;
1770     left = count;
1771     newcount = 0;
1772     while (left) {
1773         if (q >= buflim) {
1774             if (appData.debugMode) {
1775                 fprintf(debugFP, ">ICS: ");
1776                 show_bytes(debugFP, buf, newcount);
1777                 fprintf(debugFP, "\n");
1778             }
1779             outcount = OutputToProcess(pr, buf, newcount, outError);
1780             if (outcount < newcount) return -1; /* to be sure */
1781             q = buf;
1782             newcount = 0;
1783         }
1784         if (*p == '\n') {
1785             *q++ = '\r';
1786             newcount++;
1787         } else if (((unsigned char) *p) == TN_IAC) {
1788             *q++ = (char) TN_IAC;
1789             newcount ++;
1790         }
1791         *q++ = *p++;
1792         newcount++;
1793         left--;
1794     }
1795     if (appData.debugMode) {
1796         fprintf(debugFP, ">ICS: ");
1797         show_bytes(debugFP, buf, newcount);
1798         fprintf(debugFP, "\n");
1799     }
1800     outcount = OutputToProcess(pr, buf, newcount, outError);
1801     if (outcount < newcount) return -1; /* to be sure */
1802     return count;
1803 }
1804
1805 void
1806 read_from_player(isr, closure, message, count, error)
1807      InputSourceRef isr;
1808      VOIDSTAR closure;
1809      char *message;
1810      int count;
1811      int error;
1812 {
1813     int outError, outCount;
1814     static int gotEof = 0;
1815
1816     /* Pass data read from player on to ICS */
1817     if (count > 0) {
1818         gotEof = 0;
1819         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1820         if (outCount < count) {
1821             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1822         }
1823     } else if (count < 0) {
1824         RemoveInputSource(isr);
1825         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1826     } else if (gotEof++ > 0) {
1827         RemoveInputSource(isr);
1828         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1829     }
1830 }
1831
1832 void
1833 KeepAlive()
1834 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1835     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1836     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1837     SendToICS("date\n");
1838     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1839 }
1840
1841 /* added routine for printf style output to ics */
1842 void ics_printf(char *format, ...)
1843 {
1844     char buffer[MSG_SIZ];
1845     va_list args;
1846
1847     va_start(args, format);
1848     vsnprintf(buffer, sizeof(buffer), format, args);
1849     buffer[sizeof(buffer)-1] = '\0';
1850     SendToICS(buffer);
1851     va_end(args);
1852 }
1853
1854 void
1855 SendToICS(s)
1856      char *s;
1857 {
1858     int count, outCount, outError;
1859
1860     if (icsPR == NULL) return;
1861
1862     count = strlen(s);
1863     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1864     if (outCount < count) {
1865         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1866     }
1867 }
1868
1869 /* This is used for sending logon scripts to the ICS. Sending
1870    without a delay causes problems when using timestamp on ICC
1871    (at least on my machine). */
1872 void
1873 SendToICSDelayed(s,msdelay)
1874      char *s;
1875      long msdelay;
1876 {
1877     int count, outCount, outError;
1878
1879     if (icsPR == NULL) return;
1880
1881     count = strlen(s);
1882     if (appData.debugMode) {
1883         fprintf(debugFP, ">ICS: ");
1884         show_bytes(debugFP, s, count);
1885         fprintf(debugFP, "\n");
1886     }
1887     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1888                                       msdelay);
1889     if (outCount < count) {
1890         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1891     }
1892 }
1893
1894
1895 /* Remove all highlighting escape sequences in s
1896    Also deletes any suffix starting with '('
1897    */
1898 char *
1899 StripHighlightAndTitle(s)
1900      char *s;
1901 {
1902     static char retbuf[MSG_SIZ];
1903     char *p = retbuf;
1904
1905     while (*s != NULLCHAR) {
1906         while (*s == '\033') {
1907             while (*s != NULLCHAR && !isalpha(*s)) s++;
1908             if (*s != NULLCHAR) s++;
1909         }
1910         while (*s != NULLCHAR && *s != '\033') {
1911             if (*s == '(' || *s == '[') {
1912                 *p = NULLCHAR;
1913                 return retbuf;
1914             }
1915             *p++ = *s++;
1916         }
1917     }
1918     *p = NULLCHAR;
1919     return retbuf;
1920 }
1921
1922 /* Remove all highlighting escape sequences in s */
1923 char *
1924 StripHighlight(s)
1925      char *s;
1926 {
1927     static char retbuf[MSG_SIZ];
1928     char *p = retbuf;
1929
1930     while (*s != NULLCHAR) {
1931         while (*s == '\033') {
1932             while (*s != NULLCHAR && !isalpha(*s)) s++;
1933             if (*s != NULLCHAR) s++;
1934         }
1935         while (*s != NULLCHAR && *s != '\033') {
1936             *p++ = *s++;
1937         }
1938     }
1939     *p = NULLCHAR;
1940     return retbuf;
1941 }
1942
1943 char *variantNames[] = VARIANT_NAMES;
1944 char *
1945 VariantName(v)
1946      VariantClass v;
1947 {
1948     return variantNames[v];
1949 }
1950
1951
1952 /* Identify a variant from the strings the chess servers use or the
1953    PGN Variant tag names we use. */
1954 VariantClass
1955 StringToVariant(e)
1956      char *e;
1957 {
1958     char *p;
1959     int wnum = -1;
1960     VariantClass v = VariantNormal;
1961     int i, found = FALSE;
1962     char buf[MSG_SIZ];
1963     int len;
1964
1965     if (!e) return v;
1966
1967     /* [HGM] skip over optional board-size prefixes */
1968     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1969         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1970         while( *e++ != '_');
1971     }
1972
1973     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1974         v = VariantNormal;
1975         found = TRUE;
1976     } else
1977     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1978       if (StrCaseStr(e, variantNames[i])) {
1979         v = (VariantClass) i;
1980         found = TRUE;
1981         break;
1982       }
1983     }
1984
1985     if (!found) {
1986       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1987           || StrCaseStr(e, "wild/fr")
1988           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1989         v = VariantFischeRandom;
1990       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1991                  (i = 1, p = StrCaseStr(e, "w"))) {
1992         p += i;
1993         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1994         if (isdigit(*p)) {
1995           wnum = atoi(p);
1996         } else {
1997           wnum = -1;
1998         }
1999         switch (wnum) {
2000         case 0: /* FICS only, actually */
2001         case 1:
2002           /* Castling legal even if K starts on d-file */
2003           v = VariantWildCastle;
2004           break;
2005         case 2:
2006         case 3:
2007         case 4:
2008           /* Castling illegal even if K & R happen to start in
2009              normal positions. */
2010           v = VariantNoCastle;
2011           break;
2012         case 5:
2013         case 7:
2014         case 8:
2015         case 10:
2016         case 11:
2017         case 12:
2018         case 13:
2019         case 14:
2020         case 15:
2021         case 18:
2022         case 19:
2023           /* Castling legal iff K & R start in normal positions */
2024           v = VariantNormal;
2025           break;
2026         case 6:
2027         case 20:
2028         case 21:
2029           /* Special wilds for position setup; unclear what to do here */
2030           v = VariantLoadable;
2031           break;
2032         case 9:
2033           /* Bizarre ICC game */
2034           v = VariantTwoKings;
2035           break;
2036         case 16:
2037           v = VariantKriegspiel;
2038           break;
2039         case 17:
2040           v = VariantLosers;
2041           break;
2042         case 22:
2043           v = VariantFischeRandom;
2044           break;
2045         case 23:
2046           v = VariantCrazyhouse;
2047           break;
2048         case 24:
2049           v = VariantBughouse;
2050           break;
2051         case 25:
2052           v = Variant3Check;
2053           break;
2054         case 26:
2055           /* Not quite the same as FICS suicide! */
2056           v = VariantGiveaway;
2057           break;
2058         case 27:
2059           v = VariantAtomic;
2060           break;
2061         case 28:
2062           v = VariantShatranj;
2063           break;
2064
2065         /* Temporary names for future ICC types.  The name *will* change in
2066            the next xboard/WinBoard release after ICC defines it. */
2067         case 29:
2068           v = Variant29;
2069           break;
2070         case 30:
2071           v = Variant30;
2072           break;
2073         case 31:
2074           v = Variant31;
2075           break;
2076         case 32:
2077           v = Variant32;
2078           break;
2079         case 33:
2080           v = Variant33;
2081           break;
2082         case 34:
2083           v = Variant34;
2084           break;
2085         case 35:
2086           v = Variant35;
2087           break;
2088         case 36:
2089           v = Variant36;
2090           break;
2091         case 37:
2092           v = VariantShogi;
2093           break;
2094         case 38:
2095           v = VariantXiangqi;
2096           break;
2097         case 39:
2098           v = VariantCourier;
2099           break;
2100         case 40:
2101           v = VariantGothic;
2102           break;
2103         case 41:
2104           v = VariantCapablanca;
2105           break;
2106         case 42:
2107           v = VariantKnightmate;
2108           break;
2109         case 43:
2110           v = VariantFairy;
2111           break;
2112         case 44:
2113           v = VariantCylinder;
2114           break;
2115         case 45:
2116           v = VariantFalcon;
2117           break;
2118         case 46:
2119           v = VariantCapaRandom;
2120           break;
2121         case 47:
2122           v = VariantBerolina;
2123           break;
2124         case 48:
2125           v = VariantJanus;
2126           break;
2127         case 49:
2128           v = VariantSuper;
2129           break;
2130         case 50:
2131           v = VariantGreat;
2132           break;
2133         case -1:
2134           /* Found "wild" or "w" in the string but no number;
2135              must assume it's normal chess. */
2136           v = VariantNormal;
2137           break;
2138         default:
2139           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2140           if( (len > MSG_SIZ) && appData.debugMode )
2141             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2142
2143           DisplayError(buf, 0);
2144           v = VariantUnknown;
2145           break;
2146         }
2147       }
2148     }
2149     if (appData.debugMode) {
2150       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2151               e, wnum, VariantName(v));
2152     }
2153     return v;
2154 }
2155
2156 static int leftover_start = 0, leftover_len = 0;
2157 char star_match[STAR_MATCH_N][MSG_SIZ];
2158
2159 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2160    advance *index beyond it, and set leftover_start to the new value of
2161    *index; else return FALSE.  If pattern contains the character '*', it
2162    matches any sequence of characters not containing '\r', '\n', or the
2163    character following the '*' (if any), and the matched sequence(s) are
2164    copied into star_match.
2165    */
2166 int
2167 looking_at(buf, index, pattern)
2168      char *buf;
2169      int *index;
2170      char *pattern;
2171 {
2172     char *bufp = &buf[*index], *patternp = pattern;
2173     int star_count = 0;
2174     char *matchp = star_match[0];
2175
2176     for (;;) {
2177         if (*patternp == NULLCHAR) {
2178             *index = leftover_start = bufp - buf;
2179             *matchp = NULLCHAR;
2180             return TRUE;
2181         }
2182         if (*bufp == NULLCHAR) return FALSE;
2183         if (*patternp == '*') {
2184             if (*bufp == *(patternp + 1)) {
2185                 *matchp = NULLCHAR;
2186                 matchp = star_match[++star_count];
2187                 patternp += 2;
2188                 bufp++;
2189                 continue;
2190             } else if (*bufp == '\n' || *bufp == '\r') {
2191                 patternp++;
2192                 if (*patternp == NULLCHAR)
2193                   continue;
2194                 else
2195                   return FALSE;
2196             } else {
2197                 *matchp++ = *bufp++;
2198                 continue;
2199             }
2200         }
2201         if (*patternp != *bufp) return FALSE;
2202         patternp++;
2203         bufp++;
2204     }
2205 }
2206
2207 void
2208 SendToPlayer(data, length)
2209      char *data;
2210      int length;
2211 {
2212     int error, outCount;
2213     outCount = OutputToProcess(NoProc, data, length, &error);
2214     if (outCount < length) {
2215         DisplayFatalError(_("Error writing to display"), error, 1);
2216     }
2217 }
2218
2219 void
2220 PackHolding(packed, holding)
2221      char packed[];
2222      char *holding;
2223 {
2224     char *p = holding;
2225     char *q = packed;
2226     int runlength = 0;
2227     int curr = 9999;
2228     do {
2229         if (*p == curr) {
2230             runlength++;
2231         } else {
2232             switch (runlength) {
2233               case 0:
2234                 break;
2235               case 1:
2236                 *q++ = curr;
2237                 break;
2238               case 2:
2239                 *q++ = curr;
2240                 *q++ = curr;
2241                 break;
2242               default:
2243                 sprintf(q, "%d", runlength);
2244                 while (*q) q++;
2245                 *q++ = curr;
2246                 break;
2247             }
2248             runlength = 1;
2249             curr = *p;
2250         }
2251     } while (*p++);
2252     *q = NULLCHAR;
2253 }
2254
2255 /* Telnet protocol requests from the front end */
2256 void
2257 TelnetRequest(ddww, option)
2258      unsigned char ddww, option;
2259 {
2260     unsigned char msg[3];
2261     int outCount, outError;
2262
2263     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2264
2265     if (appData.debugMode) {
2266         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2267         switch (ddww) {
2268           case TN_DO:
2269             ddwwStr = "DO";
2270             break;
2271           case TN_DONT:
2272             ddwwStr = "DONT";
2273             break;
2274           case TN_WILL:
2275             ddwwStr = "WILL";
2276             break;
2277           case TN_WONT:
2278             ddwwStr = "WONT";
2279             break;
2280           default:
2281             ddwwStr = buf1;
2282             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2283             break;
2284         }
2285         switch (option) {
2286           case TN_ECHO:
2287             optionStr = "ECHO";
2288             break;
2289           default:
2290             optionStr = buf2;
2291             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2292             break;
2293         }
2294         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2295     }
2296     msg[0] = TN_IAC;
2297     msg[1] = ddww;
2298     msg[2] = option;
2299     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2300     if (outCount < 3) {
2301         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2302     }
2303 }
2304
2305 void
2306 DoEcho()
2307 {
2308     if (!appData.icsActive) return;
2309     TelnetRequest(TN_DO, TN_ECHO);
2310 }
2311
2312 void
2313 DontEcho()
2314 {
2315     if (!appData.icsActive) return;
2316     TelnetRequest(TN_DONT, TN_ECHO);
2317 }
2318
2319 void
2320 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2321 {
2322     /* put the holdings sent to us by the server on the board holdings area */
2323     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2324     char p;
2325     ChessSquare piece;
2326
2327     if(gameInfo.holdingsWidth < 2)  return;
2328     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2329         return; // prevent overwriting by pre-board holdings
2330
2331     if( (int)lowestPiece >= BlackPawn ) {
2332         holdingsColumn = 0;
2333         countsColumn = 1;
2334         holdingsStartRow = BOARD_HEIGHT-1;
2335         direction = -1;
2336     } else {
2337         holdingsColumn = BOARD_WIDTH-1;
2338         countsColumn = BOARD_WIDTH-2;
2339         holdingsStartRow = 0;
2340         direction = 1;
2341     }
2342
2343     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2344         board[i][holdingsColumn] = EmptySquare;
2345         board[i][countsColumn]   = (ChessSquare) 0;
2346     }
2347     while( (p=*holdings++) != NULLCHAR ) {
2348         piece = CharToPiece( ToUpper(p) );
2349         if(piece == EmptySquare) continue;
2350         /*j = (int) piece - (int) WhitePawn;*/
2351         j = PieceToNumber(piece);
2352         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2353         if(j < 0) continue;               /* should not happen */
2354         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2355         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2356         board[holdingsStartRow+j*direction][countsColumn]++;
2357     }
2358 }
2359
2360
2361 void
2362 VariantSwitch(Board board, VariantClass newVariant)
2363 {
2364    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2365    static Board oldBoard;
2366
2367    startedFromPositionFile = FALSE;
2368    if(gameInfo.variant == newVariant) return;
2369
2370    /* [HGM] This routine is called each time an assignment is made to
2371     * gameInfo.variant during a game, to make sure the board sizes
2372     * are set to match the new variant. If that means adding or deleting
2373     * holdings, we shift the playing board accordingly
2374     * This kludge is needed because in ICS observe mode, we get boards
2375     * of an ongoing game without knowing the variant, and learn about the
2376     * latter only later. This can be because of the move list we requested,
2377     * in which case the game history is refilled from the beginning anyway,
2378     * but also when receiving holdings of a crazyhouse game. In the latter
2379     * case we want to add those holdings to the already received position.
2380     */
2381
2382
2383    if (appData.debugMode) {
2384      fprintf(debugFP, "Switch board from %s to %s\n",
2385              VariantName(gameInfo.variant), VariantName(newVariant));
2386      setbuf(debugFP, NULL);
2387    }
2388    shuffleOpenings = 0;       /* [HGM] shuffle */
2389    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2390    switch(newVariant)
2391      {
2392      case VariantShogi:
2393        newWidth = 9;  newHeight = 9;
2394        gameInfo.holdingsSize = 7;
2395      case VariantBughouse:
2396      case VariantCrazyhouse:
2397        newHoldingsWidth = 2; break;
2398      case VariantGreat:
2399        newWidth = 10;
2400      case VariantSuper:
2401        newHoldingsWidth = 2;
2402        gameInfo.holdingsSize = 8;
2403        break;
2404      case VariantGothic:
2405      case VariantCapablanca:
2406      case VariantCapaRandom:
2407        newWidth = 10;
2408      default:
2409        newHoldingsWidth = gameInfo.holdingsSize = 0;
2410      };
2411
2412    if(newWidth  != gameInfo.boardWidth  ||
2413       newHeight != gameInfo.boardHeight ||
2414       newHoldingsWidth != gameInfo.holdingsWidth ) {
2415
2416      /* shift position to new playing area, if needed */
2417      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2418        for(i=0; i<BOARD_HEIGHT; i++)
2419          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2420            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2421              board[i][j];
2422        for(i=0; i<newHeight; i++) {
2423          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2424          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2425        }
2426      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2427        for(i=0; i<BOARD_HEIGHT; i++)
2428          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2429            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2430              board[i][j];
2431      }
2432      gameInfo.boardWidth  = newWidth;
2433      gameInfo.boardHeight = newHeight;
2434      gameInfo.holdingsWidth = newHoldingsWidth;
2435      gameInfo.variant = newVariant;
2436      InitDrawingSizes(-2, 0);
2437    } else gameInfo.variant = newVariant;
2438    CopyBoard(oldBoard, board);   // remember correctly formatted board
2439      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2440    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2441 }
2442
2443 static int loggedOn = FALSE;
2444
2445 /*-- Game start info cache: --*/
2446 int gs_gamenum;
2447 char gs_kind[MSG_SIZ];
2448 static char player1Name[128] = "";
2449 static char player2Name[128] = "";
2450 static char cont_seq[] = "\n\\   ";
2451 static int player1Rating = -1;
2452 static int player2Rating = -1;
2453 /*----------------------------*/
2454
2455 ColorClass curColor = ColorNormal;
2456 int suppressKibitz = 0;
2457
2458 // [HGM] seekgraph
2459 Boolean soughtPending = FALSE;
2460 Boolean seekGraphUp;
2461 #define MAX_SEEK_ADS 200
2462 #define SQUARE 0x80
2463 char *seekAdList[MAX_SEEK_ADS];
2464 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2465 float tcList[MAX_SEEK_ADS];
2466 char colorList[MAX_SEEK_ADS];
2467 int nrOfSeekAds = 0;
2468 int minRating = 1010, maxRating = 2800;
2469 int hMargin = 10, vMargin = 20, h, w;
2470 extern int squareSize, lineGap;
2471
2472 void
2473 PlotSeekAd(int i)
2474 {
2475         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2476         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2477         if(r < minRating+100 && r >=0 ) r = minRating+100;
2478         if(r > maxRating) r = maxRating;
2479         if(tc < 1.) tc = 1.;
2480         if(tc > 95.) tc = 95.;
2481         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2482         y = ((double)r - minRating)/(maxRating - minRating)
2483             * (h-vMargin-squareSize/8-1) + vMargin;
2484         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2485         if(strstr(seekAdList[i], " u ")) color = 1;
2486         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2487            !strstr(seekAdList[i], "bullet") &&
2488            !strstr(seekAdList[i], "blitz") &&
2489            !strstr(seekAdList[i], "standard") ) color = 2;
2490         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2491         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2492 }
2493
2494 void
2495 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2496 {
2497         char buf[MSG_SIZ], *ext = "";
2498         VariantClass v = StringToVariant(type);
2499         if(strstr(type, "wild")) {
2500             ext = type + 4; // append wild number
2501             if(v == VariantFischeRandom) type = "chess960"; else
2502             if(v == VariantLoadable) type = "setup"; else
2503             type = VariantName(v);
2504         }
2505         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2506         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2507             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2508             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2509             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2510             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2511             seekNrList[nrOfSeekAds] = nr;
2512             zList[nrOfSeekAds] = 0;
2513             seekAdList[nrOfSeekAds++] = StrSave(buf);
2514             if(plot) PlotSeekAd(nrOfSeekAds-1);
2515         }
2516 }
2517
2518 void
2519 EraseSeekDot(int i)
2520 {
2521     int x = xList[i], y = yList[i], d=squareSize/4, k;
2522     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2523     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2524     // now replot every dot that overlapped
2525     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2526         int xx = xList[k], yy = yList[k];
2527         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2528             DrawSeekDot(xx, yy, colorList[k]);
2529     }
2530 }
2531
2532 void
2533 RemoveSeekAd(int nr)
2534 {
2535         int i;
2536         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2537             EraseSeekDot(i);
2538             if(seekAdList[i]) free(seekAdList[i]);
2539             seekAdList[i] = seekAdList[--nrOfSeekAds];
2540             seekNrList[i] = seekNrList[nrOfSeekAds];
2541             ratingList[i] = ratingList[nrOfSeekAds];
2542             colorList[i]  = colorList[nrOfSeekAds];
2543             tcList[i] = tcList[nrOfSeekAds];
2544             xList[i]  = xList[nrOfSeekAds];
2545             yList[i]  = yList[nrOfSeekAds];
2546             zList[i]  = zList[nrOfSeekAds];
2547             seekAdList[nrOfSeekAds] = NULL;
2548             break;
2549         }
2550 }
2551
2552 Boolean
2553 MatchSoughtLine(char *line)
2554 {
2555     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2556     int nr, base, inc, u=0; char dummy;
2557
2558     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2559        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2560        (u=1) &&
2561        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2562         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2563         // match: compact and save the line
2564         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2565         return TRUE;
2566     }
2567     return FALSE;
2568 }
2569
2570 int
2571 DrawSeekGraph()
2572 {
2573     int i;
2574     if(!seekGraphUp) return FALSE;
2575     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2576     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2577
2578     DrawSeekBackground(0, 0, w, h);
2579     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2580     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2581     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2582         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2583         yy = h-1-yy;
2584         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2585         if(i%500 == 0) {
2586             char buf[MSG_SIZ];
2587             snprintf(buf, MSG_SIZ, "%d", i);
2588             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2589         }
2590     }
2591     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2592     for(i=1; i<100; i+=(i<10?1:5)) {
2593         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2594         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2595         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2596             char buf[MSG_SIZ];
2597             snprintf(buf, MSG_SIZ, "%d", i);
2598             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2599         }
2600     }
2601     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2602     return TRUE;
2603 }
2604
2605 int SeekGraphClick(ClickType click, int x, int y, int moving)
2606 {
2607     static int lastDown = 0, displayed = 0, lastSecond;
2608     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2609         if(click == Release || moving) return FALSE;
2610         nrOfSeekAds = 0;
2611         soughtPending = TRUE;
2612         SendToICS(ics_prefix);
2613         SendToICS("sought\n"); // should this be "sought all"?
2614     } else { // issue challenge based on clicked ad
2615         int dist = 10000; int i, closest = 0, second = 0;
2616         for(i=0; i<nrOfSeekAds; i++) {
2617             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2618             if(d < dist) { dist = d; closest = i; }
2619             second += (d - zList[i] < 120); // count in-range ads
2620             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2621         }
2622         if(dist < 120) {
2623             char buf[MSG_SIZ];
2624             second = (second > 1);
2625             if(displayed != closest || second != lastSecond) {
2626                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2627                 lastSecond = second; displayed = closest;
2628             }
2629             if(click == Press) {
2630                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2631                 lastDown = closest;
2632                 return TRUE;
2633             } // on press 'hit', only show info
2634             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2635             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2636             SendToICS(ics_prefix);
2637             SendToICS(buf);
2638             return TRUE; // let incoming board of started game pop down the graph
2639         } else if(click == Release) { // release 'miss' is ignored
2640             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2641             if(moving == 2) { // right up-click
2642                 nrOfSeekAds = 0; // refresh graph
2643                 soughtPending = TRUE;
2644                 SendToICS(ics_prefix);
2645                 SendToICS("sought\n"); // should this be "sought all"?
2646             }
2647             return TRUE;
2648         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2649         // press miss or release hit 'pop down' seek graph
2650         seekGraphUp = FALSE;
2651         DrawPosition(TRUE, NULL);
2652     }
2653     return TRUE;
2654 }
2655
2656 void
2657 read_from_ics(isr, closure, data, count, error)
2658      InputSourceRef isr;
2659      VOIDSTAR closure;
2660      char *data;
2661      int count;
2662      int error;
2663 {
2664 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2665 #define STARTED_NONE 0
2666 #define STARTED_MOVES 1
2667 #define STARTED_BOARD 2
2668 #define STARTED_OBSERVE 3
2669 #define STARTED_HOLDINGS 4
2670 #define STARTED_CHATTER 5
2671 #define STARTED_COMMENT 6
2672 #define STARTED_MOVES_NOHIDE 7
2673
2674     static int started = STARTED_NONE;
2675     static char parse[20000];
2676     static int parse_pos = 0;
2677     static char buf[BUF_SIZE + 1];
2678     static int firstTime = TRUE, intfSet = FALSE;
2679     static ColorClass prevColor = ColorNormal;
2680     static int savingComment = FALSE;
2681     static int cmatch = 0; // continuation sequence match
2682     char *bp;
2683     char str[MSG_SIZ];
2684     int i, oldi;
2685     int buf_len;
2686     int next_out;
2687     int tkind;
2688     int backup;    /* [DM] For zippy color lines */
2689     char *p;
2690     char talker[MSG_SIZ]; // [HGM] chat
2691     int channel;
2692
2693     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2694
2695     if (appData.debugMode) {
2696       if (!error) {
2697         fprintf(debugFP, "<ICS: ");
2698         show_bytes(debugFP, data, count);
2699         fprintf(debugFP, "\n");
2700       }
2701     }
2702
2703     if (appData.debugMode) { int f = forwardMostMove;
2704         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2705                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2706                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2707     }
2708     if (count > 0) {
2709         /* If last read ended with a partial line that we couldn't parse,
2710            prepend it to the new read and try again. */
2711         if (leftover_len > 0) {
2712             for (i=0; i<leftover_len; i++)
2713               buf[i] = buf[leftover_start + i];
2714         }
2715
2716     /* copy new characters into the buffer */
2717     bp = buf + leftover_len;
2718     buf_len=leftover_len;
2719     for (i=0; i<count; i++)
2720     {
2721         // ignore these
2722         if (data[i] == '\r')
2723             continue;
2724
2725         // join lines split by ICS?
2726         if (!appData.noJoin)
2727         {
2728             /*
2729                 Joining just consists of finding matches against the
2730                 continuation sequence, and discarding that sequence
2731                 if found instead of copying it.  So, until a match
2732                 fails, there's nothing to do since it might be the
2733                 complete sequence, and thus, something we don't want
2734                 copied.
2735             */
2736             if (data[i] == cont_seq[cmatch])
2737             {
2738                 cmatch++;
2739                 if (cmatch == strlen(cont_seq))
2740                 {
2741                     cmatch = 0; // complete match.  just reset the counter
2742
2743                     /*
2744                         it's possible for the ICS to not include the space
2745                         at the end of the last word, making our [correct]
2746                         join operation fuse two separate words.  the server
2747                         does this when the space occurs at the width setting.
2748                     */
2749                     if (!buf_len || buf[buf_len-1] != ' ')
2750                     {
2751                         *bp++ = ' ';
2752                         buf_len++;
2753                     }
2754                 }
2755                 continue;
2756             }
2757             else if (cmatch)
2758             {
2759                 /*
2760                     match failed, so we have to copy what matched before
2761                     falling through and copying this character.  In reality,
2762                     this will only ever be just the newline character, but
2763                     it doesn't hurt to be precise.
2764                 */
2765                 strncpy(bp, cont_seq, cmatch);
2766                 bp += cmatch;
2767                 buf_len += cmatch;
2768                 cmatch = 0;
2769             }
2770         }
2771
2772         // copy this char
2773         *bp++ = data[i];
2774         buf_len++;
2775     }
2776
2777         buf[buf_len] = NULLCHAR;
2778 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2779         next_out = 0;
2780         leftover_start = 0;
2781
2782         i = 0;
2783         while (i < buf_len) {
2784             /* Deal with part of the TELNET option negotiation
2785                protocol.  We refuse to do anything beyond the
2786                defaults, except that we allow the WILL ECHO option,
2787                which ICS uses to turn off password echoing when we are
2788                directly connected to it.  We reject this option
2789                if localLineEditing mode is on (always on in xboard)
2790                and we are talking to port 23, which might be a real
2791                telnet server that will try to keep WILL ECHO on permanently.
2792              */
2793             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2794                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2795                 unsigned char option;
2796                 oldi = i;
2797                 switch ((unsigned char) buf[++i]) {
2798                   case TN_WILL:
2799                     if (appData.debugMode)
2800                       fprintf(debugFP, "\n<WILL ");
2801                     switch (option = (unsigned char) buf[++i]) {
2802                       case TN_ECHO:
2803                         if (appData.debugMode)
2804                           fprintf(debugFP, "ECHO ");
2805                         /* Reply only if this is a change, according
2806                            to the protocol rules. */
2807                         if (remoteEchoOption) break;
2808                         if (appData.localLineEditing &&
2809                             atoi(appData.icsPort) == TN_PORT) {
2810                             TelnetRequest(TN_DONT, TN_ECHO);
2811                         } else {
2812                             EchoOff();
2813                             TelnetRequest(TN_DO, TN_ECHO);
2814                             remoteEchoOption = TRUE;
2815                         }
2816                         break;
2817                       default:
2818                         if (appData.debugMode)
2819                           fprintf(debugFP, "%d ", option);
2820                         /* Whatever this is, we don't want it. */
2821                         TelnetRequest(TN_DONT, option);
2822                         break;
2823                     }
2824                     break;
2825                   case TN_WONT:
2826                     if (appData.debugMode)
2827                       fprintf(debugFP, "\n<WONT ");
2828                     switch (option = (unsigned char) buf[++i]) {
2829                       case TN_ECHO:
2830                         if (appData.debugMode)
2831                           fprintf(debugFP, "ECHO ");
2832                         /* Reply only if this is a change, according
2833                            to the protocol rules. */
2834                         if (!remoteEchoOption) break;
2835                         EchoOn();
2836                         TelnetRequest(TN_DONT, TN_ECHO);
2837                         remoteEchoOption = FALSE;
2838                         break;
2839                       default:
2840                         if (appData.debugMode)
2841                           fprintf(debugFP, "%d ", (unsigned char) option);
2842                         /* Whatever this is, it must already be turned
2843                            off, because we never agree to turn on
2844                            anything non-default, so according to the
2845                            protocol rules, we don't reply. */
2846                         break;
2847                     }
2848                     break;
2849                   case TN_DO:
2850                     if (appData.debugMode)
2851                       fprintf(debugFP, "\n<DO ");
2852                     switch (option = (unsigned char) buf[++i]) {
2853                       default:
2854                         /* Whatever this is, we refuse to do it. */
2855                         if (appData.debugMode)
2856                           fprintf(debugFP, "%d ", option);
2857                         TelnetRequest(TN_WONT, option);
2858                         break;
2859                     }
2860                     break;
2861                   case TN_DONT:
2862                     if (appData.debugMode)
2863                       fprintf(debugFP, "\n<DONT ");
2864                     switch (option = (unsigned char) buf[++i]) {
2865                       default:
2866                         if (appData.debugMode)
2867                           fprintf(debugFP, "%d ", option);
2868                         /* Whatever this is, we are already not doing
2869                            it, because we never agree to do anything
2870                            non-default, so according to the protocol
2871                            rules, we don't reply. */
2872                         break;
2873                     }
2874                     break;
2875                   case TN_IAC:
2876                     if (appData.debugMode)
2877                       fprintf(debugFP, "\n<IAC ");
2878                     /* Doubled IAC; pass it through */
2879                     i--;
2880                     break;
2881                   default:
2882                     if (appData.debugMode)
2883                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2884                     /* Drop all other telnet commands on the floor */
2885                     break;
2886                 }
2887                 if (oldi > next_out)
2888                   SendToPlayer(&buf[next_out], oldi - next_out);
2889                 if (++i > next_out)
2890                   next_out = i;
2891                 continue;
2892             }
2893
2894             /* OK, this at least will *usually* work */
2895             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2896                 loggedOn = TRUE;
2897             }
2898
2899             if (loggedOn && !intfSet) {
2900                 if (ics_type == ICS_ICC) {
2901                   snprintf(str, MSG_SIZ,
2902                           "/set-quietly interface %s\n/set-quietly style 12\n",
2903                           programVersion);
2904                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2905                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2906                 } else if (ics_type == ICS_CHESSNET) {
2907                   snprintf(str, MSG_SIZ, "/style 12\n");
2908                 } else {
2909                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2910                   strcat(str, programVersion);
2911                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2912                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2913                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2914 #ifdef WIN32
2915                   strcat(str, "$iset nohighlight 1\n");
2916 #endif
2917                   strcat(str, "$iset lock 1\n$style 12\n");
2918                 }
2919                 SendToICS(str);
2920                 NotifyFrontendLogin();
2921                 intfSet = TRUE;
2922             }
2923
2924             if (started == STARTED_COMMENT) {
2925                 /* Accumulate characters in comment */
2926                 parse[parse_pos++] = buf[i];
2927                 if (buf[i] == '\n') {
2928                     parse[parse_pos] = NULLCHAR;
2929                     if(chattingPartner>=0) {
2930                         char mess[MSG_SIZ];
2931                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2932                         OutputChatMessage(chattingPartner, mess);
2933                         chattingPartner = -1;
2934                         next_out = i+1; // [HGM] suppress printing in ICS window
2935                     } else
2936                     if(!suppressKibitz) // [HGM] kibitz
2937                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2938                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2939                         int nrDigit = 0, nrAlph = 0, j;
2940                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2941                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2942                         parse[parse_pos] = NULLCHAR;
2943                         // try to be smart: if it does not look like search info, it should go to
2944                         // ICS interaction window after all, not to engine-output window.
2945                         for(j=0; j<parse_pos; j++) { // count letters and digits
2946                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2947                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2948                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2949                         }
2950                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2951                             int depth=0; float score;
2952                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2953                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2954                                 pvInfoList[forwardMostMove-1].depth = depth;
2955                                 pvInfoList[forwardMostMove-1].score = 100*score;
2956                             }
2957                             OutputKibitz(suppressKibitz, parse);
2958                         } else {
2959                             char tmp[MSG_SIZ];
2960                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2961                             SendToPlayer(tmp, strlen(tmp));
2962                         }
2963                         next_out = i+1; // [HGM] suppress printing in ICS window
2964                     }
2965                     started = STARTED_NONE;
2966                 } else {
2967                     /* Don't match patterns against characters in comment */
2968                     i++;
2969                     continue;
2970                 }
2971             }
2972             if (started == STARTED_CHATTER) {
2973                 if (buf[i] != '\n') {
2974                     /* Don't match patterns against characters in chatter */
2975                     i++;
2976                     continue;
2977                 }
2978                 started = STARTED_NONE;
2979                 if(suppressKibitz) next_out = i+1;
2980             }
2981
2982             /* Kludge to deal with rcmd protocol */
2983             if (firstTime && looking_at(buf, &i, "\001*")) {
2984                 DisplayFatalError(&buf[1], 0, 1);
2985                 continue;
2986             } else {
2987                 firstTime = FALSE;
2988             }
2989
2990             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2991                 ics_type = ICS_ICC;
2992                 ics_prefix = "/";
2993                 if (appData.debugMode)
2994                   fprintf(debugFP, "ics_type %d\n", ics_type);
2995                 continue;
2996             }
2997             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2998                 ics_type = ICS_FICS;
2999                 ics_prefix = "$";
3000                 if (appData.debugMode)
3001                   fprintf(debugFP, "ics_type %d\n", ics_type);
3002                 continue;
3003             }
3004             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3005                 ics_type = ICS_CHESSNET;
3006                 ics_prefix = "/";
3007                 if (appData.debugMode)
3008                   fprintf(debugFP, "ics_type %d\n", ics_type);
3009                 continue;
3010             }
3011
3012             if (!loggedOn &&
3013                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3014                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3015                  looking_at(buf, &i, "will be \"*\""))) {
3016               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3017               continue;
3018             }
3019
3020             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3021               char buf[MSG_SIZ];
3022               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3023               DisplayIcsInteractionTitle(buf);
3024               have_set_title = TRUE;
3025             }
3026
3027             /* skip finger notes */
3028             if (started == STARTED_NONE &&
3029                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3030                  (buf[i] == '1' && buf[i+1] == '0')) &&
3031                 buf[i+2] == ':' && buf[i+3] == ' ') {
3032               started = STARTED_CHATTER;
3033               i += 3;
3034               continue;
3035             }
3036
3037             oldi = i;
3038             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3039             if(appData.seekGraph) {
3040                 if(soughtPending && MatchSoughtLine(buf+i)) {
3041                     i = strstr(buf+i, "rated") - buf;
3042                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3043                     next_out = leftover_start = i;
3044                     started = STARTED_CHATTER;
3045                     suppressKibitz = TRUE;
3046                     continue;
3047                 }
3048                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3049                         && looking_at(buf, &i, "* ads displayed")) {
3050                     soughtPending = FALSE;
3051                     seekGraphUp = TRUE;
3052                     DrawSeekGraph();
3053                     continue;
3054                 }
3055                 if(appData.autoRefresh) {
3056                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3057                         int s = (ics_type == ICS_ICC); // ICC format differs
3058                         if(seekGraphUp)
3059                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3060                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3061                         looking_at(buf, &i, "*% "); // eat prompt
3062                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3063                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3064                         next_out = i; // suppress
3065                         continue;
3066                     }
3067                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3068                         char *p = star_match[0];
3069                         while(*p) {
3070                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3071                             while(*p && *p++ != ' '); // next
3072                         }
3073                         looking_at(buf, &i, "*% "); // eat prompt
3074                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3075                         next_out = i;
3076                         continue;
3077                     }
3078                 }
3079             }
3080
3081             /* skip formula vars */
3082             if (started == STARTED_NONE &&
3083                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3084               started = STARTED_CHATTER;
3085               i += 3;
3086               continue;
3087             }
3088
3089             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3090             if (appData.autoKibitz && started == STARTED_NONE &&
3091                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3092                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3093                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3094                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3095                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3096                         suppressKibitz = TRUE;
3097                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3098                         next_out = i;
3099                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3100                                 && (gameMode == IcsPlayingWhite)) ||
3101                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3102                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3103                             started = STARTED_CHATTER; // own kibitz we simply discard
3104                         else {
3105                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3106                             parse_pos = 0; parse[0] = NULLCHAR;
3107                             savingComment = TRUE;
3108                             suppressKibitz = gameMode != IcsObserving ? 2 :
3109                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3110                         }
3111                         continue;
3112                 } else
3113                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3114                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3115                          && atoi(star_match[0])) {
3116                     // suppress the acknowledgements of our own autoKibitz
3117                     char *p;
3118                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3119                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3120                     SendToPlayer(star_match[0], strlen(star_match[0]));
3121                     if(looking_at(buf, &i, "*% ")) // eat prompt
3122                         suppressKibitz = FALSE;
3123                     next_out = i;
3124                     continue;
3125                 }
3126             } // [HGM] kibitz: end of patch
3127
3128             // [HGM] chat: intercept tells by users for which we have an open chat window
3129             channel = -1;
3130             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3131                                            looking_at(buf, &i, "* whispers:") ||
3132                                            looking_at(buf, &i, "* kibitzes:") ||
3133                                            looking_at(buf, &i, "* shouts:") ||
3134                                            looking_at(buf, &i, "* c-shouts:") ||
3135                                            looking_at(buf, &i, "--> * ") ||
3136                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3137                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3138                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3139                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3140                 int p;
3141                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3142                 chattingPartner = -1;
3143
3144                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3145                 for(p=0; p<MAX_CHAT; p++) {
3146                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3147                     talker[0] = '['; strcat(talker, "] ");
3148                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3149                     chattingPartner = p; break;
3150                     }
3151                 } else
3152                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3153                 for(p=0; p<MAX_CHAT; p++) {
3154                     if(!strcmp("kibitzes", chatPartner[p])) {
3155                         talker[0] = '['; strcat(talker, "] ");
3156                         chattingPartner = p; break;
3157                     }
3158                 } else
3159                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3160                 for(p=0; p<MAX_CHAT; p++) {
3161                     if(!strcmp("whispers", chatPartner[p])) {
3162                         talker[0] = '['; strcat(talker, "] ");
3163                         chattingPartner = p; break;
3164                     }
3165                 } else
3166                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3167                   if(buf[i-8] == '-' && buf[i-3] == 't')
3168                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3169                     if(!strcmp("c-shouts", chatPartner[p])) {
3170                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3171                         chattingPartner = p; break;
3172                     }
3173                   }
3174                   if(chattingPartner < 0)
3175                   for(p=0; p<MAX_CHAT; p++) {
3176                     if(!strcmp("shouts", chatPartner[p])) {
3177                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3178                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3179                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3180                         chattingPartner = p; break;
3181                     }
3182                   }
3183                 }
3184                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3185                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3186                     talker[0] = 0; Colorize(ColorTell, FALSE);
3187                     chattingPartner = p; break;
3188                 }
3189                 if(chattingPartner<0) i = oldi; else {
3190                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3191                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3192                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3193                     started = STARTED_COMMENT;
3194                     parse_pos = 0; parse[0] = NULLCHAR;
3195                     savingComment = 3 + chattingPartner; // counts as TRUE
3196                     suppressKibitz = TRUE;
3197                     continue;
3198                 }
3199             } // [HGM] chat: end of patch
3200
3201           backup = i;
3202             if (appData.zippyTalk || appData.zippyPlay) {
3203                 /* [DM] Backup address for color zippy lines */
3204 #if ZIPPY
3205                if (loggedOn == TRUE)
3206                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3207                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3208 #endif
3209             } // [DM] 'else { ' deleted
3210                 if (
3211                     /* Regular tells and says */
3212                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3213                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3214                     looking_at(buf, &i, "* says: ") ||
3215                     /* Don't color "message" or "messages" output */
3216                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3217                     looking_at(buf, &i, "*. * at *:*: ") ||
3218                     looking_at(buf, &i, "--* (*:*): ") ||
3219                     /* Message notifications (same color as tells) */
3220                     looking_at(buf, &i, "* has left a message ") ||
3221                     looking_at(buf, &i, "* just sent you a message:\n") ||
3222                     /* Whispers and kibitzes */
3223                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3224                     looking_at(buf, &i, "* kibitzes: ") ||
3225                     /* Channel tells */
3226                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3227
3228                   if (tkind == 1 && strchr(star_match[0], ':')) {
3229                       /* Avoid "tells you:" spoofs in channels */
3230                      tkind = 3;
3231                   }
3232                   if (star_match[0][0] == NULLCHAR ||
3233                       strchr(star_match[0], ' ') ||
3234                       (tkind == 3 && strchr(star_match[1], ' '))) {
3235                     /* Reject bogus matches */
3236                     i = oldi;
3237                   } else {
3238                     if (appData.colorize) {
3239                       if (oldi > next_out) {
3240                         SendToPlayer(&buf[next_out], oldi - next_out);
3241                         next_out = oldi;
3242                       }
3243                       switch (tkind) {
3244                       case 1:
3245                         Colorize(ColorTell, FALSE);
3246                         curColor = ColorTell;
3247                         break;
3248                       case 2:
3249                         Colorize(ColorKibitz, FALSE);
3250                         curColor = ColorKibitz;
3251                         break;
3252                       case 3:
3253                         p = strrchr(star_match[1], '(');
3254                         if (p == NULL) {
3255                           p = star_match[1];
3256                         } else {
3257                           p++;
3258                         }
3259                         if (atoi(p) == 1) {
3260                           Colorize(ColorChannel1, FALSE);
3261                           curColor = ColorChannel1;
3262                         } else {
3263                           Colorize(ColorChannel, FALSE);
3264                           curColor = ColorChannel;
3265                         }
3266                         break;
3267                       case 5:
3268                         curColor = ColorNormal;
3269                         break;
3270                       }
3271                     }
3272                     if (started == STARTED_NONE && appData.autoComment &&
3273                         (gameMode == IcsObserving ||
3274                          gameMode == IcsPlayingWhite ||
3275                          gameMode == IcsPlayingBlack)) {
3276                       parse_pos = i - oldi;
3277                       memcpy(parse, &buf[oldi], parse_pos);
3278                       parse[parse_pos] = NULLCHAR;
3279                       started = STARTED_COMMENT;
3280                       savingComment = TRUE;
3281                     } else {
3282                       started = STARTED_CHATTER;
3283                       savingComment = FALSE;
3284                     }
3285                     loggedOn = TRUE;
3286                     continue;
3287                   }
3288                 }
3289
3290                 if (looking_at(buf, &i, "* s-shouts: ") ||
3291                     looking_at(buf, &i, "* c-shouts: ")) {
3292                     if (appData.colorize) {
3293                         if (oldi > next_out) {
3294                             SendToPlayer(&buf[next_out], oldi - next_out);
3295                             next_out = oldi;
3296                         }
3297                         Colorize(ColorSShout, FALSE);
3298                         curColor = ColorSShout;
3299                     }
3300                     loggedOn = TRUE;
3301                     started = STARTED_CHATTER;
3302                     continue;
3303                 }
3304
3305                 if (looking_at(buf, &i, "--->")) {
3306                     loggedOn = TRUE;
3307                     continue;
3308                 }
3309
3310                 if (looking_at(buf, &i, "* shouts: ") ||
3311                     looking_at(buf, &i, "--> ")) {
3312                     if (appData.colorize) {
3313                         if (oldi > next_out) {
3314                             SendToPlayer(&buf[next_out], oldi - next_out);
3315                             next_out = oldi;
3316                         }
3317                         Colorize(ColorShout, FALSE);
3318                         curColor = ColorShout;
3319                     }
3320                     loggedOn = TRUE;
3321                     started = STARTED_CHATTER;
3322                     continue;
3323                 }
3324
3325                 if (looking_at( buf, &i, "Challenge:")) {
3326                     if (appData.colorize) {
3327                         if (oldi > next_out) {
3328                             SendToPlayer(&buf[next_out], oldi - next_out);
3329                             next_out = oldi;
3330                         }
3331                         Colorize(ColorChallenge, FALSE);
3332                         curColor = ColorChallenge;
3333                     }
3334                     loggedOn = TRUE;
3335                     continue;
3336                 }
3337
3338                 if (looking_at(buf, &i, "* offers you") ||
3339                     looking_at(buf, &i, "* offers to be") ||
3340                     looking_at(buf, &i, "* would like to") ||
3341                     looking_at(buf, &i, "* requests to") ||
3342                     looking_at(buf, &i, "Your opponent offers") ||
3343                     looking_at(buf, &i, "Your opponent requests")) {
3344
3345                     if (appData.colorize) {
3346                         if (oldi > next_out) {
3347                             SendToPlayer(&buf[next_out], oldi - next_out);
3348                             next_out = oldi;
3349                         }
3350                         Colorize(ColorRequest, FALSE);
3351                         curColor = ColorRequest;
3352                     }
3353                     continue;
3354                 }
3355
3356                 if (looking_at(buf, &i, "* (*) seeking")) {
3357                     if (appData.colorize) {
3358                         if (oldi > next_out) {
3359                             SendToPlayer(&buf[next_out], oldi - next_out);
3360                             next_out = oldi;
3361                         }
3362                         Colorize(ColorSeek, FALSE);
3363                         curColor = ColorSeek;
3364                     }
3365                     continue;
3366             }
3367
3368           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3369
3370             if (looking_at(buf, &i, "\\   ")) {
3371                 if (prevColor != ColorNormal) {
3372                     if (oldi > next_out) {
3373                         SendToPlayer(&buf[next_out], oldi - next_out);
3374                         next_out = oldi;
3375                     }
3376                     Colorize(prevColor, TRUE);
3377                     curColor = prevColor;
3378                 }
3379                 if (savingComment) {
3380                     parse_pos = i - oldi;
3381                     memcpy(parse, &buf[oldi], parse_pos);
3382                     parse[parse_pos] = NULLCHAR;
3383                     started = STARTED_COMMENT;
3384                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3385                         chattingPartner = savingComment - 3; // kludge to remember the box
3386                 } else {
3387                     started = STARTED_CHATTER;
3388                 }
3389                 continue;
3390             }
3391
3392             if (looking_at(buf, &i, "Black Strength :") ||
3393                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3394                 looking_at(buf, &i, "<10>") ||
3395                 looking_at(buf, &i, "#@#")) {
3396                 /* Wrong board style */
3397                 loggedOn = TRUE;
3398                 SendToICS(ics_prefix);
3399                 SendToICS("set style 12\n");
3400                 SendToICS(ics_prefix);
3401                 SendToICS("refresh\n");
3402                 continue;
3403             }
3404
3405             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3406                 ICSInitScript();
3407                 have_sent_ICS_logon = 1;
3408                 continue;
3409             }
3410
3411             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3412                 (looking_at(buf, &i, "\n<12> ") ||
3413                  looking_at(buf, &i, "<12> "))) {
3414                 loggedOn = TRUE;
3415                 if (oldi > next_out) {
3416                     SendToPlayer(&buf[next_out], oldi - next_out);
3417                 }
3418                 next_out = i;
3419                 started = STARTED_BOARD;
3420                 parse_pos = 0;
3421                 continue;
3422             }
3423
3424             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3425                 looking_at(buf, &i, "<b1> ")) {
3426                 if (oldi > next_out) {
3427                     SendToPlayer(&buf[next_out], oldi - next_out);
3428                 }
3429                 next_out = i;
3430                 started = STARTED_HOLDINGS;
3431                 parse_pos = 0;
3432                 continue;
3433             }
3434
3435             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3436                 loggedOn = TRUE;
3437                 /* Header for a move list -- first line */
3438
3439                 switch (ics_getting_history) {
3440                   case H_FALSE:
3441                     switch (gameMode) {
3442                       case IcsIdle:
3443                       case BeginningOfGame:
3444                         /* User typed "moves" or "oldmoves" while we
3445                            were idle.  Pretend we asked for these
3446                            moves and soak them up so user can step
3447                            through them and/or save them.
3448                            */
3449                         Reset(FALSE, TRUE);
3450                         gameMode = IcsObserving;
3451                         ModeHighlight();
3452                         ics_gamenum = -1;
3453                         ics_getting_history = H_GOT_UNREQ_HEADER;
3454                         break;
3455                       case EditGame: /*?*/
3456                       case EditPosition: /*?*/
3457                         /* Should above feature work in these modes too? */
3458                         /* For now it doesn't */
3459                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3460                         break;
3461                       default:
3462                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3463                         break;
3464                     }
3465                     break;
3466                   case H_REQUESTED:
3467                     /* Is this the right one? */
3468                     if (gameInfo.white && gameInfo.black &&
3469                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3470                         strcmp(gameInfo.black, star_match[2]) == 0) {
3471                         /* All is well */
3472                         ics_getting_history = H_GOT_REQ_HEADER;
3473                     }
3474                     break;
3475                   case H_GOT_REQ_HEADER:
3476                   case H_GOT_UNREQ_HEADER:
3477                   case H_GOT_UNWANTED_HEADER:
3478                   case H_GETTING_MOVES:
3479                     /* Should not happen */
3480                     DisplayError(_("Error gathering move list: two headers"), 0);
3481                     ics_getting_history = H_FALSE;
3482                     break;
3483                 }
3484
3485                 /* Save player ratings into gameInfo if needed */
3486                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3487                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3488                     (gameInfo.whiteRating == -1 ||
3489                      gameInfo.blackRating == -1)) {
3490
3491                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3492                     gameInfo.blackRating = string_to_rating(star_match[3]);
3493                     if (appData.debugMode)
3494                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3495                               gameInfo.whiteRating, gameInfo.blackRating);
3496                 }
3497                 continue;
3498             }
3499
3500             if (looking_at(buf, &i,
3501               "* * match, initial time: * minute*, increment: * second")) {
3502                 /* Header for a move list -- second line */
3503                 /* Initial board will follow if this is a wild game */
3504                 if (gameInfo.event != NULL) free(gameInfo.event);
3505                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3506                 gameInfo.event = StrSave(str);
3507                 /* [HGM] we switched variant. Translate boards if needed. */
3508                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3509                 continue;
3510             }
3511
3512             if (looking_at(buf, &i, "Move  ")) {
3513                 /* Beginning of a move list */
3514                 switch (ics_getting_history) {
3515                   case H_FALSE:
3516                     /* Normally should not happen */
3517                     /* Maybe user hit reset while we were parsing */
3518                     break;
3519                   case H_REQUESTED:
3520                     /* Happens if we are ignoring a move list that is not
3521                      * the one we just requested.  Common if the user
3522                      * tries to observe two games without turning off
3523                      * getMoveList */
3524                     break;
3525                   case H_GETTING_MOVES:
3526                     /* Should not happen */
3527                     DisplayError(_("Error gathering move list: nested"), 0);
3528                     ics_getting_history = H_FALSE;
3529                     break;
3530                   case H_GOT_REQ_HEADER:
3531                     ics_getting_history = H_GETTING_MOVES;
3532                     started = STARTED_MOVES;
3533                     parse_pos = 0;
3534                     if (oldi > next_out) {
3535                         SendToPlayer(&buf[next_out], oldi - next_out);
3536                     }
3537                     break;
3538                   case H_GOT_UNREQ_HEADER:
3539                     ics_getting_history = H_GETTING_MOVES;
3540                     started = STARTED_MOVES_NOHIDE;
3541                     parse_pos = 0;
3542                     break;
3543                   case H_GOT_UNWANTED_HEADER:
3544                     ics_getting_history = H_FALSE;
3545                     break;
3546                 }
3547                 continue;
3548             }
3549
3550             if (looking_at(buf, &i, "% ") ||
3551                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3552                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3553                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3554                     soughtPending = FALSE;
3555                     seekGraphUp = TRUE;
3556                     DrawSeekGraph();
3557                 }
3558                 if(suppressKibitz) next_out = i;
3559                 savingComment = FALSE;
3560                 suppressKibitz = 0;
3561                 switch (started) {
3562                   case STARTED_MOVES:
3563                   case STARTED_MOVES_NOHIDE:
3564                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3565                     parse[parse_pos + i - oldi] = NULLCHAR;
3566                     ParseGameHistory(parse);
3567 #if ZIPPY
3568                     if (appData.zippyPlay && first.initDone) {
3569                         FeedMovesToProgram(&first, forwardMostMove);
3570                         if (gameMode == IcsPlayingWhite) {
3571                             if (WhiteOnMove(forwardMostMove)) {
3572                                 if (first.sendTime) {
3573                                   if (first.useColors) {
3574                                     SendToProgram("black\n", &first);
3575                                   }
3576                                   SendTimeRemaining(&first, TRUE);
3577                                 }
3578                                 if (first.useColors) {
3579                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3580                                 }
3581                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3582                                 first.maybeThinking = TRUE;
3583                             } else {
3584                                 if (first.usePlayother) {
3585                                   if (first.sendTime) {
3586                                     SendTimeRemaining(&first, TRUE);
3587                                   }
3588                                   SendToProgram("playother\n", &first);
3589                                   firstMove = FALSE;
3590                                 } else {
3591                                   firstMove = TRUE;
3592                                 }
3593                             }
3594                         } else if (gameMode == IcsPlayingBlack) {
3595                             if (!WhiteOnMove(forwardMostMove)) {
3596                                 if (first.sendTime) {
3597                                   if (first.useColors) {
3598                                     SendToProgram("white\n", &first);
3599                                   }
3600                                   SendTimeRemaining(&first, FALSE);
3601                                 }
3602                                 if (first.useColors) {
3603                                   SendToProgram("black\n", &first);
3604                                 }
3605                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3606                                 first.maybeThinking = TRUE;
3607                             } else {
3608                                 if (first.usePlayother) {
3609                                   if (first.sendTime) {
3610                                     SendTimeRemaining(&first, FALSE);
3611                                   }
3612                                   SendToProgram("playother\n", &first);
3613                                   firstMove = FALSE;
3614                                 } else {
3615                                   firstMove = TRUE;
3616                                 }
3617                             }
3618                         }
3619                     }
3620 #endif
3621                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3622                         /* Moves came from oldmoves or moves command
3623                            while we weren't doing anything else.
3624                            */
3625                         currentMove = forwardMostMove;
3626                         ClearHighlights();/*!!could figure this out*/
3627                         flipView = appData.flipView;
3628                         DrawPosition(TRUE, boards[currentMove]);
3629                         DisplayBothClocks();
3630                         snprintf(str, MSG_SIZ, "%s vs. %s",
3631                                 gameInfo.white, gameInfo.black);
3632                         DisplayTitle(str);
3633                         gameMode = IcsIdle;
3634                     } else {
3635                         /* Moves were history of an active game */
3636                         if (gameInfo.resultDetails != NULL) {
3637                             free(gameInfo.resultDetails);
3638                             gameInfo.resultDetails = NULL;
3639                         }
3640                     }
3641                     HistorySet(parseList, backwardMostMove,
3642                                forwardMostMove, currentMove-1);
3643                     DisplayMove(currentMove - 1);
3644                     if (started == STARTED_MOVES) next_out = i;
3645                     started = STARTED_NONE;
3646                     ics_getting_history = H_FALSE;
3647                     break;
3648
3649                   case STARTED_OBSERVE:
3650                     started = STARTED_NONE;
3651                     SendToICS(ics_prefix);
3652                     SendToICS("refresh\n");
3653                     break;
3654
3655                   default:
3656                     break;
3657                 }
3658                 if(bookHit) { // [HGM] book: simulate book reply
3659                     static char bookMove[MSG_SIZ]; // a bit generous?
3660
3661                     programStats.nodes = programStats.depth = programStats.time =
3662                     programStats.score = programStats.got_only_move = 0;
3663                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3664
3665                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3666                     strcat(bookMove, bookHit);
3667                     HandleMachineMove(bookMove, &first);
3668                 }
3669                 continue;
3670             }
3671
3672             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3673                  started == STARTED_HOLDINGS ||
3674                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3675                 /* Accumulate characters in move list or board */
3676                 parse[parse_pos++] = buf[i];
3677             }
3678
3679             /* Start of game messages.  Mostly we detect start of game
3680                when the first board image arrives.  On some versions
3681                of the ICS, though, we need to do a "refresh" after starting
3682                to observe in order to get the current board right away. */
3683             if (looking_at(buf, &i, "Adding game * to observation list")) {
3684                 started = STARTED_OBSERVE;
3685                 continue;
3686             }
3687
3688             /* Handle auto-observe */
3689             if (appData.autoObserve &&
3690                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3691                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3692                 char *player;
3693                 /* Choose the player that was highlighted, if any. */
3694                 if (star_match[0][0] == '\033' ||
3695                     star_match[1][0] != '\033') {
3696                     player = star_match[0];
3697                 } else {
3698                     player = star_match[2];
3699                 }
3700                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3701                         ics_prefix, StripHighlightAndTitle(player));
3702                 SendToICS(str);
3703
3704                 /* Save ratings from notify string */
3705                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3706                 player1Rating = string_to_rating(star_match[1]);
3707                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3708                 player2Rating = string_to_rating(star_match[3]);
3709
3710                 if (appData.debugMode)
3711                   fprintf(debugFP,
3712                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3713                           player1Name, player1Rating,
3714                           player2Name, player2Rating);
3715
3716                 continue;
3717             }
3718
3719             /* Deal with automatic examine mode after a game,
3720                and with IcsObserving -> IcsExamining transition */
3721             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3722                 looking_at(buf, &i, "has made you an examiner of game *")) {
3723
3724                 int gamenum = atoi(star_match[0]);
3725                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3726                     gamenum == ics_gamenum) {
3727                     /* We were already playing or observing this game;
3728                        no need to refetch history */
3729                     gameMode = IcsExamining;
3730                     if (pausing) {
3731                         pauseExamForwardMostMove = forwardMostMove;
3732                     } else if (currentMove < forwardMostMove) {
3733                         ForwardInner(forwardMostMove);
3734                     }
3735                 } else {
3736                     /* I don't think this case really can happen */
3737                     SendToICS(ics_prefix);
3738                     SendToICS("refresh\n");
3739                 }
3740                 continue;
3741             }
3742
3743             /* Error messages */
3744 //          if (ics_user_moved) {
3745             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3746                 if (looking_at(buf, &i, "Illegal move") ||
3747                     looking_at(buf, &i, "Not a legal move") ||
3748                     looking_at(buf, &i, "Your king is in check") ||
3749                     looking_at(buf, &i, "It isn't your turn") ||
3750                     looking_at(buf, &i, "It is not your move")) {
3751                     /* Illegal move */
3752                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3753                         currentMove = forwardMostMove-1;
3754                         DisplayMove(currentMove - 1); /* before DMError */
3755                         DrawPosition(FALSE, boards[currentMove]);
3756                         SwitchClocks(forwardMostMove-1); // [HGM] race
3757                         DisplayBothClocks();
3758                     }
3759                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3760                     ics_user_moved = 0;
3761                     continue;
3762                 }
3763             }
3764
3765             if (looking_at(buf, &i, "still have time") ||
3766                 looking_at(buf, &i, "not out of time") ||
3767                 looking_at(buf, &i, "either player is out of time") ||
3768                 looking_at(buf, &i, "has timeseal; checking")) {
3769                 /* We must have called his flag a little too soon */
3770                 whiteFlag = blackFlag = FALSE;
3771                 continue;
3772             }
3773
3774             if (looking_at(buf, &i, "added * seconds to") ||
3775                 looking_at(buf, &i, "seconds were added to")) {
3776                 /* Update the clocks */
3777                 SendToICS(ics_prefix);
3778                 SendToICS("refresh\n");
3779                 continue;
3780             }
3781
3782             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3783                 ics_clock_paused = TRUE;
3784                 StopClocks();
3785                 continue;
3786             }
3787
3788             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3789                 ics_clock_paused = FALSE;
3790                 StartClocks();
3791                 continue;
3792             }
3793
3794             /* Grab player ratings from the Creating: message.
3795                Note we have to check for the special case when
3796                the ICS inserts things like [white] or [black]. */
3797             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3798                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3799                 /* star_matches:
3800                    0    player 1 name (not necessarily white)
3801                    1    player 1 rating
3802                    2    empty, white, or black (IGNORED)
3803                    3    player 2 name (not necessarily black)
3804                    4    player 2 rating
3805
3806                    The names/ratings are sorted out when the game
3807                    actually starts (below).
3808                 */
3809                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3810                 player1Rating = string_to_rating(star_match[1]);
3811                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3812                 player2Rating = string_to_rating(star_match[4]);
3813
3814                 if (appData.debugMode)
3815                   fprintf(debugFP,
3816                           "Ratings from 'Creating:' %s %d, %s %d\n",
3817                           player1Name, player1Rating,
3818                           player2Name, player2Rating);
3819
3820                 continue;
3821             }
3822
3823             /* Improved generic start/end-of-game messages */
3824             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3825                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3826                 /* If tkind == 0: */
3827                 /* star_match[0] is the game number */
3828                 /*           [1] is the white player's name */
3829                 /*           [2] is the black player's name */
3830                 /* For end-of-game: */
3831                 /*           [3] is the reason for the game end */
3832                 /*           [4] is a PGN end game-token, preceded by " " */
3833                 /* For start-of-game: */
3834                 /*           [3] begins with "Creating" or "Continuing" */
3835                 /*           [4] is " *" or empty (don't care). */
3836                 int gamenum = atoi(star_match[0]);
3837                 char *whitename, *blackname, *why, *endtoken;
3838                 ChessMove endtype = EndOfFile;
3839
3840                 if (tkind == 0) {
3841                   whitename = star_match[1];
3842                   blackname = star_match[2];
3843                   why = star_match[3];
3844                   endtoken = star_match[4];
3845                 } else {
3846                   whitename = star_match[1];
3847                   blackname = star_match[3];
3848                   why = star_match[5];
3849                   endtoken = star_match[6];
3850                 }
3851
3852                 /* Game start messages */
3853                 if (strncmp(why, "Creating ", 9) == 0 ||
3854                     strncmp(why, "Continuing ", 11) == 0) {
3855                     gs_gamenum = gamenum;
3856                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3857                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3858 #if ZIPPY
3859                     if (appData.zippyPlay) {
3860                         ZippyGameStart(whitename, blackname);
3861                     }
3862 #endif /*ZIPPY*/
3863                     partnerBoardValid = FALSE; // [HGM] bughouse
3864                     continue;
3865                 }
3866
3867                 /* Game end messages */
3868                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3869                     ics_gamenum != gamenum) {
3870                     continue;
3871                 }
3872                 while (endtoken[0] == ' ') endtoken++;
3873                 switch (endtoken[0]) {
3874                   case '*':
3875                   default:
3876                     endtype = GameUnfinished;
3877                     break;
3878                   case '0':
3879                     endtype = BlackWins;
3880                     break;
3881                   case '1':
3882                     if (endtoken[1] == '/')
3883                       endtype = GameIsDrawn;
3884                     else
3885                       endtype = WhiteWins;
3886                     break;
3887                 }
3888                 GameEnds(endtype, why, GE_ICS);
3889 #if ZIPPY
3890                 if (appData.zippyPlay && first.initDone) {
3891                     ZippyGameEnd(endtype, why);
3892                     if (first.pr == NULL) {
3893                       /* Start the next process early so that we'll
3894                          be ready for the next challenge */
3895                       StartChessProgram(&first);
3896                     }
3897                     /* Send "new" early, in case this command takes
3898                        a long time to finish, so that we'll be ready
3899                        for the next challenge. */
3900                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3901                     Reset(TRUE, TRUE);
3902                 }
3903 #endif /*ZIPPY*/
3904                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3905                 continue;
3906             }
3907
3908             if (looking_at(buf, &i, "Removing game * from observation") ||
3909                 looking_at(buf, &i, "no longer observing game *") ||
3910                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3911                 if (gameMode == IcsObserving &&
3912                     atoi(star_match[0]) == ics_gamenum)
3913                   {
3914                       /* icsEngineAnalyze */
3915                       if (appData.icsEngineAnalyze) {
3916                             ExitAnalyzeMode();
3917                             ModeHighlight();
3918                       }
3919                       StopClocks();
3920                       gameMode = IcsIdle;
3921                       ics_gamenum = -1;
3922                       ics_user_moved = FALSE;
3923                   }
3924                 continue;
3925             }
3926
3927             if (looking_at(buf, &i, "no longer examining game *")) {
3928                 if (gameMode == IcsExamining &&
3929                     atoi(star_match[0]) == ics_gamenum)
3930                   {
3931                       gameMode = IcsIdle;
3932                       ics_gamenum = -1;
3933                       ics_user_moved = FALSE;
3934                   }
3935                 continue;
3936             }
3937
3938             /* Advance leftover_start past any newlines we find,
3939                so only partial lines can get reparsed */
3940             if (looking_at(buf, &i, "\n")) {
3941                 prevColor = curColor;
3942                 if (curColor != ColorNormal) {
3943                     if (oldi > next_out) {
3944                         SendToPlayer(&buf[next_out], oldi - next_out);
3945                         next_out = oldi;
3946                     }
3947                     Colorize(ColorNormal, FALSE);
3948                     curColor = ColorNormal;
3949                 }
3950                 if (started == STARTED_BOARD) {
3951                     started = STARTED_NONE;
3952                     parse[parse_pos] = NULLCHAR;
3953                     ParseBoard12(parse);
3954                     ics_user_moved = 0;
3955
3956                     /* Send premove here */
3957                     if (appData.premove) {
3958                       char str[MSG_SIZ];
3959                       if (currentMove == 0 &&
3960                           gameMode == IcsPlayingWhite &&
3961                           appData.premoveWhite) {
3962                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3963                         if (appData.debugMode)
3964                           fprintf(debugFP, "Sending premove:\n");
3965                         SendToICS(str);
3966                       } else if (currentMove == 1 &&
3967                                  gameMode == IcsPlayingBlack &&
3968                                  appData.premoveBlack) {
3969                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3970                         if (appData.debugMode)
3971                           fprintf(debugFP, "Sending premove:\n");
3972                         SendToICS(str);
3973                       } else if (gotPremove) {
3974                         gotPremove = 0;
3975                         ClearPremoveHighlights();
3976                         if (appData.debugMode)
3977                           fprintf(debugFP, "Sending premove:\n");
3978                           UserMoveEvent(premoveFromX, premoveFromY,
3979                                         premoveToX, premoveToY,
3980                                         premovePromoChar);
3981                       }
3982                     }
3983
3984                     /* Usually suppress following prompt */
3985                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3986                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3987                         if (looking_at(buf, &i, "*% ")) {
3988                             savingComment = FALSE;
3989                             suppressKibitz = 0;
3990                         }
3991                     }
3992                     next_out = i;
3993                 } else if (started == STARTED_HOLDINGS) {
3994                     int gamenum;
3995                     char new_piece[MSG_SIZ];
3996                     started = STARTED_NONE;
3997                     parse[parse_pos] = NULLCHAR;
3998                     if (appData.debugMode)
3999                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4000                                                         parse, currentMove);
4001                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4002                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4003                         if (gameInfo.variant == VariantNormal) {
4004                           /* [HGM] We seem to switch variant during a game!
4005                            * Presumably no holdings were displayed, so we have
4006                            * to move the position two files to the right to
4007                            * create room for them!
4008                            */
4009                           VariantClass newVariant;
4010                           switch(gameInfo.boardWidth) { // base guess on board width
4011                                 case 9:  newVariant = VariantShogi; break;
4012                                 case 10: newVariant = VariantGreat; break;
4013                                 default: newVariant = VariantCrazyhouse; break;
4014                           }
4015                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4016                           /* Get a move list just to see the header, which
4017                              will tell us whether this is really bug or zh */
4018                           if (ics_getting_history == H_FALSE) {
4019                             ics_getting_history = H_REQUESTED;
4020                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4021                             SendToICS(str);
4022                           }
4023                         }
4024                         new_piece[0] = NULLCHAR;
4025                         sscanf(parse, "game %d white [%s black [%s <- %s",
4026                                &gamenum, white_holding, black_holding,
4027                                new_piece);
4028                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4029                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4030                         /* [HGM] copy holdings to board holdings area */
4031                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4032                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4033                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4034 #if ZIPPY
4035                         if (appData.zippyPlay && first.initDone) {
4036                             ZippyHoldings(white_holding, black_holding,
4037                                           new_piece);
4038                         }
4039 #endif /*ZIPPY*/
4040                         if (tinyLayout || smallLayout) {
4041                             char wh[16], bh[16];
4042                             PackHolding(wh, white_holding);
4043                             PackHolding(bh, black_holding);
4044                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4045                                     gameInfo.white, gameInfo.black);
4046                         } else {
4047                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4048                                     gameInfo.white, white_holding,
4049                                     gameInfo.black, black_holding);
4050                         }
4051                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4052                         DrawPosition(FALSE, boards[currentMove]);
4053                         DisplayTitle(str);
4054                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4055                         sscanf(parse, "game %d white [%s black [%s <- %s",
4056                                &gamenum, white_holding, black_holding,
4057                                new_piece);
4058                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4059                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4060                         /* [HGM] copy holdings to partner-board holdings area */
4061                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4062                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4063                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4064                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4065                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4066                       }
4067                     }
4068                     /* Suppress following prompt */
4069                     if (looking_at(buf, &i, "*% ")) {
4070                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4071                         savingComment = FALSE;
4072                         suppressKibitz = 0;
4073                     }
4074                     next_out = i;
4075                 }
4076                 continue;
4077             }
4078
4079             i++;                /* skip unparsed character and loop back */
4080         }
4081
4082         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4083 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4084 //          SendToPlayer(&buf[next_out], i - next_out);
4085             started != STARTED_HOLDINGS && leftover_start > next_out) {
4086             SendToPlayer(&buf[next_out], leftover_start - next_out);
4087             next_out = i;
4088         }
4089
4090         leftover_len = buf_len - leftover_start;
4091         /* if buffer ends with something we couldn't parse,
4092            reparse it after appending the next read */
4093
4094     } else if (count == 0) {
4095         RemoveInputSource(isr);
4096         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4097     } else {
4098         DisplayFatalError(_("Error reading from ICS"), error, 1);
4099     }
4100 }
4101
4102
4103 /* Board style 12 looks like this:
4104
4105    <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
4106
4107  * The "<12> " is stripped before it gets to this routine.  The two
4108  * trailing 0's (flip state and clock ticking) are later addition, and
4109  * some chess servers may not have them, or may have only the first.
4110  * Additional trailing fields may be added in the future.
4111  */
4112
4113 #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"
4114
4115 #define RELATION_OBSERVING_PLAYED    0
4116 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4117 #define RELATION_PLAYING_MYMOVE      1
4118 #define RELATION_PLAYING_NOTMYMOVE  -1
4119 #define RELATION_EXAMINING           2
4120 #define RELATION_ISOLATED_BOARD     -3
4121 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4122
4123 void
4124 ParseBoard12(string)
4125      char *string;
4126 {
4127     GameMode newGameMode;
4128     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4129     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4130     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4131     char to_play, board_chars[200];
4132     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4133     char black[32], white[32];
4134     Board board;
4135     int prevMove = currentMove;
4136     int ticking = 2;
4137     ChessMove moveType;
4138     int fromX, fromY, toX, toY;
4139     char promoChar;
4140     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4141     char *bookHit = NULL; // [HGM] book
4142     Boolean weird = FALSE, reqFlag = FALSE;
4143
4144     fromX = fromY = toX = toY = -1;
4145
4146     newGame = FALSE;
4147
4148     if (appData.debugMode)
4149       fprintf(debugFP, _("Parsing board: %s\n"), string);
4150
4151     move_str[0] = NULLCHAR;
4152     elapsed_time[0] = NULLCHAR;
4153     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4154         int  i = 0, j;
4155         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4156             if(string[i] == ' ') { ranks++; files = 0; }
4157             else files++;
4158             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4159             i++;
4160         }
4161         for(j = 0; j <i; j++) board_chars[j] = string[j];
4162         board_chars[i] = '\0';
4163         string += i + 1;
4164     }
4165     n = sscanf(string, PATTERN, &to_play, &double_push,
4166                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4167                &gamenum, white, black, &relation, &basetime, &increment,
4168                &white_stren, &black_stren, &white_time, &black_time,
4169                &moveNum, str, elapsed_time, move_str, &ics_flip,
4170                &ticking);
4171
4172     if (n < 21) {
4173         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4174         DisplayError(str, 0);
4175         return;
4176     }
4177
4178     /* Convert the move number to internal form */
4179     moveNum = (moveNum - 1) * 2;
4180     if (to_play == 'B') moveNum++;
4181     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4182       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4183                         0, 1);
4184       return;
4185     }
4186
4187     switch (relation) {
4188       case RELATION_OBSERVING_PLAYED:
4189       case RELATION_OBSERVING_STATIC:
4190         if (gamenum == -1) {
4191             /* Old ICC buglet */
4192             relation = RELATION_OBSERVING_STATIC;
4193         }
4194         newGameMode = IcsObserving;
4195         break;
4196       case RELATION_PLAYING_MYMOVE:
4197       case RELATION_PLAYING_NOTMYMOVE:
4198         newGameMode =
4199           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4200             IcsPlayingWhite : IcsPlayingBlack;
4201         break;
4202       case RELATION_EXAMINING:
4203         newGameMode = IcsExamining;
4204         break;
4205       case RELATION_ISOLATED_BOARD:
4206       default:
4207         /* Just display this board.  If user was doing something else,
4208            we will forget about it until the next board comes. */
4209         newGameMode = IcsIdle;
4210         break;
4211       case RELATION_STARTING_POSITION:
4212         newGameMode = gameMode;
4213         break;
4214     }
4215
4216     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4217          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4218       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4219       char *toSqr;
4220       for (k = 0; k < ranks; k++) {
4221         for (j = 0; j < files; j++)
4222           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4223         if(gameInfo.holdingsWidth > 1) {
4224              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4225              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4226         }
4227       }
4228       CopyBoard(partnerBoard, board);
4229       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4230         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4231         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4232       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4233       if(toSqr = strchr(str, '-')) {
4234         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4235         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4236       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4237       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4238       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4239       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4240       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4241       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4242                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4243       DisplayMessage(partnerStatus, "");
4244         partnerBoardValid = TRUE;
4245       return;
4246     }
4247
4248     /* Modify behavior for initial board display on move listing
4249        of wild games.
4250        */
4251     switch (ics_getting_history) {
4252       case H_FALSE:
4253       case H_REQUESTED:
4254         break;
4255       case H_GOT_REQ_HEADER:
4256       case H_GOT_UNREQ_HEADER:
4257         /* This is the initial position of the current game */
4258         gamenum = ics_gamenum;
4259         moveNum = 0;            /* old ICS bug workaround */
4260         if (to_play == 'B') {
4261           startedFromSetupPosition = TRUE;
4262           blackPlaysFirst = TRUE;
4263           moveNum = 1;
4264           if (forwardMostMove == 0) forwardMostMove = 1;
4265           if (backwardMostMove == 0) backwardMostMove = 1;
4266           if (currentMove == 0) currentMove = 1;
4267         }
4268         newGameMode = gameMode;
4269         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4270         break;
4271       case H_GOT_UNWANTED_HEADER:
4272         /* This is an initial board that we don't want */
4273         return;
4274       case H_GETTING_MOVES:
4275         /* Should not happen */
4276         DisplayError(_("Error gathering move list: extra board"), 0);
4277         ics_getting_history = H_FALSE;
4278         return;
4279     }
4280
4281    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4282                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4283      /* [HGM] We seem to have switched variant unexpectedly
4284       * Try to guess new variant from board size
4285       */
4286           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4287           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4288           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4289           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4290           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4291           if(!weird) newVariant = VariantNormal;
4292           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4293           /* Get a move list just to see the header, which
4294              will tell us whether this is really bug or zh */
4295           if (ics_getting_history == H_FALSE) {
4296             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4297             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4298             SendToICS(str);
4299           }
4300     }
4301
4302     /* Take action if this is the first board of a new game, or of a
4303        different game than is currently being displayed.  */
4304     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4305         relation == RELATION_ISOLATED_BOARD) {
4306
4307         /* Forget the old game and get the history (if any) of the new one */
4308         if (gameMode != BeginningOfGame) {
4309           Reset(TRUE, TRUE);
4310         }
4311         newGame = TRUE;
4312         if (appData.autoRaiseBoard) BoardToTop();
4313         prevMove = -3;
4314         if (gamenum == -1) {
4315             newGameMode = IcsIdle;
4316         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4317                    appData.getMoveList && !reqFlag) {
4318             /* Need to get game history */
4319             ics_getting_history = H_REQUESTED;
4320             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4321             SendToICS(str);
4322         }
4323
4324         /* Initially flip the board to have black on the bottom if playing
4325            black or if the ICS flip flag is set, but let the user change
4326            it with the Flip View button. */
4327         flipView = appData.autoFlipView ?
4328           (newGameMode == IcsPlayingBlack) || ics_flip :
4329           appData.flipView;
4330
4331         /* Done with values from previous mode; copy in new ones */
4332         gameMode = newGameMode;
4333         ModeHighlight();
4334         ics_gamenum = gamenum;
4335         if (gamenum == gs_gamenum) {
4336             int klen = strlen(gs_kind);
4337             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4338             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4339             gameInfo.event = StrSave(str);
4340         } else {
4341             gameInfo.event = StrSave("ICS game");
4342         }
4343         gameInfo.site = StrSave(appData.icsHost);
4344         gameInfo.date = PGNDate();
4345         gameInfo.round = StrSave("-");
4346         gameInfo.white = StrSave(white);
4347         gameInfo.black = StrSave(black);
4348         timeControl = basetime * 60 * 1000;
4349         timeControl_2 = 0;
4350         timeIncrement = increment * 1000;
4351         movesPerSession = 0;
4352         gameInfo.timeControl = TimeControlTagValue();
4353         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4354   if (appData.debugMode) {
4355     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4356     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4357     setbuf(debugFP, NULL);
4358   }
4359
4360         gameInfo.outOfBook = NULL;
4361
4362         /* Do we have the ratings? */
4363         if (strcmp(player1Name, white) == 0 &&
4364             strcmp(player2Name, black) == 0) {
4365             if (appData.debugMode)
4366               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4367                       player1Rating, player2Rating);
4368             gameInfo.whiteRating = player1Rating;
4369             gameInfo.blackRating = player2Rating;
4370         } else if (strcmp(player2Name, white) == 0 &&
4371                    strcmp(player1Name, black) == 0) {
4372             if (appData.debugMode)
4373               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4374                       player2Rating, player1Rating);
4375             gameInfo.whiteRating = player2Rating;
4376             gameInfo.blackRating = player1Rating;
4377         }
4378         player1Name[0] = player2Name[0] = NULLCHAR;
4379
4380         /* Silence shouts if requested */
4381         if (appData.quietPlay &&
4382             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4383             SendToICS(ics_prefix);
4384             SendToICS("set shout 0\n");
4385         }
4386     }
4387
4388     /* Deal with midgame name changes */
4389     if (!newGame) {
4390         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4391             if (gameInfo.white) free(gameInfo.white);
4392             gameInfo.white = StrSave(white);
4393         }
4394         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4395             if (gameInfo.black) free(gameInfo.black);
4396             gameInfo.black = StrSave(black);
4397         }
4398     }
4399
4400     /* Throw away game result if anything actually changes in examine mode */
4401     if (gameMode == IcsExamining && !newGame) {
4402         gameInfo.result = GameUnfinished;
4403         if (gameInfo.resultDetails != NULL) {
4404             free(gameInfo.resultDetails);
4405             gameInfo.resultDetails = NULL;
4406         }
4407     }
4408
4409     /* In pausing && IcsExamining mode, we ignore boards coming
4410        in if they are in a different variation than we are. */
4411     if (pauseExamInvalid) return;
4412     if (pausing && gameMode == IcsExamining) {
4413         if (moveNum <= pauseExamForwardMostMove) {
4414             pauseExamInvalid = TRUE;
4415             forwardMostMove = pauseExamForwardMostMove;
4416             return;
4417         }
4418     }
4419
4420   if (appData.debugMode) {
4421     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4422   }
4423     /* Parse the board */
4424     for (k = 0; k < ranks; k++) {
4425       for (j = 0; j < files; j++)
4426         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4427       if(gameInfo.holdingsWidth > 1) {
4428            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4429            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4430       }
4431     }
4432     CopyBoard(boards[moveNum], board);
4433     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4434     if (moveNum == 0) {
4435         startedFromSetupPosition =
4436           !CompareBoards(board, initialPosition);
4437         if(startedFromSetupPosition)
4438             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4439     }
4440
4441     /* [HGM] Set castling rights. Take the outermost Rooks,
4442        to make it also work for FRC opening positions. Note that board12
4443        is really defective for later FRC positions, as it has no way to
4444        indicate which Rook can castle if they are on the same side of King.
4445        For the initial position we grant rights to the outermost Rooks,
4446        and remember thos rights, and we then copy them on positions
4447        later in an FRC game. This means WB might not recognize castlings with
4448        Rooks that have moved back to their original position as illegal,
4449        but in ICS mode that is not its job anyway.
4450     */
4451     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4452     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4453
4454         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4455             if(board[0][i] == WhiteRook) j = i;
4456         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4457         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4458             if(board[0][i] == WhiteRook) j = i;
4459         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4460         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4461             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4462         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4463         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4464             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4465         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4466
4467         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4468         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4469             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4470         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4471             if(board[BOARD_HEIGHT-1][k] == bKing)
4472                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4473         if(gameInfo.variant == VariantTwoKings) {
4474             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4475             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4476             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4477         }
4478     } else { int r;
4479         r = boards[moveNum][CASTLING][0] = initialRights[0];
4480         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4481         r = boards[moveNum][CASTLING][1] = initialRights[1];
4482         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4483         r = boards[moveNum][CASTLING][3] = initialRights[3];
4484         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4485         r = boards[moveNum][CASTLING][4] = initialRights[4];
4486         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4487         /* wildcastle kludge: always assume King has rights */
4488         r = boards[moveNum][CASTLING][2] = initialRights[2];
4489         r = boards[moveNum][CASTLING][5] = initialRights[5];
4490     }
4491     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4492     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4493
4494
4495     if (ics_getting_history == H_GOT_REQ_HEADER ||
4496         ics_getting_history == H_GOT_UNREQ_HEADER) {
4497         /* This was an initial position from a move list, not
4498            the current position */
4499         return;
4500     }
4501
4502     /* Update currentMove and known move number limits */
4503     newMove = newGame || moveNum > forwardMostMove;
4504
4505     if (newGame) {
4506         forwardMostMove = backwardMostMove = currentMove = moveNum;
4507         if (gameMode == IcsExamining && moveNum == 0) {
4508           /* Workaround for ICS limitation: we are not told the wild
4509              type when starting to examine a game.  But if we ask for
4510              the move list, the move list header will tell us */
4511             ics_getting_history = H_REQUESTED;
4512             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4513             SendToICS(str);
4514         }
4515     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4516                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4517 #if ZIPPY
4518         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4519         /* [HGM] applied this also to an engine that is silently watching        */
4520         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4521             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4522             gameInfo.variant == currentlyInitializedVariant) {
4523           takeback = forwardMostMove - moveNum;
4524           for (i = 0; i < takeback; i++) {
4525             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4526             SendToProgram("undo\n", &first);
4527           }
4528         }
4529 #endif
4530
4531         forwardMostMove = moveNum;
4532         if (!pausing || currentMove > forwardMostMove)
4533           currentMove = forwardMostMove;
4534     } else {
4535         /* New part of history that is not contiguous with old part */
4536         if (pausing && gameMode == IcsExamining) {
4537             pauseExamInvalid = TRUE;
4538             forwardMostMove = pauseExamForwardMostMove;
4539             return;
4540         }
4541         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4542 #if ZIPPY
4543             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4544                 // [HGM] when we will receive the move list we now request, it will be
4545                 // fed to the engine from the first move on. So if the engine is not
4546                 // in the initial position now, bring it there.
4547                 InitChessProgram(&first, 0);
4548             }
4549 #endif
4550             ics_getting_history = H_REQUESTED;
4551             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4552             SendToICS(str);
4553         }
4554         forwardMostMove = backwardMostMove = currentMove = moveNum;
4555     }
4556
4557     /* Update the clocks */
4558     if (strchr(elapsed_time, '.')) {
4559       /* Time is in ms */
4560       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4561       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4562     } else {
4563       /* Time is in seconds */
4564       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4565       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4566     }
4567
4568
4569 #if ZIPPY
4570     if (appData.zippyPlay && newGame &&
4571         gameMode != IcsObserving && gameMode != IcsIdle &&
4572         gameMode != IcsExamining)
4573       ZippyFirstBoard(moveNum, basetime, increment);
4574 #endif
4575
4576     /* Put the move on the move list, first converting
4577        to canonical algebraic form. */
4578     if (moveNum > 0) {
4579   if (appData.debugMode) {
4580     if (appData.debugMode) { int f = forwardMostMove;
4581         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4582                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4583                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4584     }
4585     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4586     fprintf(debugFP, "moveNum = %d\n", moveNum);
4587     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4588     setbuf(debugFP, NULL);
4589   }
4590         if (moveNum <= backwardMostMove) {
4591             /* We don't know what the board looked like before
4592                this move.  Punt. */
4593           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4594             strcat(parseList[moveNum - 1], " ");
4595             strcat(parseList[moveNum - 1], elapsed_time);
4596             moveList[moveNum - 1][0] = NULLCHAR;
4597         } else if (strcmp(move_str, "none") == 0) {
4598             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4599             /* Again, we don't know what the board looked like;
4600                this is really the start of the game. */
4601             parseList[moveNum - 1][0] = NULLCHAR;
4602             moveList[moveNum - 1][0] = NULLCHAR;
4603             backwardMostMove = moveNum;
4604             startedFromSetupPosition = TRUE;
4605             fromX = fromY = toX = toY = -1;
4606         } else {
4607           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4608           //                 So we parse the long-algebraic move string in stead of the SAN move
4609           int valid; char buf[MSG_SIZ], *prom;
4610
4611           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4612                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4613           // str looks something like "Q/a1-a2"; kill the slash
4614           if(str[1] == '/')
4615             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4616           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4617           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4618                 strcat(buf, prom); // long move lacks promo specification!
4619           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4620                 if(appData.debugMode)
4621                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4622                 safeStrCpy(move_str, buf, MSG_SIZ);
4623           }
4624           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4625                                 &fromX, &fromY, &toX, &toY, &promoChar)
4626                || ParseOneMove(buf, moveNum - 1, &moveType,
4627                                 &fromX, &fromY, &toX, &toY, &promoChar);
4628           // end of long SAN patch
4629           if (valid) {
4630             (void) CoordsToAlgebraic(boards[moveNum - 1],
4631                                      PosFlags(moveNum - 1),
4632                                      fromY, fromX, toY, toX, promoChar,
4633                                      parseList[moveNum-1]);
4634             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4635               case MT_NONE:
4636               case MT_STALEMATE:
4637               default:
4638                 break;
4639               case MT_CHECK:
4640                 if(gameInfo.variant != VariantShogi)
4641                     strcat(parseList[moveNum - 1], "+");
4642                 break;
4643               case MT_CHECKMATE:
4644               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4645                 strcat(parseList[moveNum - 1], "#");
4646                 break;
4647             }
4648             strcat(parseList[moveNum - 1], " ");
4649             strcat(parseList[moveNum - 1], elapsed_time);
4650             /* currentMoveString is set as a side-effect of ParseOneMove */
4651             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4652             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4653             strcat(moveList[moveNum - 1], "\n");
4654
4655             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4656                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4657               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4658                 ChessSquare old, new = boards[moveNum][k][j];
4659                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4660                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4661                   if(old == new) continue;
4662                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4663                   else if(new == WhiteWazir || new == BlackWazir) {
4664                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4665                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4666                       else boards[moveNum][k][j] = old; // preserve type of Gold
4667                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4668                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4669               }
4670           } else {
4671             /* Move from ICS was illegal!?  Punt. */
4672             if (appData.debugMode) {
4673               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4674               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4675             }
4676             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4677             strcat(parseList[moveNum - 1], " ");
4678             strcat(parseList[moveNum - 1], elapsed_time);
4679             moveList[moveNum - 1][0] = NULLCHAR;
4680             fromX = fromY = toX = toY = -1;
4681           }
4682         }
4683   if (appData.debugMode) {
4684     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4685     setbuf(debugFP, NULL);
4686   }
4687
4688 #if ZIPPY
4689         /* Send move to chess program (BEFORE animating it). */
4690         if (appData.zippyPlay && !newGame && newMove &&
4691            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4692
4693             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4694                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4695                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4696                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4697                             move_str);
4698                     DisplayError(str, 0);
4699                 } else {
4700                     if (first.sendTime) {
4701                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4702                     }
4703                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4704                     if (firstMove && !bookHit) {
4705                         firstMove = FALSE;
4706                         if (first.useColors) {
4707                           SendToProgram(gameMode == IcsPlayingWhite ?
4708                                         "white\ngo\n" :
4709                                         "black\ngo\n", &first);
4710                         } else {
4711                           SendToProgram("go\n", &first);
4712                         }
4713                         first.maybeThinking = TRUE;
4714                     }
4715                 }
4716             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4717               if (moveList[moveNum - 1][0] == NULLCHAR) {
4718                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4719                 DisplayError(str, 0);
4720               } else {
4721                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4722                 SendMoveToProgram(moveNum - 1, &first);
4723               }
4724             }
4725         }
4726 #endif
4727     }
4728
4729     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4730         /* If move comes from a remote source, animate it.  If it
4731            isn't remote, it will have already been animated. */
4732         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4733             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4734         }
4735         if (!pausing && appData.highlightLastMove) {
4736             SetHighlights(fromX, fromY, toX, toY);
4737         }
4738     }
4739
4740     /* Start the clocks */
4741     whiteFlag = blackFlag = FALSE;
4742     appData.clockMode = !(basetime == 0 && increment == 0);
4743     if (ticking == 0) {
4744       ics_clock_paused = TRUE;
4745       StopClocks();
4746     } else if (ticking == 1) {
4747       ics_clock_paused = FALSE;
4748     }
4749     if (gameMode == IcsIdle ||
4750         relation == RELATION_OBSERVING_STATIC ||
4751         relation == RELATION_EXAMINING ||
4752         ics_clock_paused)
4753       DisplayBothClocks();
4754     else
4755       StartClocks();
4756
4757     /* Display opponents and material strengths */
4758     if (gameInfo.variant != VariantBughouse &&
4759         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4760         if (tinyLayout || smallLayout) {
4761             if(gameInfo.variant == VariantNormal)
4762               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4763                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4764                     basetime, increment);
4765             else
4766               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4767                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4768                     basetime, increment, (int) gameInfo.variant);
4769         } else {
4770             if(gameInfo.variant == VariantNormal)
4771               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4772                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4773                     basetime, increment);
4774             else
4775               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4776                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4777                     basetime, increment, VariantName(gameInfo.variant));
4778         }
4779         DisplayTitle(str);
4780   if (appData.debugMode) {
4781     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4782   }
4783     }
4784
4785
4786     /* Display the board */
4787     if (!pausing && !appData.noGUI) {
4788
4789       if (appData.premove)
4790           if (!gotPremove ||
4791              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4792              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4793               ClearPremoveHighlights();
4794
4795       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4796         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4797       DrawPosition(j, boards[currentMove]);
4798
4799       DisplayMove(moveNum - 1);
4800       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4801             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4802               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4803         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4804       }
4805     }
4806
4807     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4808 #if ZIPPY
4809     if(bookHit) { // [HGM] book: simulate book reply
4810         static char bookMove[MSG_SIZ]; // a bit generous?
4811
4812         programStats.nodes = programStats.depth = programStats.time =
4813         programStats.score = programStats.got_only_move = 0;
4814         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4815
4816         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4817         strcat(bookMove, bookHit);
4818         HandleMachineMove(bookMove, &first);
4819     }
4820 #endif
4821 }
4822
4823 void
4824 GetMoveListEvent()
4825 {
4826     char buf[MSG_SIZ];
4827     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4828         ics_getting_history = H_REQUESTED;
4829         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4830         SendToICS(buf);
4831     }
4832 }
4833
4834 void
4835 AnalysisPeriodicEvent(force)
4836      int force;
4837 {
4838     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4839          && !force) || !appData.periodicUpdates)
4840       return;
4841
4842     /* Send . command to Crafty to collect stats */
4843     SendToProgram(".\n", &first);
4844
4845     /* Don't send another until we get a response (this makes
4846        us stop sending to old Crafty's which don't understand
4847        the "." command (sending illegal cmds resets node count & time,
4848        which looks bad)) */
4849     programStats.ok_to_send = 0;
4850 }
4851
4852 void ics_update_width(new_width)
4853         int new_width;
4854 {
4855         ics_printf("set width %d\n", new_width);
4856 }
4857
4858 void
4859 SendMoveToProgram(moveNum, cps)
4860      int moveNum;
4861      ChessProgramState *cps;
4862 {
4863     char buf[MSG_SIZ];
4864
4865     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4866         // null move in variant where engine does not understand it (for analysis purposes)
4867         SendBoard(cps, moveNum + 1); // send position after move in stead.
4868         return;
4869     }
4870     if (cps->useUsermove) {
4871       SendToProgram("usermove ", cps);
4872     }
4873     if (cps->useSAN) {
4874       char *space;
4875       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4876         int len = space - parseList[moveNum];
4877         memcpy(buf, parseList[moveNum], len);
4878         buf[len++] = '\n';
4879         buf[len] = NULLCHAR;
4880       } else {
4881         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4882       }
4883       SendToProgram(buf, cps);
4884     } else {
4885       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4886         AlphaRank(moveList[moveNum], 4);
4887         SendToProgram(moveList[moveNum], cps);
4888         AlphaRank(moveList[moveNum], 4); // and back
4889       } else
4890       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4891        * the engine. It would be nice to have a better way to identify castle
4892        * moves here. */
4893       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4894                                                                          && cps->useOOCastle) {
4895         int fromX = moveList[moveNum][0] - AAA;
4896         int fromY = moveList[moveNum][1] - ONE;
4897         int toX = moveList[moveNum][2] - AAA;
4898         int toY = moveList[moveNum][3] - ONE;
4899         if((boards[moveNum][fromY][fromX] == WhiteKing
4900             && boards[moveNum][toY][toX] == WhiteRook)
4901            || (boards[moveNum][fromY][fromX] == BlackKing
4902                && boards[moveNum][toY][toX] == BlackRook)) {
4903           if(toX > fromX) SendToProgram("O-O\n", cps);
4904           else SendToProgram("O-O-O\n", cps);
4905         }
4906         else SendToProgram(moveList[moveNum], cps);
4907       } else
4908       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4909         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4910           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4911           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4912                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4913         } else
4914           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4915                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4916         SendToProgram(buf, cps);
4917       }
4918       else SendToProgram(moveList[moveNum], cps);
4919       /* End of additions by Tord */
4920     }
4921
4922     /* [HGM] setting up the opening has brought engine in force mode! */
4923     /*       Send 'go' if we are in a mode where machine should play. */
4924     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4925         (gameMode == TwoMachinesPlay   ||
4926 #if ZIPPY
4927          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4928 #endif
4929          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4930         SendToProgram("go\n", cps);
4931   if (appData.debugMode) {
4932     fprintf(debugFP, "(extra)\n");
4933   }
4934     }
4935     setboardSpoiledMachineBlack = 0;
4936 }
4937
4938 void
4939 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4940      ChessMove moveType;
4941      int fromX, fromY, toX, toY;
4942      char promoChar;
4943 {
4944     char user_move[MSG_SIZ];
4945
4946     switch (moveType) {
4947       default:
4948         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4949                 (int)moveType, fromX, fromY, toX, toY);
4950         DisplayError(user_move + strlen("say "), 0);
4951         break;
4952       case WhiteKingSideCastle:
4953       case BlackKingSideCastle:
4954       case WhiteQueenSideCastleWild:
4955       case BlackQueenSideCastleWild:
4956       /* PUSH Fabien */
4957       case WhiteHSideCastleFR:
4958       case BlackHSideCastleFR:
4959       /* POP Fabien */
4960         snprintf(user_move, MSG_SIZ, "o-o\n");
4961         break;
4962       case WhiteQueenSideCastle:
4963       case BlackQueenSideCastle:
4964       case WhiteKingSideCastleWild:
4965       case BlackKingSideCastleWild:
4966       /* PUSH Fabien */
4967       case WhiteASideCastleFR:
4968       case BlackASideCastleFR:
4969       /* POP Fabien */
4970         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4971         break;
4972       case WhiteNonPromotion:
4973       case BlackNonPromotion:
4974         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4975         break;
4976       case WhitePromotion:
4977       case BlackPromotion:
4978         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4979           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4980                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4981                 PieceToChar(WhiteFerz));
4982         else if(gameInfo.variant == VariantGreat)
4983           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4984                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4985                 PieceToChar(WhiteMan));
4986         else
4987           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4988                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4989                 promoChar);
4990         break;
4991       case WhiteDrop:
4992       case BlackDrop:
4993       drop:
4994         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4995                  ToUpper(PieceToChar((ChessSquare) fromX)),
4996                  AAA + toX, ONE + toY);
4997         break;
4998       case IllegalMove:  /* could be a variant we don't quite understand */
4999         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5000       case NormalMove:
5001       case WhiteCapturesEnPassant:
5002       case BlackCapturesEnPassant:
5003         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5004                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5005         break;
5006     }
5007     SendToICS(user_move);
5008     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5009         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5010 }
5011
5012 void
5013 UploadGameEvent()
5014 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5015     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5016     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5017     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5018         DisplayError("You cannot do this while you are playing or observing", 0);
5019         return;
5020     }
5021     if(gameMode != IcsExamining) { // is this ever not the case?
5022         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5023
5024         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5025           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5026         } else { // on FICS we must first go to general examine mode
5027           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5028         }
5029         if(gameInfo.variant != VariantNormal) {
5030             // try figure out wild number, as xboard names are not always valid on ICS
5031             for(i=1; i<=36; i++) {
5032               snprintf(buf, MSG_SIZ, "wild/%d", i);
5033                 if(StringToVariant(buf) == gameInfo.variant) break;
5034             }
5035             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5036             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5037             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5038         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5039         SendToICS(ics_prefix);
5040         SendToICS(buf);
5041         if(startedFromSetupPosition || backwardMostMove != 0) {
5042           fen = PositionToFEN(backwardMostMove, NULL);
5043           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5044             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5045             SendToICS(buf);
5046           } else { // FICS: everything has to set by separate bsetup commands
5047             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5048             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5049             SendToICS(buf);
5050             if(!WhiteOnMove(backwardMostMove)) {
5051                 SendToICS("bsetup tomove black\n");
5052             }
5053             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5054             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5055             SendToICS(buf);
5056             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5057             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5058             SendToICS(buf);
5059             i = boards[backwardMostMove][EP_STATUS];
5060             if(i >= 0) { // set e.p.
5061               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5062                 SendToICS(buf);
5063             }
5064             bsetup++;
5065           }
5066         }
5067       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5068             SendToICS("bsetup done\n"); // switch to normal examining.
5069     }
5070     for(i = backwardMostMove; i<last; i++) {
5071         char buf[20];
5072         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5073         SendToICS(buf);
5074     }
5075     SendToICS(ics_prefix);
5076     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5077 }
5078
5079 void
5080 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5081      int rf, ff, rt, ft;
5082      char promoChar;
5083      char move[7];
5084 {
5085     if (rf == DROP_RANK) {
5086       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5087       sprintf(move, "%c@%c%c\n",
5088                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5089     } else {
5090         if (promoChar == 'x' || promoChar == NULLCHAR) {
5091           sprintf(move, "%c%c%c%c\n",
5092                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5093         } else {
5094             sprintf(move, "%c%c%c%c%c\n",
5095                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5096         }
5097     }
5098 }
5099
5100 void
5101 ProcessICSInitScript(f)
5102      FILE *f;
5103 {
5104     char buf[MSG_SIZ];
5105
5106     while (fgets(buf, MSG_SIZ, f)) {
5107         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5108     }
5109
5110     fclose(f);
5111 }
5112
5113
5114 static int lastX, lastY, selectFlag, dragging;
5115
5116 void
5117 Sweep(int step)
5118 {
5119     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5120     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5121     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5122     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5123     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5124     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5125     do {
5126         promoSweep -= step;
5127         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5128         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5129         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5130         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5131         if(!step) step = 1;
5132     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5133             appData.testLegality && (promoSweep == king ||
5134             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5135     ChangeDragPiece(promoSweep);
5136 }
5137
5138 int PromoScroll(int x, int y)
5139 {
5140   int step = 0;
5141
5142   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5143   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5144   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5145   if(!step) return FALSE;
5146   lastX = x; lastY = y;
5147   if((promoSweep < BlackPawn) == flipView) step = -step;
5148   if(step > 0) selectFlag = 1;
5149   if(!selectFlag) Sweep(step);
5150   return FALSE;
5151 }
5152
5153 void
5154 NextPiece(int step)
5155 {
5156     ChessSquare piece = boards[currentMove][toY][toX];
5157     do {
5158         pieceSweep -= step;
5159         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5160         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5161         if(!step) step = -1;
5162     } while(PieceToChar(pieceSweep) == '.');
5163     boards[currentMove][toY][toX] = pieceSweep;
5164     DrawPosition(FALSE, boards[currentMove]);
5165     boards[currentMove][toY][toX] = piece;
5166 }
5167 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5168 void
5169 AlphaRank(char *move, int n)
5170 {
5171 //    char *p = move, c; int x, y;
5172
5173     if (appData.debugMode) {
5174         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5175     }
5176
5177     if(move[1]=='*' &&
5178        move[2]>='0' && move[2]<='9' &&
5179        move[3]>='a' && move[3]<='x'    ) {
5180         move[1] = '@';
5181         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5182         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5183     } else
5184     if(move[0]>='0' && move[0]<='9' &&
5185        move[1]>='a' && move[1]<='x' &&
5186        move[2]>='0' && move[2]<='9' &&
5187        move[3]>='a' && move[3]<='x'    ) {
5188         /* input move, Shogi -> normal */
5189         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5190         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5191         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5192         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5193     } else
5194     if(move[1]=='@' &&
5195        move[3]>='0' && move[3]<='9' &&
5196        move[2]>='a' && move[2]<='x'    ) {
5197         move[1] = '*';
5198         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5199         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5200     } else
5201     if(
5202        move[0]>='a' && move[0]<='x' &&
5203        move[3]>='0' && move[3]<='9' &&
5204        move[2]>='a' && move[2]<='x'    ) {
5205          /* output move, normal -> Shogi */
5206         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5207         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5208         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5209         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5210         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5211     }
5212     if (appData.debugMode) {
5213         fprintf(debugFP, "   out = '%s'\n", move);
5214     }
5215 }
5216
5217 char yy_textstr[8000];
5218
5219 /* Parser for moves from gnuchess, ICS, or user typein box */
5220 Boolean
5221 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5222      char *move;
5223      int moveNum;
5224      ChessMove *moveType;
5225      int *fromX, *fromY, *toX, *toY;
5226      char *promoChar;
5227 {
5228     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5229
5230     switch (*moveType) {
5231       case WhitePromotion:
5232       case BlackPromotion:
5233       case WhiteNonPromotion:
5234       case BlackNonPromotion:
5235       case NormalMove:
5236       case WhiteCapturesEnPassant:
5237       case BlackCapturesEnPassant:
5238       case WhiteKingSideCastle:
5239       case WhiteQueenSideCastle:
5240       case BlackKingSideCastle:
5241       case BlackQueenSideCastle:
5242       case WhiteKingSideCastleWild:
5243       case WhiteQueenSideCastleWild:
5244       case BlackKingSideCastleWild:
5245       case BlackQueenSideCastleWild:
5246       /* Code added by Tord: */
5247       case WhiteHSideCastleFR:
5248       case WhiteASideCastleFR:
5249       case BlackHSideCastleFR:
5250       case BlackASideCastleFR:
5251       /* End of code added by Tord */
5252       case IllegalMove:         /* bug or odd chess variant */
5253         *fromX = currentMoveString[0] - AAA;
5254         *fromY = currentMoveString[1] - ONE;
5255         *toX = currentMoveString[2] - AAA;
5256         *toY = currentMoveString[3] - ONE;
5257         *promoChar = currentMoveString[4];
5258         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5259             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5260     if (appData.debugMode) {
5261         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5262     }
5263             *fromX = *fromY = *toX = *toY = 0;
5264             return FALSE;
5265         }
5266         if (appData.testLegality) {
5267           return (*moveType != IllegalMove);
5268         } else {
5269           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5270                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5271         }
5272
5273       case WhiteDrop:
5274       case BlackDrop:
5275         *fromX = *moveType == WhiteDrop ?
5276           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5277           (int) CharToPiece(ToLower(currentMoveString[0]));
5278         *fromY = DROP_RANK;
5279         *toX = currentMoveString[2] - AAA;
5280         *toY = currentMoveString[3] - ONE;
5281         *promoChar = NULLCHAR;
5282         return TRUE;
5283
5284       case AmbiguousMove:
5285       case ImpossibleMove:
5286       case EndOfFile:
5287       case ElapsedTime:
5288       case Comment:
5289       case PGNTag:
5290       case NAG:
5291       case WhiteWins:
5292       case BlackWins:
5293       case GameIsDrawn:
5294       default:
5295     if (appData.debugMode) {
5296         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5297     }
5298         /* bug? */
5299         *fromX = *fromY = *toX = *toY = 0;
5300         *promoChar = NULLCHAR;
5301         return FALSE;
5302     }
5303 }
5304
5305 Boolean pushed = FALSE;
5306 char *lastParseAttempt;
5307
5308 void
5309 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5310 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5311   int fromX, fromY, toX, toY; char promoChar;
5312   ChessMove moveType;
5313   Boolean valid;
5314   int nr = 0;
5315
5316   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5317     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5318     pushed = TRUE;
5319   }
5320   endPV = forwardMostMove;
5321   do {
5322     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5323     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5324     lastParseAttempt = pv;
5325     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5326 if(appData.debugMode){
5327 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);
5328 }
5329     if(!valid && nr == 0 &&
5330        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5331         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5332         // Hande case where played move is different from leading PV move
5333         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5334         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5335         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5336         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5337           endPV += 2; // if position different, keep this
5338           moveList[endPV-1][0] = fromX + AAA;
5339           moveList[endPV-1][1] = fromY + ONE;
5340           moveList[endPV-1][2] = toX + AAA;
5341           moveList[endPV-1][3] = toY + ONE;
5342           parseList[endPV-1][0] = NULLCHAR;
5343           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5344         }
5345       }
5346     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5347     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5348     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5349     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5350         valid++; // allow comments in PV
5351         continue;
5352     }
5353     nr++;
5354     if(endPV+1 > framePtr) break; // no space, truncate
5355     if(!valid) break;
5356     endPV++;
5357     CopyBoard(boards[endPV], boards[endPV-1]);
5358     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5359     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5360     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5361     CoordsToAlgebraic(boards[endPV - 1],
5362                              PosFlags(endPV - 1),
5363                              fromY, fromX, toY, toX, promoChar,
5364                              parseList[endPV - 1]);
5365   } while(valid);
5366   if(atEnd == 2) return; // used hidden, for PV conversion
5367   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5368   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5369   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5370                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5371   DrawPosition(TRUE, boards[currentMove]);
5372 }
5373
5374 int
5375 MultiPV(ChessProgramState *cps)
5376 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5377         int i;
5378         for(i=0; i<cps->nrOptions; i++)
5379             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5380                 return i;
5381         return -1;
5382 }
5383
5384 Boolean
5385 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5386 {
5387         int startPV, multi, lineStart, origIndex = index;
5388         char *p, buf2[MSG_SIZ];
5389
5390         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5391         lastX = x; lastY = y;
5392         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5393         lineStart = startPV = index;
5394         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5395         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5396         index = startPV;
5397         do{ while(buf[index] && buf[index] != '\n') index++;
5398         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5399         buf[index] = 0;
5400         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5401                 int n = first.option[multi].value;
5402                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5403                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5404                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5405                 first.option[multi].value = n;
5406                 *start = *end = 0;
5407                 return FALSE;
5408         }
5409         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5410         *start = startPV; *end = index-1;
5411         return TRUE;
5412 }
5413
5414 char *
5415 PvToSAN(char *pv)
5416 {
5417         static char buf[10*MSG_SIZ];
5418         int i, k=0, savedEnd=endPV;
5419         *buf = NULLCHAR;
5420         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5421         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5422         for(i = forwardMostMove; i<endPV; i++){
5423             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5424             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5425             k += strlen(buf+k);
5426         }
5427         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5428         if(forwardMostMove < savedEnd) PopInner(0);
5429         endPV = savedEnd;
5430         return buf;
5431 }
5432
5433 Boolean
5434 LoadPV(int x, int y)
5435 { // called on right mouse click to load PV
5436   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5437   lastX = x; lastY = y;
5438   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5439   return TRUE;
5440 }
5441
5442 void
5443 UnLoadPV()
5444 {
5445   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5446   if(endPV < 0) return;
5447   endPV = -1;
5448   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5449         Boolean saveAnimate = appData.animate;
5450         if(pushed) {
5451             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5452                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5453             } else storedGames--; // abandon shelved tail of original game
5454         }
5455         pushed = FALSE;
5456         forwardMostMove = currentMove;
5457         currentMove = oldFMM;
5458         appData.animate = FALSE;
5459         ToNrEvent(forwardMostMove);
5460         appData.animate = saveAnimate;
5461   }
5462   currentMove = forwardMostMove;
5463   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5464   ClearPremoveHighlights();
5465   DrawPosition(TRUE, boards[currentMove]);
5466 }
5467
5468 void
5469 MovePV(int x, int y, int h)
5470 { // step through PV based on mouse coordinates (called on mouse move)
5471   int margin = h>>3, step = 0;
5472
5473   // we must somehow check if right button is still down (might be released off board!)
5474   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5475   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5476   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5477   if(!step) return;
5478   lastX = x; lastY = y;
5479
5480   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5481   if(endPV < 0) return;
5482   if(y < margin) step = 1; else
5483   if(y > h - margin) step = -1;
5484   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5485   currentMove += step;
5486   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5487   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5488                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5489   DrawPosition(FALSE, boards[currentMove]);
5490 }
5491
5492
5493 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5494 // All positions will have equal probability, but the current method will not provide a unique
5495 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5496 #define DARK 1
5497 #define LITE 2
5498 #define ANY 3
5499
5500 int squaresLeft[4];
5501 int piecesLeft[(int)BlackPawn];
5502 int seed, nrOfShuffles;
5503
5504 void GetPositionNumber()
5505 {       // sets global variable seed
5506         int i;
5507
5508         seed = appData.defaultFrcPosition;
5509         if(seed < 0) { // randomize based on time for negative FRC position numbers
5510                 for(i=0; i<50; i++) seed += random();
5511                 seed = random() ^ random() >> 8 ^ random() << 8;
5512                 if(seed<0) seed = -seed;
5513         }
5514 }
5515
5516 int put(Board board, int pieceType, int rank, int n, int shade)
5517 // put the piece on the (n-1)-th empty squares of the given shade
5518 {
5519         int i;
5520
5521         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5522                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5523                         board[rank][i] = (ChessSquare) pieceType;
5524                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5525                         squaresLeft[ANY]--;
5526                         piecesLeft[pieceType]--;
5527                         return i;
5528                 }
5529         }
5530         return -1;
5531 }
5532
5533
5534 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5535 // calculate where the next piece goes, (any empty square), and put it there
5536 {
5537         int i;
5538
5539         i = seed % squaresLeft[shade];
5540         nrOfShuffles *= squaresLeft[shade];
5541         seed /= squaresLeft[shade];
5542         put(board, pieceType, rank, i, shade);
5543 }
5544
5545 void AddTwoPieces(Board board, int pieceType, int rank)
5546 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5547 {
5548         int i, n=squaresLeft[ANY], j=n-1, k;
5549
5550         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5551         i = seed % k;  // pick one
5552         nrOfShuffles *= k;
5553         seed /= k;
5554         while(i >= j) i -= j--;
5555         j = n - 1 - j; i += j;
5556         put(board, pieceType, rank, j, ANY);
5557         put(board, pieceType, rank, i, ANY);
5558 }
5559
5560 void SetUpShuffle(Board board, int number)
5561 {
5562         int i, p, first=1;
5563
5564         GetPositionNumber(); nrOfShuffles = 1;
5565
5566         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5567         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5568         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5569
5570         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5571
5572         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5573             p = (int) board[0][i];
5574             if(p < (int) BlackPawn) piecesLeft[p] ++;
5575             board[0][i] = EmptySquare;
5576         }
5577
5578         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5579             // shuffles restricted to allow normal castling put KRR first
5580             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5581                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5582             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5583                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5584             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5585                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5586             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5587                 put(board, WhiteRook, 0, 0, ANY);
5588             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5589         }
5590
5591         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5592             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5593             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5594                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5595                 while(piecesLeft[p] >= 2) {
5596                     AddOnePiece(board, p, 0, LITE);
5597                     AddOnePiece(board, p, 0, DARK);
5598                 }
5599                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5600             }
5601
5602         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5603             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5604             // but we leave King and Rooks for last, to possibly obey FRC restriction
5605             if(p == (int)WhiteRook) continue;
5606             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5607             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5608         }
5609
5610         // now everything is placed, except perhaps King (Unicorn) and Rooks
5611
5612         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5613             // Last King gets castling rights
5614             while(piecesLeft[(int)WhiteUnicorn]) {
5615                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5616                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5617             }
5618
5619             while(piecesLeft[(int)WhiteKing]) {
5620                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5621                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5622             }
5623
5624
5625         } else {
5626             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5627             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5628         }
5629
5630         // Only Rooks can be left; simply place them all
5631         while(piecesLeft[(int)WhiteRook]) {
5632                 i = put(board, WhiteRook, 0, 0, ANY);
5633                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5634                         if(first) {
5635                                 first=0;
5636                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5637                         }
5638                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5639                 }
5640         }
5641         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5642             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5643         }
5644
5645         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5646 }
5647
5648 int SetCharTable( char *table, const char * map )
5649 /* [HGM] moved here from winboard.c because of its general usefulness */
5650 /*       Basically a safe strcpy that uses the last character as King */
5651 {
5652     int result = FALSE; int NrPieces;
5653
5654     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5655                     && NrPieces >= 12 && !(NrPieces&1)) {
5656         int i; /* [HGM] Accept even length from 12 to 34 */
5657
5658         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5659         for( i=0; i<NrPieces/2-1; i++ ) {
5660             table[i] = map[i];
5661             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5662         }
5663         table[(int) WhiteKing]  = map[NrPieces/2-1];
5664         table[(int) BlackKing]  = map[NrPieces-1];
5665
5666         result = TRUE;
5667     }
5668
5669     return result;
5670 }
5671
5672 void Prelude(Board board)
5673 {       // [HGM] superchess: random selection of exo-pieces
5674         int i, j, k; ChessSquare p;
5675         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5676
5677         GetPositionNumber(); // use FRC position number
5678
5679         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5680             SetCharTable(pieceToChar, appData.pieceToCharTable);
5681             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5682                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5683         }
5684
5685         j = seed%4;                 seed /= 4;
5686         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5687         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5688         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5689         j = seed%3 + (seed%3 >= j); seed /= 3;
5690         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5691         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5692         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5693         j = seed%3;                 seed /= 3;
5694         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5695         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5696         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5697         j = seed%2 + (seed%2 >= j); seed /= 2;
5698         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5699         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5700         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5701         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5702         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5703         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5704         put(board, exoPieces[0],    0, 0, ANY);
5705         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5706 }
5707
5708 void
5709 InitPosition(redraw)
5710      int redraw;
5711 {
5712     ChessSquare (* pieces)[BOARD_FILES];
5713     int i, j, pawnRow, overrule,
5714     oldx = gameInfo.boardWidth,
5715     oldy = gameInfo.boardHeight,
5716     oldh = gameInfo.holdingsWidth;
5717     static int oldv;
5718
5719     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5720
5721     /* [AS] Initialize pv info list [HGM] and game status */
5722     {
5723         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5724             pvInfoList[i].depth = 0;
5725             boards[i][EP_STATUS] = EP_NONE;
5726             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5727         }
5728
5729         initialRulePlies = 0; /* 50-move counter start */
5730
5731         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5732         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5733     }
5734
5735
5736     /* [HGM] logic here is completely changed. In stead of full positions */
5737     /* the initialized data only consist of the two backranks. The switch */
5738     /* selects which one we will use, which is than copied to the Board   */
5739     /* initialPosition, which for the rest is initialized by Pawns and    */
5740     /* empty squares. This initial position is then copied to boards[0],  */
5741     /* possibly after shuffling, so that it remains available.            */
5742
5743     gameInfo.holdingsWidth = 0; /* default board sizes */
5744     gameInfo.boardWidth    = 8;
5745     gameInfo.boardHeight   = 8;
5746     gameInfo.holdingsSize  = 0;
5747     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5748     for(i=0; i<BOARD_FILES-2; i++)
5749       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5750     initialPosition[EP_STATUS] = EP_NONE;
5751     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5752     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5753          SetCharTable(pieceNickName, appData.pieceNickNames);
5754     else SetCharTable(pieceNickName, "............");
5755     pieces = FIDEArray;
5756
5757     switch (gameInfo.variant) {
5758     case VariantFischeRandom:
5759       shuffleOpenings = TRUE;
5760     default:
5761       break;
5762     case VariantShatranj:
5763       pieces = ShatranjArray;
5764       nrCastlingRights = 0;
5765       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5766       break;
5767     case VariantMakruk:
5768       pieces = makrukArray;
5769       nrCastlingRights = 0;
5770       startedFromSetupPosition = TRUE;
5771       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5772       break;
5773     case VariantTwoKings:
5774       pieces = twoKingsArray;
5775       break;
5776     case VariantGrand:
5777       pieces = GrandArray;
5778       nrCastlingRights = 0;
5779       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5780       gameInfo.boardWidth = 10;
5781       gameInfo.boardHeight = 10;
5782       gameInfo.holdingsSize = 7;
5783       break;
5784     case VariantCapaRandom:
5785       shuffleOpenings = TRUE;
5786     case VariantCapablanca:
5787       pieces = CapablancaArray;
5788       gameInfo.boardWidth = 10;
5789       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5790       break;
5791     case VariantGothic:
5792       pieces = GothicArray;
5793       gameInfo.boardWidth = 10;
5794       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5795       break;
5796     case VariantSChess:
5797       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5798       gameInfo.holdingsSize = 7;
5799       break;
5800     case VariantJanus:
5801       pieces = JanusArray;
5802       gameInfo.boardWidth = 10;
5803       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5804       nrCastlingRights = 6;
5805         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5806         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5807         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5808         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5809         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5810         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5811       break;
5812     case VariantFalcon:
5813       pieces = FalconArray;
5814       gameInfo.boardWidth = 10;
5815       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5816       break;
5817     case VariantXiangqi:
5818       pieces = XiangqiArray;
5819       gameInfo.boardWidth  = 9;
5820       gameInfo.boardHeight = 10;
5821       nrCastlingRights = 0;
5822       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5823       break;
5824     case VariantShogi:
5825       pieces = ShogiArray;
5826       gameInfo.boardWidth  = 9;
5827       gameInfo.boardHeight = 9;
5828       gameInfo.holdingsSize = 7;
5829       nrCastlingRights = 0;
5830       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5831       break;
5832     case VariantCourier:
5833       pieces = CourierArray;
5834       gameInfo.boardWidth  = 12;
5835       nrCastlingRights = 0;
5836       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5837       break;
5838     case VariantKnightmate:
5839       pieces = KnightmateArray;
5840       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5841       break;
5842     case VariantSpartan:
5843       pieces = SpartanArray;
5844       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5845       break;
5846     case VariantFairy:
5847       pieces = fairyArray;
5848       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5849       break;
5850     case VariantGreat:
5851       pieces = GreatArray;
5852       gameInfo.boardWidth = 10;
5853       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5854       gameInfo.holdingsSize = 8;
5855       break;
5856     case VariantSuper:
5857       pieces = FIDEArray;
5858       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5859       gameInfo.holdingsSize = 8;
5860       startedFromSetupPosition = TRUE;
5861       break;
5862     case VariantCrazyhouse:
5863     case VariantBughouse:
5864       pieces = FIDEArray;
5865       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5866       gameInfo.holdingsSize = 5;
5867       break;
5868     case VariantWildCastle:
5869       pieces = FIDEArray;
5870       /* !!?shuffle with kings guaranteed to be on d or e file */
5871       shuffleOpenings = 1;
5872       break;
5873     case VariantNoCastle:
5874       pieces = FIDEArray;
5875       nrCastlingRights = 0;
5876       /* !!?unconstrained back-rank shuffle */
5877       shuffleOpenings = 1;
5878       break;
5879     }
5880
5881     overrule = 0;
5882     if(appData.NrFiles >= 0) {
5883         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5884         gameInfo.boardWidth = appData.NrFiles;
5885     }
5886     if(appData.NrRanks >= 0) {
5887         gameInfo.boardHeight = appData.NrRanks;
5888     }
5889     if(appData.holdingsSize >= 0) {
5890         i = appData.holdingsSize;
5891         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5892         gameInfo.holdingsSize = i;
5893     }
5894     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5895     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5896         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5897
5898     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5899     if(pawnRow < 1) pawnRow = 1;
5900     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5901
5902     /* User pieceToChar list overrules defaults */
5903     if(appData.pieceToCharTable != NULL)
5904         SetCharTable(pieceToChar, appData.pieceToCharTable);
5905
5906     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5907
5908         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5909             s = (ChessSquare) 0; /* account holding counts in guard band */
5910         for( i=0; i<BOARD_HEIGHT; i++ )
5911             initialPosition[i][j] = s;
5912
5913         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5914         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5915         initialPosition[pawnRow][j] = WhitePawn;
5916         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5917         if(gameInfo.variant == VariantXiangqi) {
5918             if(j&1) {
5919                 initialPosition[pawnRow][j] =
5920                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5921                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5922                    initialPosition[2][j] = WhiteCannon;
5923                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5924                 }
5925             }
5926         }
5927         if(gameInfo.variant == VariantGrand) {
5928             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5929                initialPosition[0][j] = WhiteRook;
5930                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5931             }
5932         }
5933         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5934     }
5935     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5936
5937             j=BOARD_LEFT+1;
5938             initialPosition[1][j] = WhiteBishop;
5939             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5940             j=BOARD_RGHT-2;
5941             initialPosition[1][j] = WhiteRook;
5942             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5943     }
5944
5945     if( nrCastlingRights == -1) {
5946         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5947         /*       This sets default castling rights from none to normal corners   */
5948         /* Variants with other castling rights must set them themselves above    */
5949         nrCastlingRights = 6;
5950
5951         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5952         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5953         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5954         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5955         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5956         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5957      }
5958
5959      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5960      if(gameInfo.variant == VariantGreat) { // promotion commoners
5961         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5962         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5963         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5964         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5965      }
5966      if( gameInfo.variant == VariantSChess ) {
5967       initialPosition[1][0] = BlackMarshall;
5968       initialPosition[2][0] = BlackAngel;
5969       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5970       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5971       initialPosition[1][1] = initialPosition[2][1] = 
5972       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5973      }
5974   if (appData.debugMode) {
5975     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5976   }
5977     if(shuffleOpenings) {
5978         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5979         startedFromSetupPosition = TRUE;
5980     }
5981     if(startedFromPositionFile) {
5982       /* [HGM] loadPos: use PositionFile for every new game */
5983       CopyBoard(initialPosition, filePosition);
5984       for(i=0; i<nrCastlingRights; i++)
5985           initialRights[i] = filePosition[CASTLING][i];
5986       startedFromSetupPosition = TRUE;
5987     }
5988
5989     CopyBoard(boards[0], initialPosition);
5990
5991     if(oldx != gameInfo.boardWidth ||
5992        oldy != gameInfo.boardHeight ||
5993        oldv != gameInfo.variant ||
5994        oldh != gameInfo.holdingsWidth
5995                                          )
5996             InitDrawingSizes(-2 ,0);
5997
5998     oldv = gameInfo.variant;
5999     if (redraw)
6000       DrawPosition(TRUE, boards[currentMove]);
6001 }
6002
6003 void
6004 SendBoard(cps, moveNum)
6005      ChessProgramState *cps;
6006      int moveNum;
6007 {
6008     char message[MSG_SIZ];
6009
6010     if (cps->useSetboard) {
6011       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6012       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6013       SendToProgram(message, cps);
6014       free(fen);
6015
6016     } else {
6017       ChessSquare *bp;
6018       int i, j;
6019       /* Kludge to set black to move, avoiding the troublesome and now
6020        * deprecated "black" command.
6021        */
6022       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6023         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6024
6025       SendToProgram("edit\n", cps);
6026       SendToProgram("#\n", cps);
6027       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6028         bp = &boards[moveNum][i][BOARD_LEFT];
6029         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6030           if ((int) *bp < (int) BlackPawn) {
6031             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6032                     AAA + j, ONE + i);
6033             if(message[0] == '+' || message[0] == '~') {
6034               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6035                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6036                         AAA + j, ONE + i);
6037             }
6038             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6039                 message[1] = BOARD_RGHT   - 1 - j + '1';
6040                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6041             }
6042             SendToProgram(message, cps);
6043           }
6044         }
6045       }
6046
6047       SendToProgram("c\n", cps);
6048       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6049         bp = &boards[moveNum][i][BOARD_LEFT];
6050         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6051           if (((int) *bp != (int) EmptySquare)
6052               && ((int) *bp >= (int) BlackPawn)) {
6053             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6054                     AAA + j, ONE + i);
6055             if(message[0] == '+' || message[0] == '~') {
6056               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6057                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6058                         AAA + j, ONE + i);
6059             }
6060             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6061                 message[1] = BOARD_RGHT   - 1 - j + '1';
6062                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6063             }
6064             SendToProgram(message, cps);
6065           }
6066         }
6067       }
6068
6069       SendToProgram(".\n", cps);
6070     }
6071     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6072 }
6073
6074 ChessSquare
6075 DefaultPromoChoice(int white)
6076 {
6077     ChessSquare result;
6078     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6079         result = WhiteFerz; // no choice
6080     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6081         result= WhiteKing; // in Suicide Q is the last thing we want
6082     else if(gameInfo.variant == VariantSpartan)
6083         result = white ? WhiteQueen : WhiteAngel;
6084     else result = WhiteQueen;
6085     if(!white) result = WHITE_TO_BLACK result;
6086     return result;
6087 }
6088
6089 static int autoQueen; // [HGM] oneclick
6090
6091 int
6092 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6093 {
6094     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6095     /* [HGM] add Shogi promotions */
6096     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6097     ChessSquare piece;
6098     ChessMove moveType;
6099     Boolean premove;
6100
6101     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6102     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6103
6104     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6105       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6106         return FALSE;
6107
6108     piece = boards[currentMove][fromY][fromX];
6109     if(gameInfo.variant == VariantShogi) {
6110         promotionZoneSize = BOARD_HEIGHT/3;
6111         highestPromotingPiece = (int)WhiteFerz;
6112     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6113         promotionZoneSize = 3;
6114     }
6115
6116     // Treat Lance as Pawn when it is not representing Amazon
6117     if(gameInfo.variant != VariantSuper) {
6118         if(piece == WhiteLance) piece = WhitePawn; else
6119         if(piece == BlackLance) piece = BlackPawn;
6120     }
6121
6122     // next weed out all moves that do not touch the promotion zone at all
6123     if((int)piece >= BlackPawn) {
6124         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6125              return FALSE;
6126         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6127     } else {
6128         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6129            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6130     }
6131
6132     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6133
6134     // weed out mandatory Shogi promotions
6135     if(gameInfo.variant == VariantShogi) {
6136         if(piece >= BlackPawn) {
6137             if(toY == 0 && piece == BlackPawn ||
6138                toY == 0 && piece == BlackQueen ||
6139                toY <= 1 && piece == BlackKnight) {
6140                 *promoChoice = '+';
6141                 return FALSE;
6142             }
6143         } else {
6144             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6145                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6146                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6147                 *promoChoice = '+';
6148                 return FALSE;
6149             }
6150         }
6151     }
6152
6153     // weed out obviously illegal Pawn moves
6154     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6155         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6156         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6157         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6158         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6159         // note we are not allowed to test for valid (non-)capture, due to premove
6160     }
6161
6162     // we either have a choice what to promote to, or (in Shogi) whether to promote
6163     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6164         *promoChoice = PieceToChar(BlackFerz);  // no choice
6165         return FALSE;
6166     }
6167     // no sense asking what we must promote to if it is going to explode...
6168     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6169         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6170         return FALSE;
6171     }
6172     // give caller the default choice even if we will not make it
6173     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6174     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6175     if(        sweepSelect && gameInfo.variant != VariantGreat
6176                            && gameInfo.variant != VariantGrand
6177                            && gameInfo.variant != VariantSuper) return FALSE;
6178     if(autoQueen) return FALSE; // predetermined
6179
6180     // suppress promotion popup on illegal moves that are not premoves
6181     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6182               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6183     if(appData.testLegality && !premove) {
6184         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6185                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6186         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6187             return FALSE;
6188     }
6189
6190     return TRUE;
6191 }
6192
6193 int
6194 InPalace(row, column)
6195      int row, column;
6196 {   /* [HGM] for Xiangqi */
6197     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6198          column < (BOARD_WIDTH + 4)/2 &&
6199          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6200     return FALSE;
6201 }
6202
6203 int
6204 PieceForSquare (x, y)
6205      int x;
6206      int y;
6207 {
6208   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6209      return -1;
6210   else
6211      return boards[currentMove][y][x];
6212 }
6213
6214 int
6215 OKToStartUserMove(x, y)
6216      int x, y;
6217 {
6218     ChessSquare from_piece;
6219     int white_piece;
6220
6221     if (matchMode) return FALSE;
6222     if (gameMode == EditPosition) return TRUE;
6223
6224     if (x >= 0 && y >= 0)
6225       from_piece = boards[currentMove][y][x];
6226     else
6227       from_piece = EmptySquare;
6228
6229     if (from_piece == EmptySquare) return FALSE;
6230
6231     white_piece = (int)from_piece >= (int)WhitePawn &&
6232       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6233
6234     switch (gameMode) {
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 PlayFromGameFile:
6263             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6264       case EditGame:
6265         if (!white_piece && WhiteOnMove(currentMove)) {
6266             DisplayMoveError(_("It is White's turn"));
6267             return FALSE;
6268         }
6269         if (white_piece && !WhiteOnMove(currentMove)) {
6270             DisplayMoveError(_("It is Black's turn"));
6271             return FALSE;
6272         }
6273         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6274             /* Editing correspondence game history */
6275             /* Could disallow this or prompt for confirmation */
6276             cmailOldMove = -1;
6277         }
6278         break;
6279
6280       case BeginningOfGame:
6281         if (appData.icsActive) return FALSE;
6282         if (!appData.noChessProgram) {
6283             if (!white_piece) {
6284                 DisplayMoveError(_("You are playing White"));
6285                 return FALSE;
6286             }
6287         }
6288         break;
6289
6290       case Training:
6291         if (!white_piece && WhiteOnMove(currentMove)) {
6292             DisplayMoveError(_("It is White's turn"));
6293             return FALSE;
6294         }
6295         if (white_piece && !WhiteOnMove(currentMove)) {
6296             DisplayMoveError(_("It is Black's turn"));
6297             return FALSE;
6298         }
6299         break;
6300
6301       default:
6302       case IcsExamining:
6303         break;
6304     }
6305     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6306         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6307         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6308         && gameMode != AnalyzeFile && gameMode != Training) {
6309         DisplayMoveError(_("Displayed position is not current"));
6310         return FALSE;
6311     }
6312     return TRUE;
6313 }
6314
6315 Boolean
6316 OnlyMove(int *x, int *y, Boolean captures) {
6317     DisambiguateClosure cl;
6318     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6319     switch(gameMode) {
6320       case MachinePlaysBlack:
6321       case IcsPlayingWhite:
6322       case BeginningOfGame:
6323         if(!WhiteOnMove(currentMove)) return FALSE;
6324         break;
6325       case MachinePlaysWhite:
6326       case IcsPlayingBlack:
6327         if(WhiteOnMove(currentMove)) return FALSE;
6328         break;
6329       case EditGame:
6330         break;
6331       default:
6332         return FALSE;
6333     }
6334     cl.pieceIn = EmptySquare;
6335     cl.rfIn = *y;
6336     cl.ffIn = *x;
6337     cl.rtIn = -1;
6338     cl.ftIn = -1;
6339     cl.promoCharIn = NULLCHAR;
6340     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6341     if( cl.kind == NormalMove ||
6342         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6343         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6344         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6345       fromX = cl.ff;
6346       fromY = cl.rf;
6347       *x = cl.ft;
6348       *y = cl.rt;
6349       return TRUE;
6350     }
6351     if(cl.kind != ImpossibleMove) return FALSE;
6352     cl.pieceIn = EmptySquare;
6353     cl.rfIn = -1;
6354     cl.ffIn = -1;
6355     cl.rtIn = *y;
6356     cl.ftIn = *x;
6357     cl.promoCharIn = NULLCHAR;
6358     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6359     if( cl.kind == NormalMove ||
6360         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6361         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6362         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6363       fromX = cl.ff;
6364       fromY = cl.rf;
6365       *x = cl.ft;
6366       *y = cl.rt;
6367       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6368       return TRUE;
6369     }
6370     return FALSE;
6371 }
6372
6373 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6374 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6375 int lastLoadGameUseList = FALSE;
6376 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6377 ChessMove lastLoadGameStart = EndOfFile;
6378
6379 void
6380 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6381      int fromX, fromY, toX, toY;
6382      int promoChar;
6383 {
6384     ChessMove moveType;
6385     ChessSquare pdown, pup;
6386
6387     /* Check if the user is playing in turn.  This is complicated because we
6388        let the user "pick up" a piece before it is his turn.  So the piece he
6389        tried to pick up may have been captured by the time he puts it down!
6390        Therefore we use the color the user is supposed to be playing in this
6391        test, not the color of the piece that is currently on the starting
6392        square---except in EditGame mode, where the user is playing both
6393        sides; fortunately there the capture race can't happen.  (It can
6394        now happen in IcsExamining mode, but that's just too bad.  The user
6395        will get a somewhat confusing message in that case.)
6396        */
6397
6398     switch (gameMode) {
6399       case AnalyzeFile:
6400       case TwoMachinesPlay:
6401       case EndOfGame:
6402       case IcsObserving:
6403       case IcsIdle:
6404         /* We switched into a game mode where moves are not accepted,
6405            perhaps while the mouse button was down. */
6406         return;
6407
6408       case MachinePlaysWhite:
6409         /* User is moving for Black */
6410         if (WhiteOnMove(currentMove)) {
6411             DisplayMoveError(_("It is White's turn"));
6412             return;
6413         }
6414         break;
6415
6416       case MachinePlaysBlack:
6417         /* User is moving for White */
6418         if (!WhiteOnMove(currentMove)) {
6419             DisplayMoveError(_("It is Black's turn"));
6420             return;
6421         }
6422         break;
6423
6424       case PlayFromGameFile:
6425             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6426       case EditGame:
6427       case IcsExamining:
6428       case BeginningOfGame:
6429       case AnalyzeMode:
6430       case Training:
6431         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6432         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6433             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6434             /* User is moving for Black */
6435             if (WhiteOnMove(currentMove)) {
6436                 DisplayMoveError(_("It is White's turn"));
6437                 return;
6438             }
6439         } else {
6440             /* User is moving for White */
6441             if (!WhiteOnMove(currentMove)) {
6442                 DisplayMoveError(_("It is Black's turn"));
6443                 return;
6444             }
6445         }
6446         break;
6447
6448       case IcsPlayingBlack:
6449         /* User is moving for Black */
6450         if (WhiteOnMove(currentMove)) {
6451             if (!appData.premove) {
6452                 DisplayMoveError(_("It is White's turn"));
6453             } else if (toX >= 0 && toY >= 0) {
6454                 premoveToX = toX;
6455                 premoveToY = toY;
6456                 premoveFromX = fromX;
6457                 premoveFromY = fromY;
6458                 premovePromoChar = promoChar;
6459                 gotPremove = 1;
6460                 if (appData.debugMode)
6461                     fprintf(debugFP, "Got premove: fromX %d,"
6462                             "fromY %d, toX %d, toY %d\n",
6463                             fromX, fromY, toX, toY);
6464             }
6465             return;
6466         }
6467         break;
6468
6469       case IcsPlayingWhite:
6470         /* User is moving for White */
6471         if (!WhiteOnMove(currentMove)) {
6472             if (!appData.premove) {
6473                 DisplayMoveError(_("It is Black's turn"));
6474             } else if (toX >= 0 && toY >= 0) {
6475                 premoveToX = toX;
6476                 premoveToY = toY;
6477                 premoveFromX = fromX;
6478                 premoveFromY = fromY;
6479                 premovePromoChar = promoChar;
6480                 gotPremove = 1;
6481                 if (appData.debugMode)
6482                     fprintf(debugFP, "Got premove: fromX %d,"
6483                             "fromY %d, toX %d, toY %d\n",
6484                             fromX, fromY, toX, toY);
6485             }
6486             return;
6487         }
6488         break;
6489
6490       default:
6491         break;
6492
6493       case EditPosition:
6494         /* EditPosition, empty square, or different color piece;
6495            click-click move is possible */
6496         if (toX == -2 || toY == -2) {
6497             boards[0][fromY][fromX] = EmptySquare;
6498             DrawPosition(FALSE, boards[currentMove]);
6499             return;
6500         } else if (toX >= 0 && toY >= 0) {
6501             boards[0][toY][toX] = boards[0][fromY][fromX];
6502             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6503                 if(boards[0][fromY][0] != EmptySquare) {
6504                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6505                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6506                 }
6507             } else
6508             if(fromX == BOARD_RGHT+1) {
6509                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6510                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6511                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6512                 }
6513             } else
6514             boards[0][fromY][fromX] = EmptySquare;
6515             DrawPosition(FALSE, boards[currentMove]);
6516             return;
6517         }
6518         return;
6519     }
6520
6521     if(toX < 0 || toY < 0) return;
6522     pdown = boards[currentMove][fromY][fromX];
6523     pup = boards[currentMove][toY][toX];
6524
6525     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6526     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6527          if( pup != EmptySquare ) return;
6528          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6529            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6530                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6531            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6532            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6533            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6534            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6535          fromY = DROP_RANK;
6536     }
6537
6538     /* [HGM] always test for legality, to get promotion info */
6539     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6540                                          fromY, fromX, toY, toX, promoChar);
6541
6542     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6543
6544     /* [HGM] but possibly ignore an IllegalMove result */
6545     if (appData.testLegality) {
6546         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6547             DisplayMoveError(_("Illegal move"));
6548             return;
6549         }
6550     }
6551
6552     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6553 }
6554
6555 /* Common tail of UserMoveEvent and DropMenuEvent */
6556 int
6557 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6558      ChessMove moveType;
6559      int fromX, fromY, toX, toY;
6560      /*char*/int promoChar;
6561 {
6562     char *bookHit = 0;
6563
6564     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6565         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6566         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6567         if(WhiteOnMove(currentMove)) {
6568             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6569         } else {
6570             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6571         }
6572     }
6573
6574     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6575        move type in caller when we know the move is a legal promotion */
6576     if(moveType == NormalMove && promoChar)
6577         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6578
6579     /* [HGM] <popupFix> The following if has been moved here from
6580        UserMoveEvent(). Because it seemed to belong here (why not allow
6581        piece drops in training games?), and because it can only be
6582        performed after it is known to what we promote. */
6583     if (gameMode == Training) {
6584       /* compare the move played on the board to the next move in the
6585        * game. If they match, display the move and the opponent's response.
6586        * If they don't match, display an error message.
6587        */
6588       int saveAnimate;
6589       Board testBoard;
6590       CopyBoard(testBoard, boards[currentMove]);
6591       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6592
6593       if (CompareBoards(testBoard, boards[currentMove+1])) {
6594         ForwardInner(currentMove+1);
6595
6596         /* Autoplay the opponent's response.
6597          * if appData.animate was TRUE when Training mode was entered,
6598          * the response will be animated.
6599          */
6600         saveAnimate = appData.animate;
6601         appData.animate = animateTraining;
6602         ForwardInner(currentMove+1);
6603         appData.animate = saveAnimate;
6604
6605         /* check for the end of the game */
6606         if (currentMove >= forwardMostMove) {
6607           gameMode = PlayFromGameFile;
6608           ModeHighlight();
6609           SetTrainingModeOff();
6610           DisplayInformation(_("End of game"));
6611         }
6612       } else {
6613         DisplayError(_("Incorrect move"), 0);
6614       }
6615       return 1;
6616     }
6617
6618   /* Ok, now we know that the move is good, so we can kill
6619      the previous line in Analysis Mode */
6620   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6621                                 && currentMove < forwardMostMove) {
6622     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6623     else forwardMostMove = currentMove;
6624   }
6625
6626   /* If we need the chess program but it's dead, restart it */
6627   ResurrectChessProgram();
6628
6629   /* A user move restarts a paused game*/
6630   if (pausing)
6631     PauseEvent();
6632
6633   thinkOutput[0] = NULLCHAR;
6634
6635   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6636
6637   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6638     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6639     return 1;
6640   }
6641
6642   if (gameMode == BeginningOfGame) {
6643     if (appData.noChessProgram) {
6644       gameMode = EditGame;
6645       SetGameInfo();
6646     } else {
6647       char buf[MSG_SIZ];
6648       gameMode = MachinePlaysBlack;
6649       StartClocks();
6650       SetGameInfo();
6651       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6652       DisplayTitle(buf);
6653       if (first.sendName) {
6654         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6655         SendToProgram(buf, &first);
6656       }
6657       StartClocks();
6658     }
6659     ModeHighlight();
6660   }
6661
6662   /* Relay move to ICS or chess engine */
6663   if (appData.icsActive) {
6664     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6665         gameMode == IcsExamining) {
6666       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6667         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6668         SendToICS("draw ");
6669         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6670       }
6671       // also send plain move, in case ICS does not understand atomic claims
6672       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6673       ics_user_moved = 1;
6674     }
6675   } else {
6676     if (first.sendTime && (gameMode == BeginningOfGame ||
6677                            gameMode == MachinePlaysWhite ||
6678                            gameMode == MachinePlaysBlack)) {
6679       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6680     }
6681     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6682          // [HGM] book: if program might be playing, let it use book
6683         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6684         first.maybeThinking = TRUE;
6685     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6686         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6687         SendBoard(&first, currentMove+1);
6688     } else SendMoveToProgram(forwardMostMove-1, &first);
6689     if (currentMove == cmailOldMove + 1) {
6690       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6691     }
6692   }
6693
6694   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6695
6696   switch (gameMode) {
6697   case EditGame:
6698     if(appData.testLegality)
6699     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6700     case MT_NONE:
6701     case MT_CHECK:
6702       break;
6703     case MT_CHECKMATE:
6704     case MT_STAINMATE:
6705       if (WhiteOnMove(currentMove)) {
6706         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6707       } else {
6708         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6709       }
6710       break;
6711     case MT_STALEMATE:
6712       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6713       break;
6714     }
6715     break;
6716
6717   case MachinePlaysBlack:
6718   case MachinePlaysWhite:
6719     /* disable certain menu options while machine is thinking */
6720     SetMachineThinkingEnables();
6721     break;
6722
6723   default:
6724     break;
6725   }
6726
6727   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6728   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6729
6730   if(bookHit) { // [HGM] book: simulate book reply
6731         static char bookMove[MSG_SIZ]; // a bit generous?
6732
6733         programStats.nodes = programStats.depth = programStats.time =
6734         programStats.score = programStats.got_only_move = 0;
6735         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6736
6737         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6738         strcat(bookMove, bookHit);
6739         HandleMachineMove(bookMove, &first);
6740   }
6741   return 1;
6742 }
6743
6744 void
6745 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6746      Board board;
6747      int flags;
6748      ChessMove kind;
6749      int rf, ff, rt, ft;
6750      VOIDSTAR closure;
6751 {
6752     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6753     Markers *m = (Markers *) closure;
6754     if(rf == fromY && ff == fromX)
6755         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6756                          || kind == WhiteCapturesEnPassant
6757                          || kind == BlackCapturesEnPassant);
6758     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6759 }
6760
6761 void
6762 MarkTargetSquares(int clear)
6763 {
6764   int x, y;
6765   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6766      !appData.testLegality || gameMode == EditPosition) return;
6767   if(clear) {
6768     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6769   } else {
6770     int capt = 0;
6771     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6772     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6773       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6774       if(capt)
6775       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6776     }
6777   }
6778   DrawPosition(TRUE, NULL);
6779 }
6780
6781 int
6782 Explode(Board board, int fromX, int fromY, int toX, int toY)
6783 {
6784     if(gameInfo.variant == VariantAtomic &&
6785        (board[toY][toX] != EmptySquare ||                     // capture?
6786         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6787                          board[fromY][fromX] == BlackPawn   )
6788       )) {
6789         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6790         return TRUE;
6791     }
6792     return FALSE;
6793 }
6794
6795 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6796
6797 int CanPromote(ChessSquare piece, int y)
6798 {
6799         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6800         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6801         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6802            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6803            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6804                                                   gameInfo.variant == VariantMakruk) return FALSE;
6805         return (piece == BlackPawn && y == 1 ||
6806                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6807                 piece == BlackLance && y == 1 ||
6808                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6809 }
6810
6811 void LeftClick(ClickType clickType, int xPix, int yPix)
6812 {
6813     int x, y;
6814     Boolean saveAnimate;
6815     static int second = 0, promotionChoice = 0, clearFlag = 0;
6816     char promoChoice = NULLCHAR;
6817     ChessSquare piece;
6818
6819     if(appData.seekGraph && appData.icsActive && loggedOn &&
6820         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6821         SeekGraphClick(clickType, xPix, yPix, 0);
6822         return;
6823     }
6824
6825     if (clickType == Press) ErrorPopDown();
6826
6827     x = EventToSquare(xPix, BOARD_WIDTH);
6828     y = EventToSquare(yPix, BOARD_HEIGHT);
6829     if (!flipView && y >= 0) {
6830         y = BOARD_HEIGHT - 1 - y;
6831     }
6832     if (flipView && x >= 0) {
6833         x = BOARD_WIDTH - 1 - x;
6834     }
6835
6836     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6837         defaultPromoChoice = promoSweep;
6838         promoSweep = EmptySquare;   // terminate sweep
6839         promoDefaultAltered = TRUE;
6840         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6841     }
6842
6843     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6844         if(clickType == Release) return; // ignore upclick of click-click destination
6845         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6846         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6847         if(gameInfo.holdingsWidth &&
6848                 (WhiteOnMove(currentMove)
6849                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6850                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6851             // click in right holdings, for determining promotion piece
6852             ChessSquare p = boards[currentMove][y][x];
6853             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6854             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6855             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6856                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6857                 fromX = fromY = -1;
6858                 return;
6859             }
6860         }
6861         DrawPosition(FALSE, boards[currentMove]);
6862         return;
6863     }
6864
6865     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6866     if(clickType == Press
6867             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6868               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6869               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6870         return;
6871
6872     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6873         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6874
6875     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6876         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6877                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6878         defaultPromoChoice = DefaultPromoChoice(side);
6879     }
6880
6881     autoQueen = appData.alwaysPromoteToQueen;
6882
6883     if (fromX == -1) {
6884       int originalY = y;
6885       gatingPiece = EmptySquare;
6886       if (clickType != Press) {
6887         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6888             DragPieceEnd(xPix, yPix); dragging = 0;
6889             DrawPosition(FALSE, NULL);
6890         }
6891         return;
6892       }
6893       fromX = x; fromY = y; toX = toY = -1;
6894       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6895          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6896          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6897             /* First square */
6898             if (OKToStartUserMove(fromX, fromY)) {
6899                 second = 0;
6900                 MarkTargetSquares(0);
6901                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6902                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6903                     promoSweep = defaultPromoChoice;
6904                     selectFlag = 0; lastX = xPix; lastY = yPix;
6905                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6906                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6907                 }
6908                 if (appData.highlightDragging) {
6909                     SetHighlights(fromX, fromY, -1, -1);
6910                 }
6911             } else fromX = fromY = -1;
6912             return;
6913         }
6914     }
6915
6916     /* fromX != -1 */
6917     if (clickType == Press && gameMode != EditPosition) {
6918         ChessSquare fromP;
6919         ChessSquare toP;
6920         int frc;
6921
6922         // ignore off-board to clicks
6923         if(y < 0 || x < 0) return;
6924
6925         /* Check if clicking again on the same color piece */
6926         fromP = boards[currentMove][fromY][fromX];
6927         toP = boards[currentMove][y][x];
6928         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6929         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6930              WhitePawn <= toP && toP <= WhiteKing &&
6931              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6932              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6933             (BlackPawn <= fromP && fromP <= BlackKing &&
6934              BlackPawn <= toP && toP <= BlackKing &&
6935              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6936              !(fromP == BlackKing && toP == BlackRook && frc))) {
6937             /* Clicked again on same color piece -- changed his mind */
6938             second = (x == fromX && y == fromY);
6939             promoDefaultAltered = FALSE;
6940             MarkTargetSquares(1);
6941            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6942             if (appData.highlightDragging) {
6943                 SetHighlights(x, y, -1, -1);
6944             } else {
6945                 ClearHighlights();
6946             }
6947             if (OKToStartUserMove(x, y)) {
6948                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6949                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6950                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6951                  gatingPiece = boards[currentMove][fromY][fromX];
6952                 else gatingPiece = EmptySquare;
6953                 fromX = x;
6954                 fromY = y; dragging = 1;
6955                 MarkTargetSquares(0);
6956                 DragPieceBegin(xPix, yPix, FALSE);
6957                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6958                     promoSweep = defaultPromoChoice;
6959                     selectFlag = 0; lastX = xPix; lastY = yPix;
6960                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6961                 }
6962             }
6963            }
6964            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6965            second = FALSE; 
6966         }
6967         // ignore clicks on holdings
6968         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6969     }
6970
6971     if (clickType == Release && x == fromX && y == fromY) {
6972         DragPieceEnd(xPix, yPix); dragging = 0;
6973         if(clearFlag) {
6974             // a deferred attempt to click-click move an empty square on top of a piece
6975             boards[currentMove][y][x] = EmptySquare;
6976             ClearHighlights();
6977             DrawPosition(FALSE, boards[currentMove]);
6978             fromX = fromY = -1; clearFlag = 0;
6979             return;
6980         }
6981         if (appData.animateDragging) {
6982             /* Undo animation damage if any */
6983             DrawPosition(FALSE, NULL);
6984         }
6985         if (second) {
6986             /* Second up/down in same square; just abort move */
6987             second = 0;
6988             fromX = fromY = -1;
6989             gatingPiece = EmptySquare;
6990             ClearHighlights();
6991             gotPremove = 0;
6992             ClearPremoveHighlights();
6993         } else {
6994             /* First upclick in same square; start click-click mode */
6995             SetHighlights(x, y, -1, -1);
6996         }
6997         return;
6998     }
6999
7000     clearFlag = 0;
7001
7002     /* we now have a different from- and (possibly off-board) to-square */
7003     /* Completed move */
7004     toX = x;
7005     toY = y;
7006     saveAnimate = appData.animate;
7007     MarkTargetSquares(1);
7008     if (clickType == Press) {
7009         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7010             // must be Edit Position mode with empty-square selected
7011             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7012             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7013             return;
7014         }
7015         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7016             ChessSquare piece = boards[currentMove][fromY][fromX];
7017             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7018             promoSweep = defaultPromoChoice;
7019             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7020             selectFlag = 0; lastX = xPix; lastY = yPix;
7021             Sweep(0); // Pawn that is going to promote: preview promotion piece
7022             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7023             DrawPosition(FALSE, boards[currentMove]);
7024             return;
7025         }
7026         /* Finish clickclick move */
7027         if (appData.animate || appData.highlightLastMove) {
7028             SetHighlights(fromX, fromY, toX, toY);
7029         } else {
7030             ClearHighlights();
7031         }
7032     } else {
7033         /* Finish drag move */
7034         if (appData.highlightLastMove) {
7035             SetHighlights(fromX, fromY, toX, toY);
7036         } else {
7037             ClearHighlights();
7038         }
7039         DragPieceEnd(xPix, yPix); dragging = 0;
7040         /* Don't animate move and drag both */
7041         appData.animate = FALSE;
7042     }
7043
7044     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7045     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7046         ChessSquare piece = boards[currentMove][fromY][fromX];
7047         if(gameMode == EditPosition && piece != EmptySquare &&
7048            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7049             int n;
7050
7051             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7052                 n = PieceToNumber(piece - (int)BlackPawn);
7053                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7054                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7055                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7056             } else
7057             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7058                 n = PieceToNumber(piece);
7059                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7060                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7061                 boards[currentMove][n][BOARD_WIDTH-2]++;
7062             }
7063             boards[currentMove][fromY][fromX] = EmptySquare;
7064         }
7065         ClearHighlights();
7066         fromX = fromY = -1;
7067         DrawPosition(TRUE, boards[currentMove]);
7068         return;
7069     }
7070
7071     // off-board moves should not be highlighted
7072     if(x < 0 || y < 0) ClearHighlights();
7073
7074     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7075
7076     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7077         SetHighlights(fromX, fromY, toX, toY);
7078         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7079             // [HGM] super: promotion to captured piece selected from holdings
7080             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7081             promotionChoice = TRUE;
7082             // kludge follows to temporarily execute move on display, without promoting yet
7083             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7084             boards[currentMove][toY][toX] = p;
7085             DrawPosition(FALSE, boards[currentMove]);
7086             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7087             boards[currentMove][toY][toX] = q;
7088             DisplayMessage("Click in holdings to choose piece", "");
7089             return;
7090         }
7091         PromotionPopUp();
7092     } else {
7093         int oldMove = currentMove;
7094         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7095         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7096         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7097         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7098            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7099             DrawPosition(TRUE, boards[currentMove]);
7100         fromX = fromY = -1;
7101     }
7102     appData.animate = saveAnimate;
7103     if (appData.animate || appData.animateDragging) {
7104         /* Undo animation damage if needed */
7105         DrawPosition(FALSE, NULL);
7106     }
7107 }
7108
7109 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7110 {   // front-end-free part taken out of PieceMenuPopup
7111     int whichMenu; int xSqr, ySqr;
7112
7113     if(seekGraphUp) { // [HGM] seekgraph
7114         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7115         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7116         return -2;
7117     }
7118
7119     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7120          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7121         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7122         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7123         if(action == Press)   {
7124             originalFlip = flipView;
7125             flipView = !flipView; // temporarily flip board to see game from partners perspective
7126             DrawPosition(TRUE, partnerBoard);
7127             DisplayMessage(partnerStatus, "");
7128             partnerUp = TRUE;
7129         } else if(action == Release) {
7130             flipView = originalFlip;
7131             DrawPosition(TRUE, boards[currentMove]);
7132             partnerUp = FALSE;
7133         }
7134         return -2;
7135     }
7136
7137     xSqr = EventToSquare(x, BOARD_WIDTH);
7138     ySqr = EventToSquare(y, BOARD_HEIGHT);
7139     if (action == Release) {
7140         if(pieceSweep != EmptySquare) {
7141             EditPositionMenuEvent(pieceSweep, toX, toY);
7142             pieceSweep = EmptySquare;
7143         } else UnLoadPV(); // [HGM] pv
7144     }
7145     if (action != Press) return -2; // return code to be ignored
7146     switch (gameMode) {
7147       case IcsExamining:
7148         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7149       case EditPosition:
7150         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7151         if (xSqr < 0 || ySqr < 0) return -1;
7152         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7153         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7154         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7155         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7156         NextPiece(0);
7157         return 2; // grab
7158       case IcsObserving:
7159         if(!appData.icsEngineAnalyze) return -1;
7160       case IcsPlayingWhite:
7161       case IcsPlayingBlack:
7162         if(!appData.zippyPlay) goto noZip;
7163       case AnalyzeMode:
7164       case AnalyzeFile:
7165       case MachinePlaysWhite:
7166       case MachinePlaysBlack:
7167       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7168         if (!appData.dropMenu) {
7169           LoadPV(x, y);
7170           return 2; // flag front-end to grab mouse events
7171         }
7172         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7173            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7174       case EditGame:
7175       noZip:
7176         if (xSqr < 0 || ySqr < 0) return -1;
7177         if (!appData.dropMenu || appData.testLegality &&
7178             gameInfo.variant != VariantBughouse &&
7179             gameInfo.variant != VariantCrazyhouse) return -1;
7180         whichMenu = 1; // drop menu
7181         break;
7182       default:
7183         return -1;
7184     }
7185
7186     if (((*fromX = xSqr) < 0) ||
7187         ((*fromY = ySqr) < 0)) {
7188         *fromX = *fromY = -1;
7189         return -1;
7190     }
7191     if (flipView)
7192       *fromX = BOARD_WIDTH - 1 - *fromX;
7193     else
7194       *fromY = BOARD_HEIGHT - 1 - *fromY;
7195
7196     return whichMenu;
7197 }
7198
7199 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7200 {
7201 //    char * hint = lastHint;
7202     FrontEndProgramStats stats;
7203
7204     stats.which = cps == &first ? 0 : 1;
7205     stats.depth = cpstats->depth;
7206     stats.nodes = cpstats->nodes;
7207     stats.score = cpstats->score;
7208     stats.time = cpstats->time;
7209     stats.pv = cpstats->movelist;
7210     stats.hint = lastHint;
7211     stats.an_move_index = 0;
7212     stats.an_move_count = 0;
7213
7214     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7215         stats.hint = cpstats->move_name;
7216         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7217         stats.an_move_count = cpstats->nr_moves;
7218     }
7219
7220     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
7221
7222     SetProgramStats( &stats );
7223 }
7224
7225 void
7226 ClearEngineOutputPane(int which)
7227 {
7228     static FrontEndProgramStats dummyStats;
7229     dummyStats.which = which;
7230     dummyStats.pv = "#";
7231     SetProgramStats( &dummyStats );
7232 }
7233
7234 #define MAXPLAYERS 500
7235
7236 char *
7237 TourneyStandings(int display)
7238 {
7239     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7240     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7241     char result, *p, *names[MAXPLAYERS];
7242
7243     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7244         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7245     names[0] = p = strdup(appData.participants);
7246     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7247
7248     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7249
7250     while(result = appData.results[nr]) {
7251         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7252         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7253         wScore = bScore = 0;
7254         switch(result) {
7255           case '+': wScore = 2; break;
7256           case '-': bScore = 2; break;
7257           case '=': wScore = bScore = 1; break;
7258           case ' ':
7259           case '*': return strdup("busy"); // tourney not finished
7260         }
7261         score[w] += wScore;
7262         score[b] += bScore;
7263         games[w]++;
7264         games[b]++;
7265         nr++;
7266     }
7267     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7268     for(w=0; w<nPlayers; w++) {
7269         bScore = -1;
7270         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7271         ranking[w] = b; points[w] = bScore; score[b] = -2;
7272     }
7273     p = malloc(nPlayers*34+1);
7274     for(w=0; w<nPlayers && w<display; w++)
7275         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7276     free(names[0]);
7277     return p;
7278 }
7279
7280 void
7281 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7282 {       // count all piece types
7283         int p, f, r;
7284         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7285         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7286         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7287                 p = board[r][f];
7288                 pCnt[p]++;
7289                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7290                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7291                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7292                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7293                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7294                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7295         }
7296 }
7297
7298 int
7299 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7300 {
7301         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7302         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7303
7304         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7305         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7306         if(myPawns == 2 && nMine == 3) // KPP
7307             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7308         if(myPawns == 1 && nMine == 2) // KP
7309             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7310         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7311             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7312         if(myPawns) return FALSE;
7313         if(pCnt[WhiteRook+side])
7314             return pCnt[BlackRook-side] ||
7315                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7316                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7317                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7318         if(pCnt[WhiteCannon+side]) {
7319             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7320             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7321         }
7322         if(pCnt[WhiteKnight+side])
7323             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7324         return FALSE;
7325 }
7326
7327 int
7328 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7329 {
7330         VariantClass v = gameInfo.variant;
7331
7332         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7333         if(v == VariantShatranj) return TRUE; // always winnable through baring
7334         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7335         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7336
7337         if(v == VariantXiangqi) {
7338                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7339
7340                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7341                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7342                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7343                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7344                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7345                 if(stale) // we have at least one last-rank P plus perhaps C
7346                     return majors // KPKX
7347                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7348                 else // KCA*E*
7349                     return pCnt[WhiteFerz+side] // KCAK
7350                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7351                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7352                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7353
7354         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7355                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7356
7357                 if(nMine == 1) return FALSE; // bare King
7358                 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
7359                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7360                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7361                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7362                 if(pCnt[WhiteKnight+side])
7363                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7364                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7365                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7366                 if(nBishops)
7367                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7368                 if(pCnt[WhiteAlfil+side])
7369                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7370                 if(pCnt[WhiteWazir+side])
7371                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7372         }
7373
7374         return TRUE;
7375 }
7376
7377 int
7378 CompareWithRights(Board b1, Board b2)
7379 {
7380     int rights = 0;
7381     if(!CompareBoards(b1, b2)) return FALSE;
7382     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7383     /* compare castling rights */
7384     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7385            rights++; /* King lost rights, while rook still had them */
7386     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7387         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7388            rights++; /* but at least one rook lost them */
7389     }
7390     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7391            rights++;
7392     if( b1[CASTLING][5] != NoRights ) {
7393         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7394            rights++;
7395     }
7396     return rights == 0;
7397 }
7398
7399 int
7400 Adjudicate(ChessProgramState *cps)
7401 {       // [HGM] some adjudications useful with buggy engines
7402         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7403         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7404         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7405         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7406         int k, count = 0; static int bare = 1;
7407         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7408         Boolean canAdjudicate = !appData.icsActive;
7409
7410         // most tests only when we understand the game, i.e. legality-checking on
7411             if( appData.testLegality )
7412             {   /* [HGM] Some more adjudications for obstinate engines */
7413                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7414                 static int moveCount = 6;
7415                 ChessMove result;
7416                 char *reason = NULL;
7417
7418                 /* Count what is on board. */
7419                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7420
7421                 /* Some material-based adjudications that have to be made before stalemate test */
7422                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7423                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7424                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7425                      if(canAdjudicate && appData.checkMates) {
7426                          if(engineOpponent)
7427                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7428                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7429                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7430                          return 1;
7431                      }
7432                 }
7433
7434                 /* Bare King in Shatranj (loses) or Losers (wins) */
7435                 if( nrW == 1 || nrB == 1) {
7436                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7437                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7438                      if(canAdjudicate && appData.checkMates) {
7439                          if(engineOpponent)
7440                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7441                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7442                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7443                          return 1;
7444                      }
7445                   } else
7446                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7447                   {    /* bare King */
7448                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7449                         if(canAdjudicate && appData.checkMates) {
7450                             /* but only adjudicate if adjudication enabled */
7451                             if(engineOpponent)
7452                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7453                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7454                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7455                             return 1;
7456                         }
7457                   }
7458                 } else bare = 1;
7459
7460
7461             // don't wait for engine to announce game end if we can judge ourselves
7462             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7463               case MT_CHECK:
7464                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7465                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7466                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7467                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7468                             checkCnt++;
7469                         if(checkCnt >= 2) {
7470                             reason = "Xboard adjudication: 3rd check";
7471                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7472                             break;
7473                         }
7474                     }
7475                 }
7476               case MT_NONE:
7477               default:
7478                 break;
7479               case MT_STALEMATE:
7480               case MT_STAINMATE:
7481                 reason = "Xboard adjudication: Stalemate";
7482                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7483                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7484                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7485                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7486                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7487                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7488                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7489                                                                         EP_CHECKMATE : EP_WINS);
7490                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7491                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7492                 }
7493                 break;
7494               case MT_CHECKMATE:
7495                 reason = "Xboard adjudication: Checkmate";
7496                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7497                 break;
7498             }
7499
7500                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7501                     case EP_STALEMATE:
7502                         result = GameIsDrawn; break;
7503                     case EP_CHECKMATE:
7504                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7505                     case EP_WINS:
7506                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7507                     default:
7508                         result = EndOfFile;
7509                 }
7510                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7511                     if(engineOpponent)
7512                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7513                     GameEnds( result, reason, GE_XBOARD );
7514                     return 1;
7515                 }
7516
7517                 /* Next absolutely insufficient mating material. */
7518                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7519                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7520                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7521
7522                      /* always flag draws, for judging claims */
7523                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7524
7525                      if(canAdjudicate && appData.materialDraws) {
7526                          /* but only adjudicate them if adjudication enabled */
7527                          if(engineOpponent) {
7528                            SendToProgram("force\n", engineOpponent); // suppress reply
7529                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7530                          }
7531                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7532                          return 1;
7533                      }
7534                 }
7535
7536                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7537                 if(gameInfo.variant == VariantXiangqi ?
7538                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7539                  : nrW + nrB == 4 &&
7540                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7541                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7542                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7543                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7544                    ) ) {
7545                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7546                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7547                           if(engineOpponent) {
7548                             SendToProgram("force\n", engineOpponent); // suppress reply
7549                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7550                           }
7551                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7552                           return 1;
7553                      }
7554                 } else moveCount = 6;
7555             }
7556         if (appData.debugMode) { int i;
7557             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7558                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7559                     appData.drawRepeats);
7560             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7561               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7562
7563         }
7564
7565         // Repetition draws and 50-move rule can be applied independently of legality testing
7566
7567                 /* Check for rep-draws */
7568                 count = 0;
7569                 for(k = forwardMostMove-2;
7570                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7571                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7572                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7573                     k-=2)
7574                 {   int rights=0;
7575                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7576                         /* compare castling rights */
7577                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7578                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7579                                 rights++; /* King lost rights, while rook still had them */
7580                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7581                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7582                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7583                                    rights++; /* but at least one rook lost them */
7584                         }
7585                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7586                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7587                                 rights++;
7588                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7589                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7590                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7591                                    rights++;
7592                         }
7593                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7594                             && appData.drawRepeats > 1) {
7595                              /* adjudicate after user-specified nr of repeats */
7596                              int result = GameIsDrawn;
7597                              char *details = "XBoard adjudication: repetition draw";
7598                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7599                                 // [HGM] xiangqi: check for forbidden perpetuals
7600                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7601                                 for(m=forwardMostMove; m>k; m-=2) {
7602                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7603                                         ourPerpetual = 0; // the current mover did not always check
7604                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7605                                         hisPerpetual = 0; // the opponent did not always check
7606                                 }
7607                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7608                                                                         ourPerpetual, hisPerpetual);
7609                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7610                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7611                                     details = "Xboard adjudication: perpetual checking";
7612                                 } else
7613                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7614                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7615                                 } else
7616                                 // Now check for perpetual chases
7617                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7618                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7619                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7620                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7621                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7622                                         details = "Xboard adjudication: perpetual chasing";
7623                                     } else
7624                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7625                                         break; // Abort repetition-checking loop.
7626                                 }
7627                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7628                              }
7629                              if(engineOpponent) {
7630                                SendToProgram("force\n", engineOpponent); // suppress reply
7631                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7632                              }
7633                              GameEnds( result, details, GE_XBOARD );
7634                              return 1;
7635                         }
7636                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7637                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7638                     }
7639                 }
7640
7641                 /* Now we test for 50-move draws. Determine ply count */
7642                 count = forwardMostMove;
7643                 /* look for last irreversble move */
7644                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7645                     count--;
7646                 /* if we hit starting position, add initial plies */
7647                 if( count == backwardMostMove )
7648                     count -= initialRulePlies;
7649                 count = forwardMostMove - count;
7650                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7651                         // adjust reversible move counter for checks in Xiangqi
7652                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7653                         if(i < backwardMostMove) i = backwardMostMove;
7654                         while(i <= forwardMostMove) {
7655                                 lastCheck = inCheck; // check evasion does not count
7656                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7657                                 if(inCheck || lastCheck) count--; // check does not count
7658                                 i++;
7659                         }
7660                 }
7661                 if( count >= 100)
7662                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7663                          /* this is used to judge if draw claims are legal */
7664                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7665                          if(engineOpponent) {
7666                            SendToProgram("force\n", engineOpponent); // suppress reply
7667                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7668                          }
7669                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7670                          return 1;
7671                 }
7672
7673                 /* if draw offer is pending, treat it as a draw claim
7674                  * when draw condition present, to allow engines a way to
7675                  * claim draws before making their move to avoid a race
7676                  * condition occurring after their move
7677                  */
7678                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7679                          char *p = NULL;
7680                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7681                              p = "Draw claim: 50-move rule";
7682                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7683                              p = "Draw claim: 3-fold repetition";
7684                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7685                              p = "Draw claim: insufficient mating material";
7686                          if( p != NULL && canAdjudicate) {
7687                              if(engineOpponent) {
7688                                SendToProgram("force\n", engineOpponent); // suppress reply
7689                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7690                              }
7691                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7692                              return 1;
7693                          }
7694                 }
7695
7696                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7697                     if(engineOpponent) {
7698                       SendToProgram("force\n", engineOpponent); // suppress reply
7699                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7700                     }
7701                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7702                     return 1;
7703                 }
7704         return 0;
7705 }
7706
7707 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7708 {   // [HGM] book: this routine intercepts moves to simulate book replies
7709     char *bookHit = NULL;
7710
7711     //first determine if the incoming move brings opponent into his book
7712     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7713         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7714     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7715     if(bookHit != NULL && !cps->bookSuspend) {
7716         // make sure opponent is not going to reply after receiving move to book position
7717         SendToProgram("force\n", cps);
7718         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7719     }
7720     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7721     // now arrange restart after book miss
7722     if(bookHit) {
7723         // after a book hit we never send 'go', and the code after the call to this routine
7724         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7725         char buf[MSG_SIZ], *move = bookHit;
7726         if(cps->useSAN) {
7727             int fromX, fromY, toX, toY;
7728             char promoChar;
7729             ChessMove moveType;
7730             move = buf + 30;
7731             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7732                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7733                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7734                                     PosFlags(forwardMostMove),
7735                                     fromY, fromX, toY, toX, promoChar, move);
7736             } else {
7737                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7738                 bookHit = NULL;
7739             }
7740         }
7741         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7742         SendToProgram(buf, cps);
7743         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7744     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7745         SendToProgram("go\n", cps);
7746         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7747     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7748         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7749             SendToProgram("go\n", cps);
7750         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7751     }
7752     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7753 }
7754
7755 char *savedMessage;
7756 ChessProgramState *savedState;
7757 void DeferredBookMove(void)
7758 {
7759         if(savedState->lastPing != savedState->lastPong)
7760                     ScheduleDelayedEvent(DeferredBookMove, 10);
7761         else
7762         HandleMachineMove(savedMessage, savedState);
7763 }
7764
7765 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7766
7767 void
7768 HandleMachineMove(message, cps)
7769      char *message;
7770      ChessProgramState *cps;
7771 {
7772     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7773     char realname[MSG_SIZ];
7774     int fromX, fromY, toX, toY;
7775     ChessMove moveType;
7776     char promoChar;
7777     char *p, *pv=buf1;
7778     int machineWhite;
7779     char *bookHit;
7780
7781     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7782         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7783         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7784             DisplayError(_("Invalid pairing from pairing engine"), 0);
7785             return;
7786         }
7787         pairingReceived = 1;
7788         NextMatchGame();
7789         return; // Skim the pairing messages here.
7790     }
7791
7792     cps->userError = 0;
7793
7794 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7795     /*
7796      * Kludge to ignore BEL characters
7797      */
7798     while (*message == '\007') message++;
7799
7800     /*
7801      * [HGM] engine debug message: ignore lines starting with '#' character
7802      */
7803     if(cps->debug && *message == '#') return;
7804
7805     /*
7806      * Look for book output
7807      */
7808     if (cps == &first && bookRequested) {
7809         if (message[0] == '\t' || message[0] == ' ') {
7810             /* Part of the book output is here; append it */
7811             strcat(bookOutput, message);
7812             strcat(bookOutput, "  \n");
7813             return;
7814         } else if (bookOutput[0] != NULLCHAR) {
7815             /* All of book output has arrived; display it */
7816             char *p = bookOutput;
7817             while (*p != NULLCHAR) {
7818                 if (*p == '\t') *p = ' ';
7819                 p++;
7820             }
7821             DisplayInformation(bookOutput);
7822             bookRequested = FALSE;
7823             /* Fall through to parse the current output */
7824         }
7825     }
7826
7827     /*
7828      * Look for machine move.
7829      */
7830     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7831         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7832     {
7833         /* This method is only useful on engines that support ping */
7834         if (cps->lastPing != cps->lastPong) {
7835           if (gameMode == BeginningOfGame) {
7836             /* Extra move from before last new; ignore */
7837             if (appData.debugMode) {
7838                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7839             }
7840           } else {
7841             if (appData.debugMode) {
7842                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7843                         cps->which, gameMode);
7844             }
7845
7846             SendToProgram("undo\n", cps);
7847           }
7848           return;
7849         }
7850
7851         switch (gameMode) {
7852           case BeginningOfGame:
7853             /* Extra move from before last reset; ignore */
7854             if (appData.debugMode) {
7855                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7856             }
7857             return;
7858
7859           case EndOfGame:
7860           case IcsIdle:
7861           default:
7862             /* Extra move after we tried to stop.  The mode test is
7863                not a reliable way of detecting this problem, but it's
7864                the best we can do on engines that don't support ping.
7865             */
7866             if (appData.debugMode) {
7867                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7868                         cps->which, gameMode);
7869             }
7870             SendToProgram("undo\n", cps);
7871             return;
7872
7873           case MachinePlaysWhite:
7874           case IcsPlayingWhite:
7875             machineWhite = TRUE;
7876             break;
7877
7878           case MachinePlaysBlack:
7879           case IcsPlayingBlack:
7880             machineWhite = FALSE;
7881             break;
7882
7883           case TwoMachinesPlay:
7884             machineWhite = (cps->twoMachinesColor[0] == 'w');
7885             break;
7886         }
7887         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7888             if (appData.debugMode) {
7889                 fprintf(debugFP,
7890                         "Ignoring move out of turn by %s, gameMode %d"
7891                         ", forwardMost %d\n",
7892                         cps->which, gameMode, forwardMostMove);
7893             }
7894             return;
7895         }
7896
7897     if (appData.debugMode) { int f = forwardMostMove;
7898         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7899                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7900                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7901     }
7902         if(cps->alphaRank) AlphaRank(machineMove, 4);
7903         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7904                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7905             /* Machine move could not be parsed; ignore it. */
7906           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7907                     machineMove, _(cps->which));
7908             DisplayError(buf1, 0);
7909             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7910                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7911             if (gameMode == TwoMachinesPlay) {
7912               GameEnds(machineWhite ? BlackWins : WhiteWins,
7913                        buf1, GE_XBOARD);
7914             }
7915             return;
7916         }
7917
7918         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7919         /* So we have to redo legality test with true e.p. status here,  */
7920         /* to make sure an illegal e.p. capture does not slip through,   */
7921         /* to cause a forfeit on a justified illegal-move complaint      */
7922         /* of the opponent.                                              */
7923         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7924            ChessMove moveType;
7925            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7926                              fromY, fromX, toY, toX, promoChar);
7927             if (appData.debugMode) {
7928                 int i;
7929                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7930                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7931                 fprintf(debugFP, "castling rights\n");
7932             }
7933             if(moveType == IllegalMove) {
7934               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7935                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7936                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7937                            buf1, GE_XBOARD);
7938                 return;
7939            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7940            /* [HGM] Kludge to handle engines that send FRC-style castling
7941               when they shouldn't (like TSCP-Gothic) */
7942            switch(moveType) {
7943              case WhiteASideCastleFR:
7944              case BlackASideCastleFR:
7945                toX+=2;
7946                currentMoveString[2]++;
7947                break;
7948              case WhiteHSideCastleFR:
7949              case BlackHSideCastleFR:
7950                toX--;
7951                currentMoveString[2]--;
7952                break;
7953              default: ; // nothing to do, but suppresses warning of pedantic compilers
7954            }
7955         }
7956         hintRequested = FALSE;
7957         lastHint[0] = NULLCHAR;
7958         bookRequested = FALSE;
7959         /* Program may be pondering now */
7960         cps->maybeThinking = TRUE;
7961         if (cps->sendTime == 2) cps->sendTime = 1;
7962         if (cps->offeredDraw) cps->offeredDraw--;
7963
7964         /* [AS] Save move info*/
7965         pvInfoList[ forwardMostMove ].score = programStats.score;
7966         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7967         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7968
7969         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7970
7971         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7972         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7973             int count = 0;
7974
7975             while( count < adjudicateLossPlies ) {
7976                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7977
7978                 if( count & 1 ) {
7979                     score = -score; /* Flip score for winning side */
7980                 }
7981
7982                 if( score > adjudicateLossThreshold ) {
7983                     break;
7984                 }
7985
7986                 count++;
7987             }
7988
7989             if( count >= adjudicateLossPlies ) {
7990                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7991
7992                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7993                     "Xboard adjudication",
7994                     GE_XBOARD );
7995
7996                 return;
7997             }
7998         }
7999
8000         if(Adjudicate(cps)) {
8001             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8002             return; // [HGM] adjudicate: for all automatic game ends
8003         }
8004
8005 #if ZIPPY
8006         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8007             first.initDone) {
8008           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8009                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8010                 SendToICS("draw ");
8011                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8012           }
8013           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8014           ics_user_moved = 1;
8015           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8016                 char buf[3*MSG_SIZ];
8017
8018                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8019                         programStats.score / 100.,
8020                         programStats.depth,
8021                         programStats.time / 100.,
8022                         (unsigned int)programStats.nodes,
8023                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8024                         programStats.movelist);
8025                 SendToICS(buf);
8026 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8027           }
8028         }
8029 #endif
8030
8031         /* [AS] Clear stats for next move */
8032         ClearProgramStats();
8033         thinkOutput[0] = NULLCHAR;
8034         hiddenThinkOutputState = 0;
8035
8036         bookHit = NULL;
8037         if (gameMode == TwoMachinesPlay) {
8038             /* [HGM] relaying draw offers moved to after reception of move */
8039             /* and interpreting offer as claim if it brings draw condition */
8040             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8041                 SendToProgram("draw\n", cps->other);
8042             }
8043             if (cps->other->sendTime) {
8044                 SendTimeRemaining(cps->other,
8045                                   cps->other->twoMachinesColor[0] == 'w');
8046             }
8047             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8048             if (firstMove && !bookHit) {
8049                 firstMove = FALSE;
8050                 if (cps->other->useColors) {
8051                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8052                 }
8053                 SendToProgram("go\n", cps->other);
8054             }
8055             cps->other->maybeThinking = TRUE;
8056         }
8057
8058         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8059
8060         if (!pausing && appData.ringBellAfterMoves) {
8061             RingBell();
8062         }
8063
8064         /*
8065          * Reenable menu items that were disabled while
8066          * machine was thinking
8067          */
8068         if (gameMode != TwoMachinesPlay)
8069             SetUserThinkingEnables();
8070
8071         // [HGM] book: after book hit opponent has received move and is now in force mode
8072         // force the book reply into it, and then fake that it outputted this move by jumping
8073         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8074         if(bookHit) {
8075                 static char bookMove[MSG_SIZ]; // a bit generous?
8076
8077                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8078                 strcat(bookMove, bookHit);
8079                 message = bookMove;
8080                 cps = cps->other;
8081                 programStats.nodes = programStats.depth = programStats.time =
8082                 programStats.score = programStats.got_only_move = 0;
8083                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8084
8085                 if(cps->lastPing != cps->lastPong) {
8086                     savedMessage = message; // args for deferred call
8087                     savedState = cps;
8088                     ScheduleDelayedEvent(DeferredBookMove, 10);
8089                     return;
8090                 }
8091                 goto FakeBookMove;
8092         }
8093
8094         return;
8095     }
8096
8097     /* Set special modes for chess engines.  Later something general
8098      *  could be added here; for now there is just one kludge feature,
8099      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8100      *  when "xboard" is given as an interactive command.
8101      */
8102     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8103         cps->useSigint = FALSE;
8104         cps->useSigterm = FALSE;
8105     }
8106     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8107       ParseFeatures(message+8, cps);
8108       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8109     }
8110
8111     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8112       int dummy, s=6; char buf[MSG_SIZ];
8113       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8114       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8115       ParseFEN(boards[0], &dummy, message+s);
8116       DrawPosition(TRUE, boards[0]);
8117       startedFromSetupPosition = TRUE;
8118       return;
8119     }
8120     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8121      * want this, I was asked to put it in, and obliged.
8122      */
8123     if (!strncmp(message, "setboard ", 9)) {
8124         Board initial_position;
8125
8126         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8127
8128         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8129             DisplayError(_("Bad FEN received from engine"), 0);
8130             return ;
8131         } else {
8132            Reset(TRUE, FALSE);
8133            CopyBoard(boards[0], initial_position);
8134            initialRulePlies = FENrulePlies;
8135            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8136            else gameMode = MachinePlaysBlack;
8137            DrawPosition(FALSE, boards[currentMove]);
8138         }
8139         return;
8140     }
8141
8142     /*
8143      * Look for communication commands
8144      */
8145     if (!strncmp(message, "telluser ", 9)) {
8146         if(message[9] == '\\' && message[10] == '\\')
8147             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8148         PlayTellSound();
8149         DisplayNote(message + 9);
8150         return;
8151     }
8152     if (!strncmp(message, "tellusererror ", 14)) {
8153         cps->userError = 1;
8154         if(message[14] == '\\' && message[15] == '\\')
8155             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8156         PlayTellSound();
8157         DisplayError(message + 14, 0);
8158         return;
8159     }
8160     if (!strncmp(message, "tellopponent ", 13)) {
8161       if (appData.icsActive) {
8162         if (loggedOn) {
8163           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8164           SendToICS(buf1);
8165         }
8166       } else {
8167         DisplayNote(message + 13);
8168       }
8169       return;
8170     }
8171     if (!strncmp(message, "tellothers ", 11)) {
8172       if (appData.icsActive) {
8173         if (loggedOn) {
8174           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8175           SendToICS(buf1);
8176         }
8177       }
8178       return;
8179     }
8180     if (!strncmp(message, "tellall ", 8)) {
8181       if (appData.icsActive) {
8182         if (loggedOn) {
8183           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8184           SendToICS(buf1);
8185         }
8186       } else {
8187         DisplayNote(message + 8);
8188       }
8189       return;
8190     }
8191     if (strncmp(message, "warning", 7) == 0) {
8192         /* Undocumented feature, use tellusererror in new code */
8193         DisplayError(message, 0);
8194         return;
8195     }
8196     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8197         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8198         strcat(realname, " query");
8199         AskQuestion(realname, buf2, buf1, cps->pr);
8200         return;
8201     }
8202     /* Commands from the engine directly to ICS.  We don't allow these to be
8203      *  sent until we are logged on. Crafty kibitzes have been known to
8204      *  interfere with the login process.
8205      */
8206     if (loggedOn) {
8207         if (!strncmp(message, "tellics ", 8)) {
8208             SendToICS(message + 8);
8209             SendToICS("\n");
8210             return;
8211         }
8212         if (!strncmp(message, "tellicsnoalias ", 15)) {
8213             SendToICS(ics_prefix);
8214             SendToICS(message + 15);
8215             SendToICS("\n");
8216             return;
8217         }
8218         /* The following are for backward compatibility only */
8219         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8220             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8221             SendToICS(ics_prefix);
8222             SendToICS(message);
8223             SendToICS("\n");
8224             return;
8225         }
8226     }
8227     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8228         return;
8229     }
8230     /*
8231      * If the move is illegal, cancel it and redraw the board.
8232      * Also deal with other error cases.  Matching is rather loose
8233      * here to accommodate engines written before the spec.
8234      */
8235     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8236         strncmp(message, "Error", 5) == 0) {
8237         if (StrStr(message, "name") ||
8238             StrStr(message, "rating") || StrStr(message, "?") ||
8239             StrStr(message, "result") || StrStr(message, "board") ||
8240             StrStr(message, "bk") || StrStr(message, "computer") ||
8241             StrStr(message, "variant") || StrStr(message, "hint") ||
8242             StrStr(message, "random") || StrStr(message, "depth") ||
8243             StrStr(message, "accepted")) {
8244             return;
8245         }
8246         if (StrStr(message, "protover")) {
8247           /* Program is responding to input, so it's apparently done
8248              initializing, and this error message indicates it is
8249              protocol version 1.  So we don't need to wait any longer
8250              for it to initialize and send feature commands. */
8251           FeatureDone(cps, 1);
8252           cps->protocolVersion = 1;
8253           return;
8254         }
8255         cps->maybeThinking = FALSE;
8256
8257         if (StrStr(message, "draw")) {
8258             /* Program doesn't have "draw" command */
8259             cps->sendDrawOffers = 0;
8260             return;
8261         }
8262         if (cps->sendTime != 1 &&
8263             (StrStr(message, "time") || StrStr(message, "otim"))) {
8264           /* Program apparently doesn't have "time" or "otim" command */
8265           cps->sendTime = 0;
8266           return;
8267         }
8268         if (StrStr(message, "analyze")) {
8269             cps->analysisSupport = FALSE;
8270             cps->analyzing = FALSE;
8271 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8272             EditGameEvent(); // [HGM] try to preserve loaded game
8273             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8274             DisplayError(buf2, 0);
8275             return;
8276         }
8277         if (StrStr(message, "(no matching move)st")) {
8278           /* Special kludge for GNU Chess 4 only */
8279           cps->stKludge = TRUE;
8280           SendTimeControl(cps, movesPerSession, timeControl,
8281                           timeIncrement, appData.searchDepth,
8282                           searchTime);
8283           return;
8284         }
8285         if (StrStr(message, "(no matching move)sd")) {
8286           /* Special kludge for GNU Chess 4 only */
8287           cps->sdKludge = TRUE;
8288           SendTimeControl(cps, movesPerSession, timeControl,
8289                           timeIncrement, appData.searchDepth,
8290                           searchTime);
8291           return;
8292         }
8293         if (!StrStr(message, "llegal")) {
8294             return;
8295         }
8296         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8297             gameMode == IcsIdle) return;
8298         if (forwardMostMove <= backwardMostMove) return;
8299         if (pausing) PauseEvent();
8300       if(appData.forceIllegal) {
8301             // [HGM] illegal: machine refused move; force position after move into it
8302           SendToProgram("force\n", cps);
8303           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8304                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8305                 // when black is to move, while there might be nothing on a2 or black
8306                 // might already have the move. So send the board as if white has the move.
8307                 // But first we must change the stm of the engine, as it refused the last move
8308                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8309                 if(WhiteOnMove(forwardMostMove)) {
8310                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8311                     SendBoard(cps, forwardMostMove); // kludgeless board
8312                 } else {
8313                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8314                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8315                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8316                 }
8317           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8318             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8319                  gameMode == TwoMachinesPlay)
8320               SendToProgram("go\n", cps);
8321             return;
8322       } else
8323         if (gameMode == PlayFromGameFile) {
8324             /* Stop reading this game file */
8325             gameMode = EditGame;
8326             ModeHighlight();
8327         }
8328         /* [HGM] illegal-move claim should forfeit game when Xboard */
8329         /* only passes fully legal moves                            */
8330         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8331             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8332                                 "False illegal-move claim", GE_XBOARD );
8333             return; // do not take back move we tested as valid
8334         }
8335         currentMove = forwardMostMove-1;
8336         DisplayMove(currentMove-1); /* before DisplayMoveError */
8337         SwitchClocks(forwardMostMove-1); // [HGM] race
8338         DisplayBothClocks();
8339         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8340                 parseList[currentMove], _(cps->which));
8341         DisplayMoveError(buf1);
8342         DrawPosition(FALSE, boards[currentMove]);
8343         return;
8344     }
8345     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8346         /* Program has a broken "time" command that
8347            outputs a string not ending in newline.
8348            Don't use it. */
8349         cps->sendTime = 0;
8350     }
8351
8352     /*
8353      * If chess program startup fails, exit with an error message.
8354      * Attempts to recover here are futile.
8355      */
8356     if ((StrStr(message, "unknown host") != NULL)
8357         || (StrStr(message, "No remote directory") != NULL)
8358         || (StrStr(message, "not found") != NULL)
8359         || (StrStr(message, "No such file") != NULL)
8360         || (StrStr(message, "can't alloc") != NULL)
8361         || (StrStr(message, "Permission denied") != NULL)) {
8362
8363         cps->maybeThinking = FALSE;
8364         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8365                 _(cps->which), cps->program, cps->host, message);
8366         RemoveInputSource(cps->isr);
8367         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8368             if(cps == &first) appData.noChessProgram = TRUE;
8369             DisplayError(buf1, 0);
8370         }
8371         return;
8372     }
8373
8374     /*
8375      * Look for hint output
8376      */
8377     if (sscanf(message, "Hint: %s", buf1) == 1) {
8378         if (cps == &first && hintRequested) {
8379             hintRequested = FALSE;
8380             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8381                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8382                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8383                                     PosFlags(forwardMostMove),
8384                                     fromY, fromX, toY, toX, promoChar, buf1);
8385                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8386                 DisplayInformation(buf2);
8387             } else {
8388                 /* Hint move could not be parsed!? */
8389               snprintf(buf2, sizeof(buf2),
8390                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8391                         buf1, _(cps->which));
8392                 DisplayError(buf2, 0);
8393             }
8394         } else {
8395           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8396         }
8397         return;
8398     }
8399
8400     /*
8401      * Ignore other messages if game is not in progress
8402      */
8403     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8404         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8405
8406     /*
8407      * look for win, lose, draw, or draw offer
8408      */
8409     if (strncmp(message, "1-0", 3) == 0) {
8410         char *p, *q, *r = "";
8411         p = strchr(message, '{');
8412         if (p) {
8413             q = strchr(p, '}');
8414             if (q) {
8415                 *q = NULLCHAR;
8416                 r = p + 1;
8417             }
8418         }
8419         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8420         return;
8421     } else if (strncmp(message, "0-1", 3) == 0) {
8422         char *p, *q, *r = "";
8423         p = strchr(message, '{');
8424         if (p) {
8425             q = strchr(p, '}');
8426             if (q) {
8427                 *q = NULLCHAR;
8428                 r = p + 1;
8429             }
8430         }
8431         /* Kludge for Arasan 4.1 bug */
8432         if (strcmp(r, "Black resigns") == 0) {
8433             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8434             return;
8435         }
8436         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8437         return;
8438     } else if (strncmp(message, "1/2", 3) == 0) {
8439         char *p, *q, *r = "";
8440         p = strchr(message, '{');
8441         if (p) {
8442             q = strchr(p, '}');
8443             if (q) {
8444                 *q = NULLCHAR;
8445                 r = p + 1;
8446             }
8447         }
8448
8449         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8450         return;
8451
8452     } else if (strncmp(message, "White resign", 12) == 0) {
8453         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8454         return;
8455     } else if (strncmp(message, "Black resign", 12) == 0) {
8456         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8457         return;
8458     } else if (strncmp(message, "White matches", 13) == 0 ||
8459                strncmp(message, "Black matches", 13) == 0   ) {
8460         /* [HGM] ignore GNUShogi noises */
8461         return;
8462     } else if (strncmp(message, "White", 5) == 0 &&
8463                message[5] != '(' &&
8464                StrStr(message, "Black") == NULL) {
8465         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8466         return;
8467     } else if (strncmp(message, "Black", 5) == 0 &&
8468                message[5] != '(') {
8469         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8470         return;
8471     } else if (strcmp(message, "resign") == 0 ||
8472                strcmp(message, "computer resigns") == 0) {
8473         switch (gameMode) {
8474           case MachinePlaysBlack:
8475           case IcsPlayingBlack:
8476             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8477             break;
8478           case MachinePlaysWhite:
8479           case IcsPlayingWhite:
8480             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8481             break;
8482           case TwoMachinesPlay:
8483             if (cps->twoMachinesColor[0] == 'w')
8484               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8485             else
8486               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8487             break;
8488           default:
8489             /* can't happen */
8490             break;
8491         }
8492         return;
8493     } else if (strncmp(message, "opponent mates", 14) == 0) {
8494         switch (gameMode) {
8495           case MachinePlaysBlack:
8496           case IcsPlayingBlack:
8497             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8498             break;
8499           case MachinePlaysWhite:
8500           case IcsPlayingWhite:
8501             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8502             break;
8503           case TwoMachinesPlay:
8504             if (cps->twoMachinesColor[0] == 'w')
8505               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8506             else
8507               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8508             break;
8509           default:
8510             /* can't happen */
8511             break;
8512         }
8513         return;
8514     } else if (strncmp(message, "computer mates", 14) == 0) {
8515         switch (gameMode) {
8516           case MachinePlaysBlack:
8517           case IcsPlayingBlack:
8518             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8519             break;
8520           case MachinePlaysWhite:
8521           case IcsPlayingWhite:
8522             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8523             break;
8524           case TwoMachinesPlay:
8525             if (cps->twoMachinesColor[0] == 'w')
8526               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8527             else
8528               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8529             break;
8530           default:
8531             /* can't happen */
8532             break;
8533         }
8534         return;
8535     } else if (strncmp(message, "checkmate", 9) == 0) {
8536         if (WhiteOnMove(forwardMostMove)) {
8537             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8538         } else {
8539             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8540         }
8541         return;
8542     } else if (strstr(message, "Draw") != NULL ||
8543                strstr(message, "game is a draw") != NULL) {
8544         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8545         return;
8546     } else if (strstr(message, "offer") != NULL &&
8547                strstr(message, "draw") != NULL) {
8548 #if ZIPPY
8549         if (appData.zippyPlay && first.initDone) {
8550             /* Relay offer to ICS */
8551             SendToICS(ics_prefix);
8552             SendToICS("draw\n");
8553         }
8554 #endif
8555         cps->offeredDraw = 2; /* valid until this engine moves twice */
8556         if (gameMode == TwoMachinesPlay) {
8557             if (cps->other->offeredDraw) {
8558                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8559             /* [HGM] in two-machine mode we delay relaying draw offer      */
8560             /* until after we also have move, to see if it is really claim */
8561             }
8562         } else if (gameMode == MachinePlaysWhite ||
8563                    gameMode == MachinePlaysBlack) {
8564           if (userOfferedDraw) {
8565             DisplayInformation(_("Machine accepts your draw offer"));
8566             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8567           } else {
8568             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8569           }
8570         }
8571     }
8572
8573
8574     /*
8575      * Look for thinking output
8576      */
8577     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8578           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8579                                 ) {
8580         int plylev, mvleft, mvtot, curscore, time;
8581         char mvname[MOVE_LEN];
8582         u64 nodes; // [DM]
8583         char plyext;
8584         int ignore = FALSE;
8585         int prefixHint = FALSE;
8586         mvname[0] = NULLCHAR;
8587
8588         switch (gameMode) {
8589           case MachinePlaysBlack:
8590           case IcsPlayingBlack:
8591             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8592             break;
8593           case MachinePlaysWhite:
8594           case IcsPlayingWhite:
8595             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8596             break;
8597           case AnalyzeMode:
8598           case AnalyzeFile:
8599             break;
8600           case IcsObserving: /* [DM] icsEngineAnalyze */
8601             if (!appData.icsEngineAnalyze) ignore = TRUE;
8602             break;
8603           case TwoMachinesPlay:
8604             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8605                 ignore = TRUE;
8606             }
8607             break;
8608           default:
8609             ignore = TRUE;
8610             break;
8611         }
8612
8613         if (!ignore) {
8614             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8615             buf1[0] = NULLCHAR;
8616             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8617                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8618
8619                 if (plyext != ' ' && plyext != '\t') {
8620                     time *= 100;
8621                 }
8622
8623                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8624                 if( cps->scoreIsAbsolute &&
8625                     ( gameMode == MachinePlaysBlack ||
8626                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8627                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8628                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8629                      !WhiteOnMove(currentMove)
8630                     ) )
8631                 {
8632                     curscore = -curscore;
8633                 }
8634
8635                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8636
8637                 tempStats.depth = plylev;
8638                 tempStats.nodes = nodes;
8639                 tempStats.time = time;
8640                 tempStats.score = curscore;
8641                 tempStats.got_only_move = 0;
8642
8643                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8644                         int ticklen;
8645
8646                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8647                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8648                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8649                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8650                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8651                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8652                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8653                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8654                 }
8655
8656                 /* Buffer overflow protection */
8657                 if (pv[0] != NULLCHAR) {
8658                     if (strlen(pv) >= sizeof(tempStats.movelist)
8659                         && appData.debugMode) {
8660                         fprintf(debugFP,
8661                                 "PV is too long; using the first %u bytes.\n",
8662                                 (unsigned) sizeof(tempStats.movelist) - 1);
8663                     }
8664
8665                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8666                 } else {
8667                     sprintf(tempStats.movelist, " no PV\n");
8668                 }
8669
8670                 if (tempStats.seen_stat) {
8671                     tempStats.ok_to_send = 1;
8672                 }
8673
8674                 if (strchr(tempStats.movelist, '(') != NULL) {
8675                     tempStats.line_is_book = 1;
8676                     tempStats.nr_moves = 0;
8677                     tempStats.moves_left = 0;
8678                 } else {
8679                     tempStats.line_is_book = 0;
8680                 }
8681
8682                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8683                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8684
8685                 SendProgramStatsToFrontend( cps, &tempStats );
8686
8687                 /*
8688                     [AS] Protect the thinkOutput buffer from overflow... this
8689                     is only useful if buf1 hasn't overflowed first!
8690                 */
8691                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8692                          plylev,
8693                          (gameMode == TwoMachinesPlay ?
8694                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8695                          ((double) curscore) / 100.0,
8696                          prefixHint ? lastHint : "",
8697                          prefixHint ? " " : "" );
8698
8699                 if( buf1[0] != NULLCHAR ) {
8700                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8701
8702                     if( strlen(pv) > max_len ) {
8703                         if( appData.debugMode) {
8704                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8705                         }
8706                         pv[max_len+1] = '\0';
8707                     }
8708
8709                     strcat( thinkOutput, pv);
8710                 }
8711
8712                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8713                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8714                     DisplayMove(currentMove - 1);
8715                 }
8716                 return;
8717
8718             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8719                 /* crafty (9.25+) says "(only move) <move>"
8720                  * if there is only 1 legal move
8721                  */
8722                 sscanf(p, "(only move) %s", buf1);
8723                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8724                 sprintf(programStats.movelist, "%s (only move)", buf1);
8725                 programStats.depth = 1;
8726                 programStats.nr_moves = 1;
8727                 programStats.moves_left = 1;
8728                 programStats.nodes = 1;
8729                 programStats.time = 1;
8730                 programStats.got_only_move = 1;
8731
8732                 /* Not really, but we also use this member to
8733                    mean "line isn't going to change" (Crafty
8734                    isn't searching, so stats won't change) */
8735                 programStats.line_is_book = 1;
8736
8737                 SendProgramStatsToFrontend( cps, &programStats );
8738
8739                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8740                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8741                     DisplayMove(currentMove - 1);
8742                 }
8743                 return;
8744             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8745                               &time, &nodes, &plylev, &mvleft,
8746                               &mvtot, mvname) >= 5) {
8747                 /* The stat01: line is from Crafty (9.29+) in response
8748                    to the "." command */
8749                 programStats.seen_stat = 1;
8750                 cps->maybeThinking = TRUE;
8751
8752                 if (programStats.got_only_move || !appData.periodicUpdates)
8753                   return;
8754
8755                 programStats.depth = plylev;
8756                 programStats.time = time;
8757                 programStats.nodes = nodes;
8758                 programStats.moves_left = mvleft;
8759                 programStats.nr_moves = mvtot;
8760                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8761                 programStats.ok_to_send = 1;
8762                 programStats.movelist[0] = '\0';
8763
8764                 SendProgramStatsToFrontend( cps, &programStats );
8765
8766                 return;
8767
8768             } else if (strncmp(message,"++",2) == 0) {
8769                 /* Crafty 9.29+ outputs this */
8770                 programStats.got_fail = 2;
8771                 return;
8772
8773             } else if (strncmp(message,"--",2) == 0) {
8774                 /* Crafty 9.29+ outputs this */
8775                 programStats.got_fail = 1;
8776                 return;
8777
8778             } else if (thinkOutput[0] != NULLCHAR &&
8779                        strncmp(message, "    ", 4) == 0) {
8780                 unsigned message_len;
8781
8782                 p = message;
8783                 while (*p && *p == ' ') p++;
8784
8785                 message_len = strlen( p );
8786
8787                 /* [AS] Avoid buffer overflow */
8788                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8789                     strcat(thinkOutput, " ");
8790                     strcat(thinkOutput, p);
8791                 }
8792
8793                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8794                     strcat(programStats.movelist, " ");
8795                     strcat(programStats.movelist, p);
8796                 }
8797
8798                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8799                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8800                     DisplayMove(currentMove - 1);
8801                 }
8802                 return;
8803             }
8804         }
8805         else {
8806             buf1[0] = NULLCHAR;
8807
8808             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8809                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8810             {
8811                 ChessProgramStats cpstats;
8812
8813                 if (plyext != ' ' && plyext != '\t') {
8814                     time *= 100;
8815                 }
8816
8817                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8818                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8819                     curscore = -curscore;
8820                 }
8821
8822                 cpstats.depth = plylev;
8823                 cpstats.nodes = nodes;
8824                 cpstats.time = time;
8825                 cpstats.score = curscore;
8826                 cpstats.got_only_move = 0;
8827                 cpstats.movelist[0] = '\0';
8828
8829                 if (buf1[0] != NULLCHAR) {
8830                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8831                 }
8832
8833                 cpstats.ok_to_send = 0;
8834                 cpstats.line_is_book = 0;
8835                 cpstats.nr_moves = 0;
8836                 cpstats.moves_left = 0;
8837
8838                 SendProgramStatsToFrontend( cps, &cpstats );
8839             }
8840         }
8841     }
8842 }
8843
8844
8845 /* Parse a game score from the character string "game", and
8846    record it as the history of the current game.  The game
8847    score is NOT assumed to start from the standard position.
8848    The display is not updated in any way.
8849    */
8850 void
8851 ParseGameHistory(game)
8852      char *game;
8853 {
8854     ChessMove moveType;
8855     int fromX, fromY, toX, toY, boardIndex;
8856     char promoChar;
8857     char *p, *q;
8858     char buf[MSG_SIZ];
8859
8860     if (appData.debugMode)
8861       fprintf(debugFP, "Parsing game history: %s\n", game);
8862
8863     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8864     gameInfo.site = StrSave(appData.icsHost);
8865     gameInfo.date = PGNDate();
8866     gameInfo.round = StrSave("-");
8867
8868     /* Parse out names of players */
8869     while (*game == ' ') game++;
8870     p = buf;
8871     while (*game != ' ') *p++ = *game++;
8872     *p = NULLCHAR;
8873     gameInfo.white = StrSave(buf);
8874     while (*game == ' ') game++;
8875     p = buf;
8876     while (*game != ' ' && *game != '\n') *p++ = *game++;
8877     *p = NULLCHAR;
8878     gameInfo.black = StrSave(buf);
8879
8880     /* Parse moves */
8881     boardIndex = blackPlaysFirst ? 1 : 0;
8882     yynewstr(game);
8883     for (;;) {
8884         yyboardindex = boardIndex;
8885         moveType = (ChessMove) Myylex();
8886         switch (moveType) {
8887           case IllegalMove:             /* maybe suicide chess, etc. */
8888   if (appData.debugMode) {
8889     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8890     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8891     setbuf(debugFP, NULL);
8892   }
8893           case WhitePromotion:
8894           case BlackPromotion:
8895           case WhiteNonPromotion:
8896           case BlackNonPromotion:
8897           case NormalMove:
8898           case WhiteCapturesEnPassant:
8899           case BlackCapturesEnPassant:
8900           case WhiteKingSideCastle:
8901           case WhiteQueenSideCastle:
8902           case BlackKingSideCastle:
8903           case BlackQueenSideCastle:
8904           case WhiteKingSideCastleWild:
8905           case WhiteQueenSideCastleWild:
8906           case BlackKingSideCastleWild:
8907           case BlackQueenSideCastleWild:
8908           /* PUSH Fabien */
8909           case WhiteHSideCastleFR:
8910           case WhiteASideCastleFR:
8911           case BlackHSideCastleFR:
8912           case BlackASideCastleFR:
8913           /* POP Fabien */
8914             fromX = currentMoveString[0] - AAA;
8915             fromY = currentMoveString[1] - ONE;
8916             toX = currentMoveString[2] - AAA;
8917             toY = currentMoveString[3] - ONE;
8918             promoChar = currentMoveString[4];
8919             break;
8920           case WhiteDrop:
8921           case BlackDrop:
8922             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8923             fromX = moveType == WhiteDrop ?
8924               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8925             (int) CharToPiece(ToLower(currentMoveString[0]));
8926             fromY = DROP_RANK;
8927             toX = currentMoveString[2] - AAA;
8928             toY = currentMoveString[3] - ONE;
8929             promoChar = NULLCHAR;
8930             break;
8931           case AmbiguousMove:
8932             /* bug? */
8933             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8934   if (appData.debugMode) {
8935     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8936     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8937     setbuf(debugFP, NULL);
8938   }
8939             DisplayError(buf, 0);
8940             return;
8941           case ImpossibleMove:
8942             /* bug? */
8943             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8944   if (appData.debugMode) {
8945     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8946     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8947     setbuf(debugFP, NULL);
8948   }
8949             DisplayError(buf, 0);
8950             return;
8951           case EndOfFile:
8952             if (boardIndex < backwardMostMove) {
8953                 /* Oops, gap.  How did that happen? */
8954                 DisplayError(_("Gap in move list"), 0);
8955                 return;
8956             }
8957             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8958             if (boardIndex > forwardMostMove) {
8959                 forwardMostMove = boardIndex;
8960             }
8961             return;
8962           case ElapsedTime:
8963             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8964                 strcat(parseList[boardIndex-1], " ");
8965                 strcat(parseList[boardIndex-1], yy_text);
8966             }
8967             continue;
8968           case Comment:
8969           case PGNTag:
8970           case NAG:
8971           default:
8972             /* ignore */
8973             continue;
8974           case WhiteWins:
8975           case BlackWins:
8976           case GameIsDrawn:
8977           case GameUnfinished:
8978             if (gameMode == IcsExamining) {
8979                 if (boardIndex < backwardMostMove) {
8980                     /* Oops, gap.  How did that happen? */
8981                     return;
8982                 }
8983                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8984                 return;
8985             }
8986             gameInfo.result = moveType;
8987             p = strchr(yy_text, '{');
8988             if (p == NULL) p = strchr(yy_text, '(');
8989             if (p == NULL) {
8990                 p = yy_text;
8991                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8992             } else {
8993                 q = strchr(p, *p == '{' ? '}' : ')');
8994                 if (q != NULL) *q = NULLCHAR;
8995                 p++;
8996             }
8997             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8998             gameInfo.resultDetails = StrSave(p);
8999             continue;
9000         }
9001         if (boardIndex >= forwardMostMove &&
9002             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9003             backwardMostMove = blackPlaysFirst ? 1 : 0;
9004             return;
9005         }
9006         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9007                                  fromY, fromX, toY, toX, promoChar,
9008                                  parseList[boardIndex]);
9009         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9010         /* currentMoveString is set as a side-effect of yylex */
9011         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9012         strcat(moveList[boardIndex], "\n");
9013         boardIndex++;
9014         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9015         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9016           case MT_NONE:
9017           case MT_STALEMATE:
9018           default:
9019             break;
9020           case MT_CHECK:
9021             if(gameInfo.variant != VariantShogi)
9022                 strcat(parseList[boardIndex - 1], "+");
9023             break;
9024           case MT_CHECKMATE:
9025           case MT_STAINMATE:
9026             strcat(parseList[boardIndex - 1], "#");
9027             break;
9028         }
9029     }
9030 }
9031
9032
9033 /* Apply a move to the given board  */
9034 void
9035 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9036      int fromX, fromY, toX, toY;
9037      int promoChar;
9038      Board board;
9039 {
9040   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9041   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9042
9043     /* [HGM] compute & store e.p. status and castling rights for new position */
9044     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9045
9046       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9047       oldEP = (signed char)board[EP_STATUS];
9048       board[EP_STATUS] = EP_NONE;
9049
9050   if (fromY == DROP_RANK) {
9051         /* must be first */
9052         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9053             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9054             return;
9055         }
9056         piece = board[toY][toX] = (ChessSquare) fromX;
9057   } else {
9058       int i;
9059
9060       if( board[toY][toX] != EmptySquare )
9061            board[EP_STATUS] = EP_CAPTURE;
9062
9063       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9064            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9065                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9066       } else
9067       if( board[fromY][fromX] == WhitePawn ) {
9068            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9069                board[EP_STATUS] = EP_PAWN_MOVE;
9070            if( toY-fromY==2) {
9071                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9072                         gameInfo.variant != VariantBerolina || toX < fromX)
9073                       board[EP_STATUS] = toX | berolina;
9074                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9075                         gameInfo.variant != VariantBerolina || toX > fromX)
9076                       board[EP_STATUS] = toX;
9077            }
9078       } else
9079       if( board[fromY][fromX] == BlackPawn ) {
9080            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9081                board[EP_STATUS] = EP_PAWN_MOVE;
9082            if( toY-fromY== -2) {
9083                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9084                         gameInfo.variant != VariantBerolina || toX < fromX)
9085                       board[EP_STATUS] = toX | berolina;
9086                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9087                         gameInfo.variant != VariantBerolina || toX > fromX)
9088                       board[EP_STATUS] = toX;
9089            }
9090        }
9091
9092        for(i=0; i<nrCastlingRights; i++) {
9093            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9094               board[CASTLING][i] == toX   && castlingRank[i] == toY
9095              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9096        }
9097
9098      if (fromX == toX && fromY == toY) return;
9099
9100      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9101      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9102      if(gameInfo.variant == VariantKnightmate)
9103          king += (int) WhiteUnicorn - (int) WhiteKing;
9104
9105     /* Code added by Tord: */
9106     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9107     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9108         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9109       board[fromY][fromX] = EmptySquare;
9110       board[toY][toX] = EmptySquare;
9111       if((toX > fromX) != (piece == WhiteRook)) {
9112         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9113       } else {
9114         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9115       }
9116     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9117                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9118       board[fromY][fromX] = EmptySquare;
9119       board[toY][toX] = EmptySquare;
9120       if((toX > fromX) != (piece == BlackRook)) {
9121         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9122       } else {
9123         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9124       }
9125     /* End of code added by Tord */
9126
9127     } else if (board[fromY][fromX] == king
9128         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9129         && toY == fromY && toX > fromX+1) {
9130         board[fromY][fromX] = EmptySquare;
9131         board[toY][toX] = king;
9132         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9133         board[fromY][BOARD_RGHT-1] = EmptySquare;
9134     } else if (board[fromY][fromX] == king
9135         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9136                && toY == fromY && toX < fromX-1) {
9137         board[fromY][fromX] = EmptySquare;
9138         board[toY][toX] = king;
9139         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9140         board[fromY][BOARD_LEFT] = EmptySquare;
9141     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9142                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9143                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9144                ) {
9145         /* white pawn promotion */
9146         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9147         if(gameInfo.variant==VariantBughouse ||
9148            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9149             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9150         board[fromY][fromX] = EmptySquare;
9151     } else if ((fromY >= BOARD_HEIGHT>>1)
9152                && (toX != fromX)
9153                && gameInfo.variant != VariantXiangqi
9154                && gameInfo.variant != VariantBerolina
9155                && (board[fromY][fromX] == WhitePawn)
9156                && (board[toY][toX] == EmptySquare)) {
9157         board[fromY][fromX] = EmptySquare;
9158         board[toY][toX] = WhitePawn;
9159         captured = board[toY - 1][toX];
9160         board[toY - 1][toX] = EmptySquare;
9161     } else if ((fromY == BOARD_HEIGHT-4)
9162                && (toX == fromX)
9163                && gameInfo.variant == VariantBerolina
9164                && (board[fromY][fromX] == WhitePawn)
9165                && (board[toY][toX] == EmptySquare)) {
9166         board[fromY][fromX] = EmptySquare;
9167         board[toY][toX] = WhitePawn;
9168         if(oldEP & EP_BEROLIN_A) {
9169                 captured = board[fromY][fromX-1];
9170                 board[fromY][fromX-1] = EmptySquare;
9171         }else{  captured = board[fromY][fromX+1];
9172                 board[fromY][fromX+1] = EmptySquare;
9173         }
9174     } else if (board[fromY][fromX] == king
9175         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9176                && toY == fromY && toX > fromX+1) {
9177         board[fromY][fromX] = EmptySquare;
9178         board[toY][toX] = king;
9179         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9180         board[fromY][BOARD_RGHT-1] = EmptySquare;
9181     } else if (board[fromY][fromX] == king
9182         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9183                && toY == fromY && toX < fromX-1) {
9184         board[fromY][fromX] = EmptySquare;
9185         board[toY][toX] = king;
9186         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9187         board[fromY][BOARD_LEFT] = EmptySquare;
9188     } else if (fromY == 7 && fromX == 3
9189                && board[fromY][fromX] == BlackKing
9190                && toY == 7 && toX == 5) {
9191         board[fromY][fromX] = EmptySquare;
9192         board[toY][toX] = BlackKing;
9193         board[fromY][7] = EmptySquare;
9194         board[toY][4] = BlackRook;
9195     } else if (fromY == 7 && fromX == 3
9196                && board[fromY][fromX] == BlackKing
9197                && toY == 7 && toX == 1) {
9198         board[fromY][fromX] = EmptySquare;
9199         board[toY][toX] = BlackKing;
9200         board[fromY][0] = EmptySquare;
9201         board[toY][2] = BlackRook;
9202     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9203                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9204                && toY < promoRank && promoChar
9205                ) {
9206         /* black pawn promotion */
9207         board[toY][toX] = CharToPiece(ToLower(promoChar));
9208         if(gameInfo.variant==VariantBughouse ||
9209            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9210             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9211         board[fromY][fromX] = EmptySquare;
9212     } else if ((fromY < BOARD_HEIGHT>>1)
9213                && (toX != fromX)
9214                && gameInfo.variant != VariantXiangqi
9215                && gameInfo.variant != VariantBerolina
9216                && (board[fromY][fromX] == BlackPawn)
9217                && (board[toY][toX] == EmptySquare)) {
9218         board[fromY][fromX] = EmptySquare;
9219         board[toY][toX] = BlackPawn;
9220         captured = board[toY + 1][toX];
9221         board[toY + 1][toX] = EmptySquare;
9222     } else if ((fromY == 3)
9223                && (toX == fromX)
9224                && gameInfo.variant == VariantBerolina
9225                && (board[fromY][fromX] == BlackPawn)
9226                && (board[toY][toX] == EmptySquare)) {
9227         board[fromY][fromX] = EmptySquare;
9228         board[toY][toX] = BlackPawn;
9229         if(oldEP & EP_BEROLIN_A) {
9230                 captured = board[fromY][fromX-1];
9231                 board[fromY][fromX-1] = EmptySquare;
9232         }else{  captured = board[fromY][fromX+1];
9233                 board[fromY][fromX+1] = EmptySquare;
9234         }
9235     } else {
9236         board[toY][toX] = board[fromY][fromX];
9237         board[fromY][fromX] = EmptySquare;
9238     }
9239   }
9240
9241     if (gameInfo.holdingsWidth != 0) {
9242
9243       /* !!A lot more code needs to be written to support holdings  */
9244       /* [HGM] OK, so I have written it. Holdings are stored in the */
9245       /* penultimate board files, so they are automaticlly stored   */
9246       /* in the game history.                                       */
9247       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9248                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9249         /* Delete from holdings, by decreasing count */
9250         /* and erasing image if necessary            */
9251         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9252         if(p < (int) BlackPawn) { /* white drop */
9253              p -= (int)WhitePawn;
9254                  p = PieceToNumber((ChessSquare)p);
9255              if(p >= gameInfo.holdingsSize) p = 0;
9256              if(--board[p][BOARD_WIDTH-2] <= 0)
9257                   board[p][BOARD_WIDTH-1] = EmptySquare;
9258              if((int)board[p][BOARD_WIDTH-2] < 0)
9259                         board[p][BOARD_WIDTH-2] = 0;
9260         } else {                  /* black drop */
9261              p -= (int)BlackPawn;
9262                  p = PieceToNumber((ChessSquare)p);
9263              if(p >= gameInfo.holdingsSize) p = 0;
9264              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9265                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9266              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9267                         board[BOARD_HEIGHT-1-p][1] = 0;
9268         }
9269       }
9270       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9271           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9272         /* [HGM] holdings: Add to holdings, if holdings exist */
9273         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9274                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9275                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9276         }
9277         p = (int) captured;
9278         if (p >= (int) BlackPawn) {
9279           p -= (int)BlackPawn;
9280           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9281                   /* in Shogi restore piece to its original  first */
9282                   captured = (ChessSquare) (DEMOTED captured);
9283                   p = DEMOTED p;
9284           }
9285           p = PieceToNumber((ChessSquare)p);
9286           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9287           board[p][BOARD_WIDTH-2]++;
9288           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9289         } else {
9290           p -= (int)WhitePawn;
9291           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9292                   captured = (ChessSquare) (DEMOTED captured);
9293                   p = DEMOTED p;
9294           }
9295           p = PieceToNumber((ChessSquare)p);
9296           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9297           board[BOARD_HEIGHT-1-p][1]++;
9298           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9299         }
9300       }
9301     } else if (gameInfo.variant == VariantAtomic) {
9302       if (captured != EmptySquare) {
9303         int y, x;
9304         for (y = toY-1; y <= toY+1; y++) {
9305           for (x = toX-1; x <= toX+1; x++) {
9306             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9307                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9308               board[y][x] = EmptySquare;
9309             }
9310           }
9311         }
9312         board[toY][toX] = EmptySquare;
9313       }
9314     }
9315     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9316         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9317     } else
9318     if(promoChar == '+') {
9319         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9320         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9321     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9322         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9323     }
9324     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9325                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9326         // [HGM] superchess: take promotion piece out of holdings
9327         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9328         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9329             if(!--board[k][BOARD_WIDTH-2])
9330                 board[k][BOARD_WIDTH-1] = EmptySquare;
9331         } else {
9332             if(!--board[BOARD_HEIGHT-1-k][1])
9333                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9334         }
9335     }
9336
9337 }
9338
9339 /* Updates forwardMostMove */
9340 void
9341 MakeMove(fromX, fromY, toX, toY, promoChar)
9342      int fromX, fromY, toX, toY;
9343      int promoChar;
9344 {
9345 //    forwardMostMove++; // [HGM] bare: moved downstream
9346
9347     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9348         int timeLeft; static int lastLoadFlag=0; int king, piece;
9349         piece = boards[forwardMostMove][fromY][fromX];
9350         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9351         if(gameInfo.variant == VariantKnightmate)
9352             king += (int) WhiteUnicorn - (int) WhiteKing;
9353         if(forwardMostMove == 0) {
9354             if(blackPlaysFirst)
9355                 fprintf(serverMoves, "%s;", second.tidy);
9356             fprintf(serverMoves, "%s;", first.tidy);
9357             if(!blackPlaysFirst)
9358                 fprintf(serverMoves, "%s;", second.tidy);
9359         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9360         lastLoadFlag = loadFlag;
9361         // print base move
9362         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9363         // print castling suffix
9364         if( toY == fromY && piece == king ) {
9365             if(toX-fromX > 1)
9366                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9367             if(fromX-toX >1)
9368                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9369         }
9370         // e.p. suffix
9371         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9372              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9373              boards[forwardMostMove][toY][toX] == EmptySquare
9374              && fromX != toX && fromY != toY)
9375                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9376         // promotion suffix
9377         if(promoChar != NULLCHAR)
9378                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9379         if(!loadFlag) {
9380             fprintf(serverMoves, "/%d/%d",
9381                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9382             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9383             else                      timeLeft = blackTimeRemaining/1000;
9384             fprintf(serverMoves, "/%d", timeLeft);
9385         }
9386         fflush(serverMoves);
9387     }
9388
9389     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9390       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9391                         0, 1);
9392       return;
9393     }
9394     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9395     if (commentList[forwardMostMove+1] != NULL) {
9396         free(commentList[forwardMostMove+1]);
9397         commentList[forwardMostMove+1] = NULL;
9398     }
9399     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9400     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9401     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9402     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9403     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9404     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9405     gameInfo.result = GameUnfinished;
9406     if (gameInfo.resultDetails != NULL) {
9407         free(gameInfo.resultDetails);
9408         gameInfo.resultDetails = NULL;
9409     }
9410     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9411                               moveList[forwardMostMove - 1]);
9412     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9413                              PosFlags(forwardMostMove - 1),
9414                              fromY, fromX, toY, toX, promoChar,
9415                              parseList[forwardMostMove - 1]);
9416     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9417       case MT_NONE:
9418       case MT_STALEMATE:
9419       default:
9420         break;
9421       case MT_CHECK:
9422         if(gameInfo.variant != VariantShogi)
9423             strcat(parseList[forwardMostMove - 1], "+");
9424         break;
9425       case MT_CHECKMATE:
9426       case MT_STAINMATE:
9427         strcat(parseList[forwardMostMove - 1], "#");
9428         break;
9429     }
9430     if (appData.debugMode) {
9431         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9432     }
9433
9434 }
9435
9436 /* Updates currentMove if not pausing */
9437 void
9438 ShowMove(fromX, fromY, toX, toY)
9439 {
9440     int instant = (gameMode == PlayFromGameFile) ?
9441         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9442     if(appData.noGUI) return;
9443     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9444         if (!instant) {
9445             if (forwardMostMove == currentMove + 1) {
9446                 AnimateMove(boards[forwardMostMove - 1],
9447                             fromX, fromY, toX, toY);
9448             }
9449             if (appData.highlightLastMove) {
9450                 SetHighlights(fromX, fromY, toX, toY);
9451             }
9452         }
9453         currentMove = forwardMostMove;
9454     }
9455
9456     if (instant) return;
9457
9458     DisplayMove(currentMove - 1);
9459     DrawPosition(FALSE, boards[currentMove]);
9460     DisplayBothClocks();
9461     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9462     DisplayBook(currentMove);
9463 }
9464
9465 void SendEgtPath(ChessProgramState *cps)
9466 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9467         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9468
9469         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9470
9471         while(*p) {
9472             char c, *q = name+1, *r, *s;
9473
9474             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9475             while(*p && *p != ',') *q++ = *p++;
9476             *q++ = ':'; *q = 0;
9477             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9478                 strcmp(name, ",nalimov:") == 0 ) {
9479                 // take nalimov path from the menu-changeable option first, if it is defined
9480               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9481                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9482             } else
9483             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9484                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9485                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9486                 s = r = StrStr(s, ":") + 1; // beginning of path info
9487                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9488                 c = *r; *r = 0;             // temporarily null-terminate path info
9489                     *--q = 0;               // strip of trailig ':' from name
9490                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9491                 *r = c;
9492                 SendToProgram(buf,cps);     // send egtbpath command for this format
9493             }
9494             if(*p == ',') p++; // read away comma to position for next format name
9495         }
9496 }
9497
9498 void
9499 InitChessProgram(cps, setup)
9500      ChessProgramState *cps;
9501      int setup; /* [HGM] needed to setup FRC opening position */
9502 {
9503     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9504     if (appData.noChessProgram) return;
9505     hintRequested = FALSE;
9506     bookRequested = FALSE;
9507
9508     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9509     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9510     if(cps->memSize) { /* [HGM] memory */
9511       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9512         SendToProgram(buf, cps);
9513     }
9514     SendEgtPath(cps); /* [HGM] EGT */
9515     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9516       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9517         SendToProgram(buf, cps);
9518     }
9519
9520     SendToProgram(cps->initString, cps);
9521     if (gameInfo.variant != VariantNormal &&
9522         gameInfo.variant != VariantLoadable
9523         /* [HGM] also send variant if board size non-standard */
9524         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9525                                             ) {
9526       char *v = VariantName(gameInfo.variant);
9527       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9528         /* [HGM] in protocol 1 we have to assume all variants valid */
9529         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9530         DisplayFatalError(buf, 0, 1);
9531         return;
9532       }
9533
9534       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9535       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9536       if( gameInfo.variant == VariantXiangqi )
9537            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9538       if( gameInfo.variant == VariantShogi )
9539            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9540       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9541            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9542       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9543           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9544            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9545       if( gameInfo.variant == VariantCourier )
9546            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9547       if( gameInfo.variant == VariantSuper )
9548            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9549       if( gameInfo.variant == VariantGreat )
9550            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9551       if( gameInfo.variant == VariantSChess )
9552            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9553       if( gameInfo.variant == VariantGrand )
9554            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9555
9556       if(overruled) {
9557         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9558                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9559            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9560            if(StrStr(cps->variants, b) == NULL) {
9561                // specific sized variant not known, check if general sizing allowed
9562                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9563                    if(StrStr(cps->variants, "boardsize") == NULL) {
9564                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9565                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9566                        DisplayFatalError(buf, 0, 1);
9567                        return;
9568                    }
9569                    /* [HGM] here we really should compare with the maximum supported board size */
9570                }
9571            }
9572       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9573       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9574       SendToProgram(buf, cps);
9575     }
9576     currentlyInitializedVariant = gameInfo.variant;
9577
9578     /* [HGM] send opening position in FRC to first engine */
9579     if(setup) {
9580           SendToProgram("force\n", cps);
9581           SendBoard(cps, 0);
9582           /* engine is now in force mode! Set flag to wake it up after first move. */
9583           setboardSpoiledMachineBlack = 1;
9584     }
9585
9586     if (cps->sendICS) {
9587       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9588       SendToProgram(buf, cps);
9589     }
9590     cps->maybeThinking = FALSE;
9591     cps->offeredDraw = 0;
9592     if (!appData.icsActive) {
9593         SendTimeControl(cps, movesPerSession, timeControl,
9594                         timeIncrement, appData.searchDepth,
9595                         searchTime);
9596     }
9597     if (appData.showThinking
9598         // [HGM] thinking: four options require thinking output to be sent
9599         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9600                                 ) {
9601         SendToProgram("post\n", cps);
9602     }
9603     SendToProgram("hard\n", cps);
9604     if (!appData.ponderNextMove) {
9605         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9606            it without being sure what state we are in first.  "hard"
9607            is not a toggle, so that one is OK.
9608          */
9609         SendToProgram("easy\n", cps);
9610     }
9611     if (cps->usePing) {
9612       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9613       SendToProgram(buf, cps);
9614     }
9615     cps->initDone = TRUE;
9616     ClearEngineOutputPane(cps == &second);
9617 }
9618
9619
9620 void
9621 StartChessProgram(cps)
9622      ChessProgramState *cps;
9623 {
9624     char buf[MSG_SIZ];
9625     int err;
9626
9627     if (appData.noChessProgram) return;
9628     cps->initDone = FALSE;
9629
9630     if (strcmp(cps->host, "localhost") == 0) {
9631         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9632     } else if (*appData.remoteShell == NULLCHAR) {
9633         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9634     } else {
9635         if (*appData.remoteUser == NULLCHAR) {
9636           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9637                     cps->program);
9638         } else {
9639           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9640                     cps->host, appData.remoteUser, cps->program);
9641         }
9642         err = StartChildProcess(buf, "", &cps->pr);
9643     }
9644
9645     if (err != 0) {
9646       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9647         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9648         if(cps != &first) return;
9649         appData.noChessProgram = TRUE;
9650         ThawUI();
9651         SetNCPMode();
9652 //      DisplayFatalError(buf, err, 1);
9653 //      cps->pr = NoProc;
9654 //      cps->isr = NULL;
9655         return;
9656     }
9657
9658     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9659     if (cps->protocolVersion > 1) {
9660       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9661       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9662       cps->comboCnt = 0;  //                and values of combo boxes
9663       SendToProgram(buf, cps);
9664     } else {
9665       SendToProgram("xboard\n", cps);
9666     }
9667 }
9668
9669 void
9670 TwoMachinesEventIfReady P((void))
9671 {
9672   static int curMess = 0;
9673   if (first.lastPing != first.lastPong) {
9674     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9675     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9676     return;
9677   }
9678   if (second.lastPing != second.lastPong) {
9679     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9680     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9681     return;
9682   }
9683   DisplayMessage("", ""); curMess = 0;
9684   ThawUI();
9685   TwoMachinesEvent();
9686 }
9687
9688 char *
9689 MakeName(char *template)
9690 {
9691     time_t clock;
9692     struct tm *tm;
9693     static char buf[MSG_SIZ];
9694     char *p = buf;
9695     int i;
9696
9697     clock = time((time_t *)NULL);
9698     tm = localtime(&clock);
9699
9700     while(*p++ = *template++) if(p[-1] == '%') {
9701         switch(*template++) {
9702           case 0:   *p = 0; return buf;
9703           case 'Y': i = tm->tm_year+1900; break;
9704           case 'y': i = tm->tm_year-100; break;
9705           case 'M': i = tm->tm_mon+1; break;
9706           case 'd': i = tm->tm_mday; break;
9707           case 'h': i = tm->tm_hour; break;
9708           case 'm': i = tm->tm_min; break;
9709           case 's': i = tm->tm_sec; break;
9710           default:  i = 0;
9711         }
9712         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9713     }
9714     return buf;
9715 }
9716
9717 int
9718 CountPlayers(char *p)
9719 {
9720     int n = 0;
9721     while(p = strchr(p, '\n')) p++, n++; // count participants
9722     return n;
9723 }
9724
9725 FILE *
9726 WriteTourneyFile(char *results, FILE *f)
9727 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9728     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9729     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9730         // create a file with tournament description
9731         fprintf(f, "-participants {%s}\n", appData.participants);
9732         fprintf(f, "-seedBase %d\n", appData.seedBase);
9733         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9734         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9735         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9736         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9737         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9738         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9739         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9740         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9741         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9742         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9743         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9744         if(searchTime > 0)
9745                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9746         else {
9747                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9748                 fprintf(f, "-tc %s\n", appData.timeControl);
9749                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9750         }
9751         fprintf(f, "-results \"%s\"\n", results);
9752     }
9753     return f;
9754 }
9755
9756 #define MAXENGINES 1000
9757 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9758
9759 void Substitute(char *participants, int expunge)
9760 {
9761     int i, changed, changes=0, nPlayers=0;
9762     char *p, *q, *r, buf[MSG_SIZ];
9763     if(participants == NULL) return;
9764     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9765     r = p = participants; q = appData.participants;
9766     while(*p && *p == *q) {
9767         if(*p == '\n') r = p+1, nPlayers++;
9768         p++; q++;
9769     }
9770     if(*p) { // difference
9771         while(*p && *p++ != '\n');
9772         while(*q && *q++ != '\n');
9773       changed = nPlayers;
9774         changes = 1 + (strcmp(p, q) != 0);
9775     }
9776     if(changes == 1) { // a single engine mnemonic was changed
9777         q = r; while(*q) nPlayers += (*q++ == '\n');
9778         p = buf; while(*r && (*p = *r++) != '\n') p++;
9779         *p = NULLCHAR;
9780         NamesToList(firstChessProgramNames, command, mnemonic);
9781         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9782         if(mnemonic[i]) { // The substitute is valid
9783             FILE *f;
9784             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9785                 flock(fileno(f), LOCK_EX);
9786                 ParseArgsFromFile(f);
9787                 fseek(f, 0, SEEK_SET);
9788                 FREE(appData.participants); appData.participants = participants;
9789                 if(expunge) { // erase results of replaced engine
9790                     int len = strlen(appData.results), w, b, dummy;
9791                     for(i=0; i<len; i++) {
9792                         Pairing(i, nPlayers, &w, &b, &dummy);
9793                         if((w == changed || b == changed) && appData.results[i] == '*') {
9794                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9795                             fclose(f);
9796                             return;
9797                         }
9798                     }
9799                     for(i=0; i<len; i++) {
9800                         Pairing(i, nPlayers, &w, &b, &dummy);
9801                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9802                     }
9803                 }
9804                 WriteTourneyFile(appData.results, f);
9805                 fclose(f); // release lock
9806                 return;
9807             }
9808         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9809     }
9810     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9811     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9812     free(participants);
9813     return;
9814 }
9815
9816 int
9817 CreateTourney(char *name)
9818 {
9819         FILE *f;
9820         if(matchMode && strcmp(name, appData.tourneyFile)) {
9821              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9822         }
9823         if(name[0] == NULLCHAR) {
9824             if(appData.participants[0])
9825                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9826             return 0;
9827         }
9828         f = fopen(name, "r");
9829         if(f) { // file exists
9830             ASSIGN(appData.tourneyFile, name);
9831             ParseArgsFromFile(f); // parse it
9832         } else {
9833             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9834             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9835                 DisplayError(_("Not enough participants"), 0);
9836                 return 0;
9837             }
9838             ASSIGN(appData.tourneyFile, name);
9839             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9840             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9841         }
9842         fclose(f);
9843         appData.noChessProgram = FALSE;
9844         appData.clockMode = TRUE;
9845         SetGNUMode();
9846         return 1;
9847 }
9848
9849 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9850 {
9851     char buf[MSG_SIZ], *p, *q;
9852     int i=1;
9853     while(*names) {
9854         p = names; q = buf;
9855         while(*p && *p != '\n') *q++ = *p++;
9856         *q = 0;
9857         if(engineList[i]) free(engineList[i]);
9858         engineList[i] = strdup(buf);
9859         if(*p == '\n') p++;
9860         TidyProgramName(engineList[i], "localhost", buf);
9861         if(engineMnemonic[i]) free(engineMnemonic[i]);
9862         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9863             strcat(buf, " (");
9864             sscanf(q + 8, "%s", buf + strlen(buf));
9865             strcat(buf, ")");
9866         }
9867         engineMnemonic[i] = strdup(buf);
9868         names = p; i++;
9869       if(i > MAXENGINES - 2) break;
9870     }
9871     engineList[i] = engineMnemonic[i] = NULL;
9872 }
9873
9874 // following implemented as macro to avoid type limitations
9875 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9876
9877 void SwapEngines(int n)
9878 {   // swap settings for first engine and other engine (so far only some selected options)
9879     int h;
9880     char *p;
9881     if(n == 0) return;
9882     SWAP(directory, p)
9883     SWAP(chessProgram, p)
9884     SWAP(isUCI, h)
9885     SWAP(hasOwnBookUCI, h)
9886     SWAP(protocolVersion, h)
9887     SWAP(reuse, h)
9888     SWAP(scoreIsAbsolute, h)
9889     SWAP(timeOdds, h)
9890     SWAP(logo, p)
9891     SWAP(pgnName, p)
9892     SWAP(pvSAN, h)
9893 }
9894
9895 void
9896 SetPlayer(int player)
9897 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9898     int i;
9899     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9900     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9901     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9902     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9903     if(mnemonic[i]) {
9904         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9905         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9906         ParseArgsFromString(buf);
9907     }
9908     free(engineName);
9909 }
9910
9911 int
9912 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9913 {   // determine players from game number
9914     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9915
9916     if(appData.tourneyType == 0) {
9917         roundsPerCycle = (nPlayers - 1) | 1;
9918         pairingsPerRound = nPlayers / 2;
9919     } else if(appData.tourneyType > 0) {
9920         roundsPerCycle = nPlayers - appData.tourneyType;
9921         pairingsPerRound = appData.tourneyType;
9922     }
9923     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9924     gamesPerCycle = gamesPerRound * roundsPerCycle;
9925     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9926     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9927     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9928     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9929     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9930     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9931
9932     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9933     if(appData.roundSync) *syncInterval = gamesPerRound;
9934
9935     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9936
9937     if(appData.tourneyType == 0) {
9938         if(curPairing == (nPlayers-1)/2 ) {
9939             *whitePlayer = curRound;
9940             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9941         } else {
9942             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9943             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9944             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9945             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9946         }
9947     } else if(appData.tourneyType > 0) {
9948         *whitePlayer = curPairing;
9949         *blackPlayer = curRound + appData.tourneyType;
9950     }
9951
9952     // take care of white/black alternation per round. 
9953     // For cycles and games this is already taken care of by default, derived from matchGame!
9954     return curRound & 1;
9955 }
9956
9957 int
9958 NextTourneyGame(int nr, int *swapColors)
9959 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9960     char *p, *q;
9961     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9962     FILE *tf;
9963     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9964     tf = fopen(appData.tourneyFile, "r");
9965     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9966     ParseArgsFromFile(tf); fclose(tf);
9967     InitTimeControls(); // TC might be altered from tourney file
9968
9969     nPlayers = CountPlayers(appData.participants); // count participants
9970     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9971     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9972
9973     if(syncInterval) {
9974         p = q = appData.results;
9975         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9976         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9977             DisplayMessage(_("Waiting for other game(s)"),"");
9978             waitingForGame = TRUE;
9979             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9980             return 0;
9981         }
9982         waitingForGame = FALSE;
9983     }
9984
9985     if(appData.tourneyType < 0) {
9986         if(nr>=0 && !pairingReceived) {
9987             char buf[1<<16];
9988             if(pairing.pr == NoProc) {
9989                 if(!appData.pairingEngine[0]) {
9990                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9991                     return 0;
9992                 }
9993                 StartChessProgram(&pairing); // starts the pairing engine
9994             }
9995             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9996             SendToProgram(buf, &pairing);
9997             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9998             SendToProgram(buf, &pairing);
9999             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10000         }
10001         pairingReceived = 0;                              // ... so we continue here 
10002         *swapColors = 0;
10003         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10004         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10005         matchGame = 1; roundNr = nr / syncInterval + 1;
10006     }
10007
10008     if(first.pr != NoProc) return 1; // engines already loaded
10009
10010     // redefine engines, engine dir, etc.
10011     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10012     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10013     SwapEngines(1);
10014     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10015     SwapEngines(1);         // and make that valid for second engine by swapping
10016     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10017     InitEngine(&second, 1);
10018     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10019     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10020     return 1;
10021 }
10022
10023 void
10024 NextMatchGame()
10025 {   // performs game initialization that does not invoke engines, and then tries to start the game
10026     int firstWhite, swapColors = 0;
10027     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10028     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10029     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10030     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10031     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10032     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10033     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10034     Reset(FALSE, first.pr != NoProc);
10035     appData.noChessProgram = FALSE;
10036     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
10037     TwoMachinesEvent();
10038 }
10039
10040 void UserAdjudicationEvent( int result )
10041 {
10042     ChessMove gameResult = GameIsDrawn;
10043
10044     if( result > 0 ) {
10045         gameResult = WhiteWins;
10046     }
10047     else if( result < 0 ) {
10048         gameResult = BlackWins;
10049     }
10050
10051     if( gameMode == TwoMachinesPlay ) {
10052         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10053     }
10054 }
10055
10056
10057 // [HGM] save: calculate checksum of game to make games easily identifiable
10058 int StringCheckSum(char *s)
10059 {
10060         int i = 0;
10061         if(s==NULL) return 0;
10062         while(*s) i = i*259 + *s++;
10063         return i;
10064 }
10065
10066 int GameCheckSum()
10067 {
10068         int i, sum=0;
10069         for(i=backwardMostMove; i<forwardMostMove; i++) {
10070                 sum += pvInfoList[i].depth;
10071                 sum += StringCheckSum(parseList[i]);
10072                 sum += StringCheckSum(commentList[i]);
10073                 sum *= 261;
10074         }
10075         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10076         return sum + StringCheckSum(commentList[i]);
10077 } // end of save patch
10078
10079 void
10080 GameEnds(result, resultDetails, whosays)
10081      ChessMove result;
10082      char *resultDetails;
10083      int whosays;
10084 {
10085     GameMode nextGameMode;
10086     int isIcsGame;
10087     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10088
10089     if(endingGame) return; /* [HGM] crash: forbid recursion */
10090     endingGame = 1;
10091     if(twoBoards) { // [HGM] dual: switch back to one board
10092         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10093         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10094     }
10095     if (appData.debugMode) {
10096       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10097               result, resultDetails ? resultDetails : "(null)", whosays);
10098     }
10099
10100     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10101
10102     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10103         /* If we are playing on ICS, the server decides when the
10104            game is over, but the engine can offer to draw, claim
10105            a draw, or resign.
10106          */
10107 #if ZIPPY
10108         if (appData.zippyPlay && first.initDone) {
10109             if (result == GameIsDrawn) {
10110                 /* In case draw still needs to be claimed */
10111                 SendToICS(ics_prefix);
10112                 SendToICS("draw\n");
10113             } else if (StrCaseStr(resultDetails, "resign")) {
10114                 SendToICS(ics_prefix);
10115                 SendToICS("resign\n");
10116             }
10117         }
10118 #endif
10119         endingGame = 0; /* [HGM] crash */
10120         return;
10121     }
10122
10123     /* If we're loading the game from a file, stop */
10124     if (whosays == GE_FILE) {
10125       (void) StopLoadGameTimer();
10126       gameFileFP = NULL;
10127     }
10128
10129     /* Cancel draw offers */
10130     first.offeredDraw = second.offeredDraw = 0;
10131
10132     /* If this is an ICS game, only ICS can really say it's done;
10133        if not, anyone can. */
10134     isIcsGame = (gameMode == IcsPlayingWhite ||
10135                  gameMode == IcsPlayingBlack ||
10136                  gameMode == IcsObserving    ||
10137                  gameMode == IcsExamining);
10138
10139     if (!isIcsGame || whosays == GE_ICS) {
10140         /* OK -- not an ICS game, or ICS said it was done */
10141         StopClocks();
10142         if (!isIcsGame && !appData.noChessProgram)
10143           SetUserThinkingEnables();
10144
10145         /* [HGM] if a machine claims the game end we verify this claim */
10146         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10147             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10148                 char claimer;
10149                 ChessMove trueResult = (ChessMove) -1;
10150
10151                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10152                                             first.twoMachinesColor[0] :
10153                                             second.twoMachinesColor[0] ;
10154
10155                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10156                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10157                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10158                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10159                 } else
10160                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10161                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10162                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10163                 } else
10164                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10165                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10166                 }
10167
10168                 // now verify win claims, but not in drop games, as we don't understand those yet
10169                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10170                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10171                     (result == WhiteWins && claimer == 'w' ||
10172                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10173                       if (appData.debugMode) {
10174                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10175                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10176                       }
10177                       if(result != trueResult) {
10178                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10179                               result = claimer == 'w' ? BlackWins : WhiteWins;
10180                               resultDetails = buf;
10181                       }
10182                 } else
10183                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10184                     && (forwardMostMove <= backwardMostMove ||
10185                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10186                         (claimer=='b')==(forwardMostMove&1))
10187                                                                                   ) {
10188                       /* [HGM] verify: draws that were not flagged are false claims */
10189                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10190                       result = claimer == 'w' ? BlackWins : WhiteWins;
10191                       resultDetails = buf;
10192                 }
10193                 /* (Claiming a loss is accepted no questions asked!) */
10194             }
10195             /* [HGM] bare: don't allow bare King to win */
10196             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10197                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10198                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10199                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10200                && result != GameIsDrawn)
10201             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10202                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10203                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10204                         if(p >= 0 && p <= (int)WhiteKing) k++;
10205                 }
10206                 if (appData.debugMode) {
10207                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10208                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10209                 }
10210                 if(k <= 1) {
10211                         result = GameIsDrawn;
10212                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10213                         resultDetails = buf;
10214                 }
10215             }
10216         }
10217
10218
10219         if(serverMoves != NULL && !loadFlag) { char c = '=';
10220             if(result==WhiteWins) c = '+';
10221             if(result==BlackWins) c = '-';
10222             if(resultDetails != NULL)
10223                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10224         }
10225         if (resultDetails != NULL) {
10226             gameInfo.result = result;
10227             gameInfo.resultDetails = StrSave(resultDetails);
10228
10229             /* display last move only if game was not loaded from file */
10230             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10231                 DisplayMove(currentMove - 1);
10232
10233             if (forwardMostMove != 0) {
10234                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10235                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10236                                                                 ) {
10237                     if (*appData.saveGameFile != NULLCHAR) {
10238                         SaveGameToFile(appData.saveGameFile, TRUE);
10239                     } else if (appData.autoSaveGames) {
10240                         AutoSaveGame();
10241                     }
10242                     if (*appData.savePositionFile != NULLCHAR) {
10243                         SavePositionToFile(appData.savePositionFile);
10244                     }
10245                 }
10246             }
10247
10248             /* Tell program how game ended in case it is learning */
10249             /* [HGM] Moved this to after saving the PGN, just in case */
10250             /* engine died and we got here through time loss. In that */
10251             /* case we will get a fatal error writing the pipe, which */
10252             /* would otherwise lose us the PGN.                       */
10253             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10254             /* output during GameEnds should never be fatal anymore   */
10255             if (gameMode == MachinePlaysWhite ||
10256                 gameMode == MachinePlaysBlack ||
10257                 gameMode == TwoMachinesPlay ||
10258                 gameMode == IcsPlayingWhite ||
10259                 gameMode == IcsPlayingBlack ||
10260                 gameMode == BeginningOfGame) {
10261                 char buf[MSG_SIZ];
10262                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10263                         resultDetails);
10264                 if (first.pr != NoProc) {
10265                     SendToProgram(buf, &first);
10266                 }
10267                 if (second.pr != NoProc &&
10268                     gameMode == TwoMachinesPlay) {
10269                     SendToProgram(buf, &second);
10270                 }
10271             }
10272         }
10273
10274         if (appData.icsActive) {
10275             if (appData.quietPlay &&
10276                 (gameMode == IcsPlayingWhite ||
10277                  gameMode == IcsPlayingBlack)) {
10278                 SendToICS(ics_prefix);
10279                 SendToICS("set shout 1\n");
10280             }
10281             nextGameMode = IcsIdle;
10282             ics_user_moved = FALSE;
10283             /* clean up premove.  It's ugly when the game has ended and the
10284              * premove highlights are still on the board.
10285              */
10286             if (gotPremove) {
10287               gotPremove = FALSE;
10288               ClearPremoveHighlights();
10289               DrawPosition(FALSE, boards[currentMove]);
10290             }
10291             if (whosays == GE_ICS) {
10292                 switch (result) {
10293                 case WhiteWins:
10294                     if (gameMode == IcsPlayingWhite)
10295                         PlayIcsWinSound();
10296                     else if(gameMode == IcsPlayingBlack)
10297                         PlayIcsLossSound();
10298                     break;
10299                 case BlackWins:
10300                     if (gameMode == IcsPlayingBlack)
10301                         PlayIcsWinSound();
10302                     else if(gameMode == IcsPlayingWhite)
10303                         PlayIcsLossSound();
10304                     break;
10305                 case GameIsDrawn:
10306                     PlayIcsDrawSound();
10307                     break;
10308                 default:
10309                     PlayIcsUnfinishedSound();
10310                 }
10311             }
10312         } else if (gameMode == EditGame ||
10313                    gameMode == PlayFromGameFile ||
10314                    gameMode == AnalyzeMode ||
10315                    gameMode == AnalyzeFile) {
10316             nextGameMode = gameMode;
10317         } else {
10318             nextGameMode = EndOfGame;
10319         }
10320         pausing = FALSE;
10321         ModeHighlight();
10322     } else {
10323         nextGameMode = gameMode;
10324     }
10325
10326     if (appData.noChessProgram) {
10327         gameMode = nextGameMode;
10328         ModeHighlight();
10329         endingGame = 0; /* [HGM] crash */
10330         return;
10331     }
10332
10333     if (first.reuse) {
10334         /* Put first chess program into idle state */
10335         if (first.pr != NoProc &&
10336             (gameMode == MachinePlaysWhite ||
10337              gameMode == MachinePlaysBlack ||
10338              gameMode == TwoMachinesPlay ||
10339              gameMode == IcsPlayingWhite ||
10340              gameMode == IcsPlayingBlack ||
10341              gameMode == BeginningOfGame)) {
10342             SendToProgram("force\n", &first);
10343             if (first.usePing) {
10344               char buf[MSG_SIZ];
10345               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10346               SendToProgram(buf, &first);
10347             }
10348         }
10349     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10350         /* Kill off first chess program */
10351         if (first.isr != NULL)
10352           RemoveInputSource(first.isr);
10353         first.isr = NULL;
10354
10355         if (first.pr != NoProc) {
10356             ExitAnalyzeMode();
10357             DoSleep( appData.delayBeforeQuit );
10358             SendToProgram("quit\n", &first);
10359             DoSleep( appData.delayAfterQuit );
10360             DestroyChildProcess(first.pr, first.useSigterm);
10361         }
10362         first.pr = NoProc;
10363     }
10364     if (second.reuse) {
10365         /* Put second chess program into idle state */
10366         if (second.pr != NoProc &&
10367             gameMode == TwoMachinesPlay) {
10368             SendToProgram("force\n", &second);
10369             if (second.usePing) {
10370               char buf[MSG_SIZ];
10371               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10372               SendToProgram(buf, &second);
10373             }
10374         }
10375     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10376         /* Kill off second chess program */
10377         if (second.isr != NULL)
10378           RemoveInputSource(second.isr);
10379         second.isr = NULL;
10380
10381         if (second.pr != NoProc) {
10382             DoSleep( appData.delayBeforeQuit );
10383             SendToProgram("quit\n", &second);
10384             DoSleep( appData.delayAfterQuit );
10385             DestroyChildProcess(second.pr, second.useSigterm);
10386         }
10387         second.pr = NoProc;
10388     }
10389
10390     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10391         char resChar = '=';
10392         switch (result) {
10393         case WhiteWins:
10394           resChar = '+';
10395           if (first.twoMachinesColor[0] == 'w') {
10396             first.matchWins++;
10397           } else {
10398             second.matchWins++;
10399           }
10400           break;
10401         case BlackWins:
10402           resChar = '-';
10403           if (first.twoMachinesColor[0] == 'b') {
10404             first.matchWins++;
10405           } else {
10406             second.matchWins++;
10407           }
10408           break;
10409         case GameUnfinished:
10410           resChar = ' ';
10411         default:
10412           break;
10413         }
10414
10415         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10416         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10417             ReserveGame(nextGame, resChar); // sets nextGame
10418             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10419             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10420         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10421
10422         if (nextGame <= appData.matchGames && !abortMatch) {
10423             gameMode = nextGameMode;
10424             matchGame = nextGame; // this will be overruled in tourney mode!
10425             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10426             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10427             endingGame = 0; /* [HGM] crash */
10428             return;
10429         } else {
10430             gameMode = nextGameMode;
10431             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10432                      first.tidy, second.tidy,
10433                      first.matchWins, second.matchWins,
10434                      appData.matchGames - (first.matchWins + second.matchWins));
10435             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10436             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10437             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10438                 first.twoMachinesColor = "black\n";
10439                 second.twoMachinesColor = "white\n";
10440             } else {
10441                 first.twoMachinesColor = "white\n";
10442                 second.twoMachinesColor = "black\n";
10443             }
10444         }
10445     }
10446     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10447         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10448       ExitAnalyzeMode();
10449     gameMode = nextGameMode;
10450     ModeHighlight();
10451     endingGame = 0;  /* [HGM] crash */
10452     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10453         if(matchMode == TRUE) { // match through command line: exit with or without popup
10454             if(ranking) {
10455                 ToNrEvent(forwardMostMove);
10456                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10457                 else ExitEvent(0);
10458             } else DisplayFatalError(buf, 0, 0);
10459         } else { // match through menu; just stop, with or without popup
10460             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10461             ModeHighlight();
10462             if(ranking){
10463                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10464             } else DisplayNote(buf);
10465       }
10466       if(ranking) free(ranking);
10467     }
10468 }
10469
10470 /* Assumes program was just initialized (initString sent).
10471    Leaves program in force mode. */
10472 void
10473 FeedMovesToProgram(cps, upto)
10474      ChessProgramState *cps;
10475      int upto;
10476 {
10477     int i;
10478
10479     if (appData.debugMode)
10480       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10481               startedFromSetupPosition ? "position and " : "",
10482               backwardMostMove, upto, cps->which);
10483     if(currentlyInitializedVariant != gameInfo.variant) {
10484       char buf[MSG_SIZ];
10485         // [HGM] variantswitch: make engine aware of new variant
10486         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10487                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10488         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10489         SendToProgram(buf, cps);
10490         currentlyInitializedVariant = gameInfo.variant;
10491     }
10492     SendToProgram("force\n", cps);
10493     if (startedFromSetupPosition) {
10494         SendBoard(cps, backwardMostMove);
10495     if (appData.debugMode) {
10496         fprintf(debugFP, "feedMoves\n");
10497     }
10498     }
10499     for (i = backwardMostMove; i < upto; i++) {
10500         SendMoveToProgram(i, cps);
10501     }
10502 }
10503
10504
10505 int
10506 ResurrectChessProgram()
10507 {
10508      /* The chess program may have exited.
10509         If so, restart it and feed it all the moves made so far. */
10510     static int doInit = 0;
10511
10512     if (appData.noChessProgram) return 1;
10513
10514     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10515         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10516         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10517         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10518     } else {
10519         if (first.pr != NoProc) return 1;
10520         StartChessProgram(&first);
10521     }
10522     InitChessProgram(&first, FALSE);
10523     FeedMovesToProgram(&first, currentMove);
10524
10525     if (!first.sendTime) {
10526         /* can't tell gnuchess what its clock should read,
10527            so we bow to its notion. */
10528         ResetClocks();
10529         timeRemaining[0][currentMove] = whiteTimeRemaining;
10530         timeRemaining[1][currentMove] = blackTimeRemaining;
10531     }
10532
10533     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10534                 appData.icsEngineAnalyze) && first.analysisSupport) {
10535       SendToProgram("analyze\n", &first);
10536       first.analyzing = TRUE;
10537     }
10538     return 1;
10539 }
10540
10541 /*
10542  * Button procedures
10543  */
10544 void
10545 Reset(redraw, init)
10546      int redraw, init;
10547 {
10548     int i;
10549
10550     if (appData.debugMode) {
10551         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10552                 redraw, init, gameMode);
10553     }
10554     CleanupTail(); // [HGM] vari: delete any stored variations
10555     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10556     pausing = pauseExamInvalid = FALSE;
10557     startedFromSetupPosition = blackPlaysFirst = FALSE;
10558     firstMove = TRUE;
10559     whiteFlag = blackFlag = FALSE;
10560     userOfferedDraw = FALSE;
10561     hintRequested = bookRequested = FALSE;
10562     first.maybeThinking = FALSE;
10563     second.maybeThinking = FALSE;
10564     first.bookSuspend = FALSE; // [HGM] book
10565     second.bookSuspend = FALSE;
10566     thinkOutput[0] = NULLCHAR;
10567     lastHint[0] = NULLCHAR;
10568     ClearGameInfo(&gameInfo);
10569     gameInfo.variant = StringToVariant(appData.variant);
10570     ics_user_moved = ics_clock_paused = FALSE;
10571     ics_getting_history = H_FALSE;
10572     ics_gamenum = -1;
10573     white_holding[0] = black_holding[0] = NULLCHAR;
10574     ClearProgramStats();
10575     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10576
10577     ResetFrontEnd();
10578     ClearHighlights();
10579     flipView = appData.flipView;
10580     ClearPremoveHighlights();
10581     gotPremove = FALSE;
10582     alarmSounded = FALSE;
10583
10584     GameEnds(EndOfFile, NULL, GE_PLAYER);
10585     if(appData.serverMovesName != NULL) {
10586         /* [HGM] prepare to make moves file for broadcasting */
10587         clock_t t = clock();
10588         if(serverMoves != NULL) fclose(serverMoves);
10589         serverMoves = fopen(appData.serverMovesName, "r");
10590         if(serverMoves != NULL) {
10591             fclose(serverMoves);
10592             /* delay 15 sec before overwriting, so all clients can see end */
10593             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10594         }
10595         serverMoves = fopen(appData.serverMovesName, "w");
10596     }
10597
10598     ExitAnalyzeMode();
10599     gameMode = BeginningOfGame;
10600     ModeHighlight();
10601     if(appData.icsActive) gameInfo.variant = VariantNormal;
10602     currentMove = forwardMostMove = backwardMostMove = 0;
10603     InitPosition(redraw);
10604     for (i = 0; i < MAX_MOVES; i++) {
10605         if (commentList[i] != NULL) {
10606             free(commentList[i]);
10607             commentList[i] = NULL;
10608         }
10609     }
10610     ResetClocks();
10611     timeRemaining[0][0] = whiteTimeRemaining;
10612     timeRemaining[1][0] = blackTimeRemaining;
10613
10614     if (first.pr == NULL) {
10615         StartChessProgram(&first);
10616     }
10617     if (init) {
10618             InitChessProgram(&first, startedFromSetupPosition);
10619     }
10620     DisplayTitle("");
10621     DisplayMessage("", "");
10622     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10623     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10624 }
10625
10626 void
10627 AutoPlayGameLoop()
10628 {
10629     for (;;) {
10630         if (!AutoPlayOneMove())
10631           return;
10632         if (matchMode || appData.timeDelay == 0)
10633           continue;
10634         if (appData.timeDelay < 0)
10635           return;
10636         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10637         break;
10638     }
10639 }
10640
10641
10642 int
10643 AutoPlayOneMove()
10644 {
10645     int fromX, fromY, toX, toY;
10646
10647     if (appData.debugMode) {
10648       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10649     }
10650
10651     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10652       return FALSE;
10653
10654     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10655       pvInfoList[currentMove].depth = programStats.depth;
10656       pvInfoList[currentMove].score = programStats.score;
10657       pvInfoList[currentMove].time  = 0;
10658       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10659     }
10660
10661     if (currentMove >= forwardMostMove) {
10662       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10663 //      gameMode = EndOfGame;
10664 //      ModeHighlight();
10665
10666       /* [AS] Clear current move marker at the end of a game */
10667       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10668
10669       return FALSE;
10670     }
10671
10672     toX = moveList[currentMove][2] - AAA;
10673     toY = moveList[currentMove][3] - ONE;
10674
10675     if (moveList[currentMove][1] == '@') {
10676         if (appData.highlightLastMove) {
10677             SetHighlights(-1, -1, toX, toY);
10678         }
10679     } else {
10680         fromX = moveList[currentMove][0] - AAA;
10681         fromY = moveList[currentMove][1] - ONE;
10682
10683         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10684
10685         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10686
10687         if (appData.highlightLastMove) {
10688             SetHighlights(fromX, fromY, toX, toY);
10689         }
10690     }
10691     DisplayMove(currentMove);
10692     SendMoveToProgram(currentMove++, &first);
10693     DisplayBothClocks();
10694     DrawPosition(FALSE, boards[currentMove]);
10695     // [HGM] PV info: always display, routine tests if empty
10696     DisplayComment(currentMove - 1, commentList[currentMove]);
10697     return TRUE;
10698 }
10699
10700
10701 int
10702 LoadGameOneMove(readAhead)
10703      ChessMove readAhead;
10704 {
10705     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10706     char promoChar = NULLCHAR;
10707     ChessMove moveType;
10708     char move[MSG_SIZ];
10709     char *p, *q;
10710
10711     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10712         gameMode != AnalyzeMode && gameMode != Training) {
10713         gameFileFP = NULL;
10714         return FALSE;
10715     }
10716
10717     yyboardindex = forwardMostMove;
10718     if (readAhead != EndOfFile) {
10719       moveType = readAhead;
10720     } else {
10721       if (gameFileFP == NULL)
10722           return FALSE;
10723       moveType = (ChessMove) Myylex();
10724     }
10725
10726     done = FALSE;
10727     switch (moveType) {
10728       case Comment:
10729         if (appData.debugMode)
10730           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10731         p = yy_text;
10732
10733         /* append the comment but don't display it */
10734         AppendComment(currentMove, p, FALSE);
10735         return TRUE;
10736
10737       case WhiteCapturesEnPassant:
10738       case BlackCapturesEnPassant:
10739       case WhitePromotion:
10740       case BlackPromotion:
10741       case WhiteNonPromotion:
10742       case BlackNonPromotion:
10743       case NormalMove:
10744       case WhiteKingSideCastle:
10745       case WhiteQueenSideCastle:
10746       case BlackKingSideCastle:
10747       case BlackQueenSideCastle:
10748       case WhiteKingSideCastleWild:
10749       case WhiteQueenSideCastleWild:
10750       case BlackKingSideCastleWild:
10751       case BlackQueenSideCastleWild:
10752       /* PUSH Fabien */
10753       case WhiteHSideCastleFR:
10754       case WhiteASideCastleFR:
10755       case BlackHSideCastleFR:
10756       case BlackASideCastleFR:
10757       /* POP Fabien */
10758         if (appData.debugMode)
10759           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10760         fromX = currentMoveString[0] - AAA;
10761         fromY = currentMoveString[1] - ONE;
10762         toX = currentMoveString[2] - AAA;
10763         toY = currentMoveString[3] - ONE;
10764         promoChar = currentMoveString[4];
10765         break;
10766
10767       case WhiteDrop:
10768       case BlackDrop:
10769         if (appData.debugMode)
10770           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10771         fromX = moveType == WhiteDrop ?
10772           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10773         (int) CharToPiece(ToLower(currentMoveString[0]));
10774         fromY = DROP_RANK;
10775         toX = currentMoveString[2] - AAA;
10776         toY = currentMoveString[3] - ONE;
10777         break;
10778
10779       case WhiteWins:
10780       case BlackWins:
10781       case GameIsDrawn:
10782       case GameUnfinished:
10783         if (appData.debugMode)
10784           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10785         p = strchr(yy_text, '{');
10786         if (p == NULL) p = strchr(yy_text, '(');
10787         if (p == NULL) {
10788             p = yy_text;
10789             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10790         } else {
10791             q = strchr(p, *p == '{' ? '}' : ')');
10792             if (q != NULL) *q = NULLCHAR;
10793             p++;
10794         }
10795         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10796         GameEnds(moveType, p, GE_FILE);
10797         done = TRUE;
10798         if (cmailMsgLoaded) {
10799             ClearHighlights();
10800             flipView = WhiteOnMove(currentMove);
10801             if (moveType == GameUnfinished) flipView = !flipView;
10802             if (appData.debugMode)
10803               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10804         }
10805         break;
10806
10807       case EndOfFile:
10808         if (appData.debugMode)
10809           fprintf(debugFP, "Parser hit end of file\n");
10810         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10811           case MT_NONE:
10812           case MT_CHECK:
10813             break;
10814           case MT_CHECKMATE:
10815           case MT_STAINMATE:
10816             if (WhiteOnMove(currentMove)) {
10817                 GameEnds(BlackWins, "Black mates", GE_FILE);
10818             } else {
10819                 GameEnds(WhiteWins, "White mates", GE_FILE);
10820             }
10821             break;
10822           case MT_STALEMATE:
10823             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10824             break;
10825         }
10826         done = TRUE;
10827         break;
10828
10829       case MoveNumberOne:
10830         if (lastLoadGameStart == GNUChessGame) {
10831             /* GNUChessGames have numbers, but they aren't move numbers */
10832             if (appData.debugMode)
10833               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10834                       yy_text, (int) moveType);
10835             return LoadGameOneMove(EndOfFile); /* tail recursion */
10836         }
10837         /* else fall thru */
10838
10839       case XBoardGame:
10840       case GNUChessGame:
10841       case PGNTag:
10842         /* Reached start of next game in file */
10843         if (appData.debugMode)
10844           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10845         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10846           case MT_NONE:
10847           case MT_CHECK:
10848             break;
10849           case MT_CHECKMATE:
10850           case MT_STAINMATE:
10851             if (WhiteOnMove(currentMove)) {
10852                 GameEnds(BlackWins, "Black mates", GE_FILE);
10853             } else {
10854                 GameEnds(WhiteWins, "White mates", GE_FILE);
10855             }
10856             break;
10857           case MT_STALEMATE:
10858             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10859             break;
10860         }
10861         done = TRUE;
10862         break;
10863
10864       case PositionDiagram:     /* should not happen; ignore */
10865       case ElapsedTime:         /* ignore */
10866       case NAG:                 /* ignore */
10867         if (appData.debugMode)
10868           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10869                   yy_text, (int) moveType);
10870         return LoadGameOneMove(EndOfFile); /* tail recursion */
10871
10872       case IllegalMove:
10873         if (appData.testLegality) {
10874             if (appData.debugMode)
10875               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10876             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10877                     (forwardMostMove / 2) + 1,
10878                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10879             DisplayError(move, 0);
10880             done = TRUE;
10881         } else {
10882             if (appData.debugMode)
10883               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10884                       yy_text, currentMoveString);
10885             fromX = currentMoveString[0] - AAA;
10886             fromY = currentMoveString[1] - ONE;
10887             toX = currentMoveString[2] - AAA;
10888             toY = currentMoveString[3] - ONE;
10889             promoChar = currentMoveString[4];
10890         }
10891         break;
10892
10893       case AmbiguousMove:
10894         if (appData.debugMode)
10895           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10896         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10897                 (forwardMostMove / 2) + 1,
10898                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10899         DisplayError(move, 0);
10900         done = TRUE;
10901         break;
10902
10903       default:
10904       case ImpossibleMove:
10905         if (appData.debugMode)
10906           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10907         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10908                 (forwardMostMove / 2) + 1,
10909                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10910         DisplayError(move, 0);
10911         done = TRUE;
10912         break;
10913     }
10914
10915     if (done) {
10916         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10917             DrawPosition(FALSE, boards[currentMove]);
10918             DisplayBothClocks();
10919             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10920               DisplayComment(currentMove - 1, commentList[currentMove]);
10921         }
10922         (void) StopLoadGameTimer();
10923         gameFileFP = NULL;
10924         cmailOldMove = forwardMostMove;
10925         return FALSE;
10926     } else {
10927         /* currentMoveString is set as a side-effect of yylex */
10928
10929         thinkOutput[0] = NULLCHAR;
10930         MakeMove(fromX, fromY, toX, toY, promoChar);
10931         currentMove = forwardMostMove;
10932         return TRUE;
10933     }
10934 }
10935
10936 /* Load the nth game from the given file */
10937 int
10938 LoadGameFromFile(filename, n, title, useList)
10939      char *filename;
10940      int n;
10941      char *title;
10942      /*Boolean*/ int useList;
10943 {
10944     FILE *f;
10945     char buf[MSG_SIZ];
10946
10947     if (strcmp(filename, "-") == 0) {
10948         f = stdin;
10949         title = "stdin";
10950     } else {
10951         f = fopen(filename, "rb");
10952         if (f == NULL) {
10953           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10954             DisplayError(buf, errno);
10955             return FALSE;
10956         }
10957     }
10958     if (fseek(f, 0, 0) == -1) {
10959         /* f is not seekable; probably a pipe */
10960         useList = FALSE;
10961     }
10962     if (useList && n == 0) {
10963         int error = GameListBuild(f);
10964         if (error) {
10965             DisplayError(_("Cannot build game list"), error);
10966         } else if (!ListEmpty(&gameList) &&
10967                    ((ListGame *) gameList.tailPred)->number > 1) {
10968             GameListPopUp(f, title);
10969             return TRUE;
10970         }
10971         GameListDestroy();
10972         n = 1;
10973     }
10974     if (n == 0) n = 1;
10975     return LoadGame(f, n, title, FALSE);
10976 }
10977
10978
10979 void
10980 MakeRegisteredMove()
10981 {
10982     int fromX, fromY, toX, toY;
10983     char promoChar;
10984     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10985         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10986           case CMAIL_MOVE:
10987           case CMAIL_DRAW:
10988             if (appData.debugMode)
10989               fprintf(debugFP, "Restoring %s for game %d\n",
10990                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10991
10992             thinkOutput[0] = NULLCHAR;
10993             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10994             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10995             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10996             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10997             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10998             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10999             MakeMove(fromX, fromY, toX, toY, promoChar);
11000             ShowMove(fromX, fromY, toX, toY);
11001
11002             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11003               case MT_NONE:
11004               case MT_CHECK:
11005                 break;
11006
11007               case MT_CHECKMATE:
11008               case MT_STAINMATE:
11009                 if (WhiteOnMove(currentMove)) {
11010                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11011                 } else {
11012                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11013                 }
11014                 break;
11015
11016               case MT_STALEMATE:
11017                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11018                 break;
11019             }
11020
11021             break;
11022
11023           case CMAIL_RESIGN:
11024             if (WhiteOnMove(currentMove)) {
11025                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11026             } else {
11027                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11028             }
11029             break;
11030
11031           case CMAIL_ACCEPT:
11032             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11033             break;
11034
11035           default:
11036             break;
11037         }
11038     }
11039
11040     return;
11041 }
11042
11043 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11044 int
11045 CmailLoadGame(f, gameNumber, title, useList)
11046      FILE *f;
11047      int gameNumber;
11048      char *title;
11049      int useList;
11050 {
11051     int retVal;
11052
11053     if (gameNumber > nCmailGames) {
11054         DisplayError(_("No more games in this message"), 0);
11055         return FALSE;
11056     }
11057     if (f == lastLoadGameFP) {
11058         int offset = gameNumber - lastLoadGameNumber;
11059         if (offset == 0) {
11060             cmailMsg[0] = NULLCHAR;
11061             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11062                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11063                 nCmailMovesRegistered--;
11064             }
11065             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11066             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11067                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11068             }
11069         } else {
11070             if (! RegisterMove()) return FALSE;
11071         }
11072     }
11073
11074     retVal = LoadGame(f, gameNumber, title, useList);
11075
11076     /* Make move registered during previous look at this game, if any */
11077     MakeRegisteredMove();
11078
11079     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11080         commentList[currentMove]
11081           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11082         DisplayComment(currentMove - 1, commentList[currentMove]);
11083     }
11084
11085     return retVal;
11086 }
11087
11088 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11089 int
11090 ReloadGame(offset)
11091      int offset;
11092 {
11093     int gameNumber = lastLoadGameNumber + offset;
11094     if (lastLoadGameFP == NULL) {
11095         DisplayError(_("No game has been loaded yet"), 0);
11096         return FALSE;
11097     }
11098     if (gameNumber <= 0) {
11099         DisplayError(_("Can't back up any further"), 0);
11100         return FALSE;
11101     }
11102     if (cmailMsgLoaded) {
11103         return CmailLoadGame(lastLoadGameFP, gameNumber,
11104                              lastLoadGameTitle, lastLoadGameUseList);
11105     } else {
11106         return LoadGame(lastLoadGameFP, gameNumber,
11107                         lastLoadGameTitle, lastLoadGameUseList);
11108     }
11109 }
11110
11111 int keys[EmptySquare+1];
11112
11113 int
11114 PositionMatches(Board b1, Board b2)
11115 {
11116     int r, f, sum=0;
11117     switch(appData.searchMode) {
11118         case 1: return CompareWithRights(b1, b2);
11119         case 2:
11120             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11121                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11122             }
11123             return TRUE;
11124         case 3:
11125             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11126               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11127                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11128             }
11129             return sum==0;
11130         case 4:
11131             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11132                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11133             }
11134             return sum==0;
11135     }
11136     return TRUE;
11137 }
11138
11139 GameInfo dummyInfo;
11140
11141 int GameContainsPosition(FILE *f, ListGame *lg)
11142 {
11143     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11144     int fromX, fromY, toX, toY;
11145     char promoChar;
11146     static int initDone=FALSE;
11147
11148     if(!initDone) {
11149         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11150         initDone = TRUE;
11151     }
11152     dummyInfo.variant = VariantNormal;
11153     FREE(dummyInfo.fen); dummyInfo.fen = NULL;
11154     dummyInfo.whiteRating = 0;
11155     dummyInfo.blackRating = 0;
11156     FREE(dummyInfo.date); dummyInfo.date = NULL;
11157     fseek(f, lg->offset, 0);
11158     yynewfile(f);
11159     CopyBoard(boards[scratch], initialPosition); // default start position
11160     while(1) {
11161         yyboardindex = scratch + (plyNr&1);
11162       quickFlag = 1;
11163         next = Myylex();
11164       quickFlag = 0;
11165         switch(next) {
11166             case PGNTag:
11167                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11168 #if 0
11169                 ParsePGNTag(yy_text, &dummyInfo); // this has a bad memory leak...
11170                 if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL;
11171 #else
11172                 // do it ourselves avoiding malloc
11173                 { char *p = yy_text+1, *q;
11174                   while(!isdigit(*p) && !isalpha(*p)) p++;
11175                   q  = p; while(*p != ' ' && *p != '\t' && *p != '\n') p++;
11176                   *p = NULLCHAR;
11177                   if(!StrCaseCmp(q, "Date") && (p = strchr(p+1, '"'))) { if(atoi(p+1) < appData.dateThreshold) return -1; } else
11178                   if(!StrCaseCmp(q, "Variant")  &&  (p = strchr(p+1, '"'))) dummyInfo.variant = StringToVariant(p+1); else
11179                   if(!StrCaseCmp(q, "WhiteElo")  && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11180                   if(!StrCaseCmp(q, "BlackElo")  && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11181                   if(!StrCaseCmp(q, "WhiteUSCF") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11182                   if(!StrCaseCmp(q, "BlackUSCF") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11183                   if(!StrCaseCmp(q, "FEN")  && (p = strchr(p+1, '"'))) ParseFEN(boards[scratch], &btm, p+1);
11184                 }
11185 #endif
11186             default:
11187                 continue;
11188
11189             case XBoardGame:
11190             case GNUChessGame:
11191                 if(plyNr) return -1; // after we have seen moves, this is for new game
11192               continue;
11193
11194             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11195             case ImpossibleMove:
11196             case WhiteWins: // game ends here with these four
11197             case BlackWins:
11198             case GameIsDrawn:
11199             case GameUnfinished:
11200                 return -1;
11201
11202             case IllegalMove:
11203                 if(appData.testLegality) return -1;
11204             case WhiteCapturesEnPassant:
11205             case BlackCapturesEnPassant:
11206             case WhitePromotion:
11207             case BlackPromotion:
11208             case WhiteNonPromotion:
11209             case BlackNonPromotion:
11210             case NormalMove:
11211             case WhiteKingSideCastle:
11212             case WhiteQueenSideCastle:
11213             case BlackKingSideCastle:
11214             case BlackQueenSideCastle:
11215             case WhiteKingSideCastleWild:
11216             case WhiteQueenSideCastleWild:
11217             case BlackKingSideCastleWild:
11218             case BlackQueenSideCastleWild:
11219             case WhiteHSideCastleFR:
11220             case WhiteASideCastleFR:
11221             case BlackHSideCastleFR:
11222             case BlackASideCastleFR:
11223                 fromX = currentMoveString[0] - AAA;
11224                 fromY = currentMoveString[1] - ONE;
11225                 toX = currentMoveString[2] - AAA;
11226                 toY = currentMoveString[3] - ONE;
11227                 promoChar = currentMoveString[4];
11228                 break;
11229             case WhiteDrop:
11230             case BlackDrop:
11231                 fromX = next == WhiteDrop ?
11232                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11233                   (int) CharToPiece(ToLower(currentMoveString[0]));
11234                 fromY = DROP_RANK;
11235                 toX = currentMoveString[2] - AAA;
11236                 toY = currentMoveString[3] - ONE;
11237                 break;
11238         }
11239         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11240         if(plyNr == 0) { // but first figure out variant and initial position
11241             if(dummyInfo.variant != gameInfo.variant) return -1; // wrong variant
11242             if(appData.eloThreshold1 && (dummyInfo.whiteRating < appData.eloThreshold1 && dummyInfo.blackRating < appData.eloThreshold1)) return -1;
11243             if(appData.eloThreshold2 && (dummyInfo.whiteRating < appData.eloThreshold2 || dummyInfo.blackRating < appData.eloThreshold2)) return -1;
11244             if(appData.dateThreshold && (!dummyInfo.date || atoi(dummyInfo.date) < appData.dateThreshold)) return -1;
11245             if(btm) CopyBoard(boards[scratch+1], boards[scratch]), plyNr++;
11246             if(PositionMatches(boards[scratch + plyNr], boards[currentMove])) return plyNr;
11247         }
11248         CopyBoard(boards[scratch + (plyNr+1&1)], boards[scratch + (plyNr&1)]);
11249         plyNr++;
11250         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch + (plyNr&1)]);
11251         if(PositionMatches(boards[scratch + (plyNr&1)], boards[currentMove])) return plyNr;
11252     }
11253 }
11254
11255 /* Load the nth game from open file f */
11256 int
11257 LoadGame(f, gameNumber, title, useList)
11258      FILE *f;
11259      int gameNumber;
11260      char *title;
11261      int useList;
11262 {
11263     ChessMove cm;
11264     char buf[MSG_SIZ];
11265     int gn = gameNumber;
11266     ListGame *lg = NULL;
11267     int numPGNTags = 0;
11268     int err, pos = -1;
11269     GameMode oldGameMode;
11270     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11271
11272     if (appData.debugMode)
11273         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11274
11275     if (gameMode == Training )
11276         SetTrainingModeOff();
11277
11278     oldGameMode = gameMode;
11279     if (gameMode != BeginningOfGame) {
11280       Reset(FALSE, TRUE);
11281     }
11282
11283     gameFileFP = f;
11284     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11285         fclose(lastLoadGameFP);
11286     }
11287
11288     if (useList) {
11289         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11290
11291         if (lg) {
11292             fseek(f, lg->offset, 0);
11293             GameListHighlight(gameNumber);
11294             pos = lg->position;
11295             gn = 1;
11296         }
11297         else {
11298             DisplayError(_("Game number out of range"), 0);
11299             return FALSE;
11300         }
11301     } else {
11302         GameListDestroy();
11303         if (fseek(f, 0, 0) == -1) {
11304             if (f == lastLoadGameFP ?
11305                 gameNumber == lastLoadGameNumber + 1 :
11306                 gameNumber == 1) {
11307                 gn = 1;
11308             } else {
11309                 DisplayError(_("Can't seek on game file"), 0);
11310                 return FALSE;
11311             }
11312         }
11313     }
11314     lastLoadGameFP = f;
11315     lastLoadGameNumber = gameNumber;
11316     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11317     lastLoadGameUseList = useList;
11318
11319     yynewfile(f);
11320
11321     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11322       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11323                 lg->gameInfo.black);
11324             DisplayTitle(buf);
11325     } else if (*title != NULLCHAR) {
11326         if (gameNumber > 1) {
11327           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11328             DisplayTitle(buf);
11329         } else {
11330             DisplayTitle(title);
11331         }
11332     }
11333
11334     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11335         gameMode = PlayFromGameFile;
11336         ModeHighlight();
11337     }
11338
11339     currentMove = forwardMostMove = backwardMostMove = 0;
11340     CopyBoard(boards[0], initialPosition);
11341     StopClocks();
11342
11343     /*
11344      * Skip the first gn-1 games in the file.
11345      * Also skip over anything that precedes an identifiable
11346      * start of game marker, to avoid being confused by
11347      * garbage at the start of the file.  Currently
11348      * recognized start of game markers are the move number "1",
11349      * the pattern "gnuchess .* game", the pattern
11350      * "^[#;%] [^ ]* game file", and a PGN tag block.
11351      * A game that starts with one of the latter two patterns
11352      * will also have a move number 1, possibly
11353      * following a position diagram.
11354      * 5-4-02: Let's try being more lenient and allowing a game to
11355      * start with an unnumbered move.  Does that break anything?
11356      */
11357     cm = lastLoadGameStart = EndOfFile;
11358     while (gn > 0) {
11359         yyboardindex = forwardMostMove;
11360         cm = (ChessMove) Myylex();
11361         switch (cm) {
11362           case EndOfFile:
11363             if (cmailMsgLoaded) {
11364                 nCmailGames = CMAIL_MAX_GAMES - gn;
11365             } else {
11366                 Reset(TRUE, TRUE);
11367                 DisplayError(_("Game not found in file"), 0);
11368             }
11369             return FALSE;
11370
11371           case GNUChessGame:
11372           case XBoardGame:
11373             gn--;
11374             lastLoadGameStart = cm;
11375             break;
11376
11377           case MoveNumberOne:
11378             switch (lastLoadGameStart) {
11379               case GNUChessGame:
11380               case XBoardGame:
11381               case PGNTag:
11382                 break;
11383               case MoveNumberOne:
11384               case EndOfFile:
11385                 gn--;           /* count this game */
11386                 lastLoadGameStart = cm;
11387                 break;
11388               default:
11389                 /* impossible */
11390                 break;
11391             }
11392             break;
11393
11394           case PGNTag:
11395             switch (lastLoadGameStart) {
11396               case GNUChessGame:
11397               case PGNTag:
11398               case MoveNumberOne:
11399               case EndOfFile:
11400                 gn--;           /* count this game */
11401                 lastLoadGameStart = cm;
11402                 break;
11403               case XBoardGame:
11404                 lastLoadGameStart = cm; /* game counted already */
11405                 break;
11406               default:
11407                 /* impossible */
11408                 break;
11409             }
11410             if (gn > 0) {
11411                 do {
11412                     yyboardindex = forwardMostMove;
11413                     cm = (ChessMove) Myylex();
11414                 } while (cm == PGNTag || cm == Comment);
11415             }
11416             break;
11417
11418           case WhiteWins:
11419           case BlackWins:
11420           case GameIsDrawn:
11421             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11422                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11423                     != CMAIL_OLD_RESULT) {
11424                     nCmailResults ++ ;
11425                     cmailResult[  CMAIL_MAX_GAMES
11426                                 - gn - 1] = CMAIL_OLD_RESULT;
11427                 }
11428             }
11429             break;
11430
11431           case NormalMove:
11432             /* Only a NormalMove can be at the start of a game
11433              * without a position diagram. */
11434             if (lastLoadGameStart == EndOfFile ) {
11435               gn--;
11436               lastLoadGameStart = MoveNumberOne;
11437             }
11438             break;
11439
11440           default:
11441             break;
11442         }
11443     }
11444
11445     if (appData.debugMode)
11446       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11447
11448     if (cm == XBoardGame) {
11449         /* Skip any header junk before position diagram and/or move 1 */
11450         for (;;) {
11451             yyboardindex = forwardMostMove;
11452             cm = (ChessMove) Myylex();
11453
11454             if (cm == EndOfFile ||
11455                 cm == GNUChessGame || cm == XBoardGame) {
11456                 /* Empty game; pretend end-of-file and handle later */
11457                 cm = EndOfFile;
11458                 break;
11459             }
11460
11461             if (cm == MoveNumberOne || cm == PositionDiagram ||
11462                 cm == PGNTag || cm == Comment)
11463               break;
11464         }
11465     } else if (cm == GNUChessGame) {
11466         if (gameInfo.event != NULL) {
11467             free(gameInfo.event);
11468         }
11469         gameInfo.event = StrSave(yy_text);
11470     }
11471
11472     startedFromSetupPosition = FALSE;
11473     while (cm == PGNTag) {
11474         if (appData.debugMode)
11475           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11476         err = ParsePGNTag(yy_text, &gameInfo);
11477         if (!err) numPGNTags++;
11478
11479         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11480         if(gameInfo.variant != oldVariant) {
11481             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11482             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11483             InitPosition(TRUE);
11484             oldVariant = gameInfo.variant;
11485             if (appData.debugMode)
11486               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11487         }
11488
11489
11490         if (gameInfo.fen != NULL) {
11491           Board initial_position;
11492           startedFromSetupPosition = TRUE;
11493           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11494             Reset(TRUE, TRUE);
11495             DisplayError(_("Bad FEN position in file"), 0);
11496             return FALSE;
11497           }
11498           CopyBoard(boards[0], initial_position);
11499           if (blackPlaysFirst) {
11500             currentMove = forwardMostMove = backwardMostMove = 1;
11501             CopyBoard(boards[1], initial_position);
11502             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11503             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11504             timeRemaining[0][1] = whiteTimeRemaining;
11505             timeRemaining[1][1] = blackTimeRemaining;
11506             if (commentList[0] != NULL) {
11507               commentList[1] = commentList[0];
11508               commentList[0] = NULL;
11509             }
11510           } else {
11511             currentMove = forwardMostMove = backwardMostMove = 0;
11512           }
11513           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11514           {   int i;
11515               initialRulePlies = FENrulePlies;
11516               for( i=0; i< nrCastlingRights; i++ )
11517                   initialRights[i] = initial_position[CASTLING][i];
11518           }
11519           yyboardindex = forwardMostMove;
11520           free(gameInfo.fen);
11521           gameInfo.fen = NULL;
11522         }
11523
11524         yyboardindex = forwardMostMove;
11525         cm = (ChessMove) Myylex();
11526
11527         /* Handle comments interspersed among the tags */
11528         while (cm == Comment) {
11529             char *p;
11530             if (appData.debugMode)
11531               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11532             p = yy_text;
11533             AppendComment(currentMove, p, FALSE);
11534             yyboardindex = forwardMostMove;
11535             cm = (ChessMove) Myylex();
11536         }
11537     }
11538
11539     /* don't rely on existence of Event tag since if game was
11540      * pasted from clipboard the Event tag may not exist
11541      */
11542     if (numPGNTags > 0){
11543         char *tags;
11544         if (gameInfo.variant == VariantNormal) {
11545           VariantClass v = StringToVariant(gameInfo.event);
11546           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11547           if(v < VariantShogi) gameInfo.variant = v;
11548         }
11549         if (!matchMode) {
11550           if( appData.autoDisplayTags ) {
11551             tags = PGNTags(&gameInfo);
11552             TagsPopUp(tags, CmailMsg());
11553             free(tags);
11554           }
11555         }
11556     } else {
11557         /* Make something up, but don't display it now */
11558         SetGameInfo();
11559         TagsPopDown();
11560     }
11561
11562     if (cm == PositionDiagram) {
11563         int i, j;
11564         char *p;
11565         Board initial_position;
11566
11567         if (appData.debugMode)
11568           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11569
11570         if (!startedFromSetupPosition) {
11571             p = yy_text;
11572             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11573               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11574                 switch (*p) {
11575                   case '{':
11576                   case '[':
11577                   case '-':
11578                   case ' ':
11579                   case '\t':
11580                   case '\n':
11581                   case '\r':
11582                     break;
11583                   default:
11584                     initial_position[i][j++] = CharToPiece(*p);
11585                     break;
11586                 }
11587             while (*p == ' ' || *p == '\t' ||
11588                    *p == '\n' || *p == '\r') p++;
11589
11590             if (strncmp(p, "black", strlen("black"))==0)
11591               blackPlaysFirst = TRUE;
11592             else
11593               blackPlaysFirst = FALSE;
11594             startedFromSetupPosition = TRUE;
11595
11596             CopyBoard(boards[0], initial_position);
11597             if (blackPlaysFirst) {
11598                 currentMove = forwardMostMove = backwardMostMove = 1;
11599                 CopyBoard(boards[1], initial_position);
11600                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11601                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11602                 timeRemaining[0][1] = whiteTimeRemaining;
11603                 timeRemaining[1][1] = blackTimeRemaining;
11604                 if (commentList[0] != NULL) {
11605                     commentList[1] = commentList[0];
11606                     commentList[0] = NULL;
11607                 }
11608             } else {
11609                 currentMove = forwardMostMove = backwardMostMove = 0;
11610             }
11611         }
11612         yyboardindex = forwardMostMove;
11613         cm = (ChessMove) Myylex();
11614     }
11615
11616     if (first.pr == NoProc) {
11617         StartChessProgram(&first);
11618     }
11619     InitChessProgram(&first, FALSE);
11620     SendToProgram("force\n", &first);
11621     if (startedFromSetupPosition) {
11622         SendBoard(&first, forwardMostMove);
11623     if (appData.debugMode) {
11624         fprintf(debugFP, "Load Game\n");
11625     }
11626         DisplayBothClocks();
11627     }
11628
11629     /* [HGM] server: flag to write setup moves in broadcast file as one */
11630     loadFlag = appData.suppressLoadMoves;
11631
11632     while (cm == Comment) {
11633         char *p;
11634         if (appData.debugMode)
11635           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11636         p = yy_text;
11637         AppendComment(currentMove, p, FALSE);
11638         yyboardindex = forwardMostMove;
11639         cm = (ChessMove) Myylex();
11640     }
11641
11642     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11643         cm == WhiteWins || cm == BlackWins ||
11644         cm == GameIsDrawn || cm == GameUnfinished) {
11645         DisplayMessage("", _("No moves in game"));
11646         if (cmailMsgLoaded) {
11647             if (appData.debugMode)
11648               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11649             ClearHighlights();
11650             flipView = FALSE;
11651         }
11652         DrawPosition(FALSE, boards[currentMove]);
11653         DisplayBothClocks();
11654         gameMode = EditGame;
11655         ModeHighlight();
11656         gameFileFP = NULL;
11657         cmailOldMove = 0;
11658         return TRUE;
11659     }
11660
11661     // [HGM] PV info: routine tests if comment empty
11662     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11663         DisplayComment(currentMove - 1, commentList[currentMove]);
11664     }
11665     if (!matchMode && appData.timeDelay != 0)
11666       DrawPosition(FALSE, boards[currentMove]);
11667
11668     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11669       programStats.ok_to_send = 1;
11670     }
11671
11672     /* if the first token after the PGN tags is a move
11673      * and not move number 1, retrieve it from the parser
11674      */
11675     if (cm != MoveNumberOne)
11676         LoadGameOneMove(cm);
11677
11678     /* load the remaining moves from the file */
11679     while (LoadGameOneMove(EndOfFile)) {
11680       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11681       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11682     }
11683
11684     /* rewind to the start of the game */
11685     currentMove = backwardMostMove;
11686
11687     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11688
11689     if (oldGameMode == AnalyzeFile ||
11690         oldGameMode == AnalyzeMode) {
11691       AnalyzeFileEvent();
11692     }
11693
11694     if (!matchMode && pos >= 0) {
11695         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11696     } else
11697     if (matchMode || appData.timeDelay == 0) {
11698       ToEndEvent();
11699     } else if (appData.timeDelay > 0) {
11700       AutoPlayGameLoop();
11701     }
11702
11703     if (appData.debugMode)
11704         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11705
11706     loadFlag = 0; /* [HGM] true game starts */
11707     return TRUE;
11708 }
11709
11710 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11711 int
11712 ReloadPosition(offset)
11713      int offset;
11714 {
11715     int positionNumber = lastLoadPositionNumber + offset;
11716     if (lastLoadPositionFP == NULL) {
11717         DisplayError(_("No position has been loaded yet"), 0);
11718         return FALSE;
11719     }
11720     if (positionNumber <= 0) {
11721         DisplayError(_("Can't back up any further"), 0);
11722         return FALSE;
11723     }
11724     return LoadPosition(lastLoadPositionFP, positionNumber,
11725                         lastLoadPositionTitle);
11726 }
11727
11728 /* Load the nth position from the given file */
11729 int
11730 LoadPositionFromFile(filename, n, title)
11731      char *filename;
11732      int n;
11733      char *title;
11734 {
11735     FILE *f;
11736     char buf[MSG_SIZ];
11737
11738     if (strcmp(filename, "-") == 0) {
11739         return LoadPosition(stdin, n, "stdin");
11740     } else {
11741         f = fopen(filename, "rb");
11742         if (f == NULL) {
11743             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11744             DisplayError(buf, errno);
11745             return FALSE;
11746         } else {
11747             return LoadPosition(f, n, title);
11748         }
11749     }
11750 }
11751
11752 /* Load the nth position from the given open file, and close it */
11753 int
11754 LoadPosition(f, positionNumber, title)
11755      FILE *f;
11756      int positionNumber;
11757      char *title;
11758 {
11759     char *p, line[MSG_SIZ];
11760     Board initial_position;
11761     int i, j, fenMode, pn;
11762
11763     if (gameMode == Training )
11764         SetTrainingModeOff();
11765
11766     if (gameMode != BeginningOfGame) {
11767         Reset(FALSE, TRUE);
11768     }
11769     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11770         fclose(lastLoadPositionFP);
11771     }
11772     if (positionNumber == 0) positionNumber = 1;
11773     lastLoadPositionFP = f;
11774     lastLoadPositionNumber = positionNumber;
11775     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11776     if (first.pr == NoProc) {
11777       StartChessProgram(&first);
11778       InitChessProgram(&first, FALSE);
11779     }
11780     pn = positionNumber;
11781     if (positionNumber < 0) {
11782         /* Negative position number means to seek to that byte offset */
11783         if (fseek(f, -positionNumber, 0) == -1) {
11784             DisplayError(_("Can't seek on position file"), 0);
11785             return FALSE;
11786         };
11787         pn = 1;
11788     } else {
11789         if (fseek(f, 0, 0) == -1) {
11790             if (f == lastLoadPositionFP ?
11791                 positionNumber == lastLoadPositionNumber + 1 :
11792                 positionNumber == 1) {
11793                 pn = 1;
11794             } else {
11795                 DisplayError(_("Can't seek on position file"), 0);
11796                 return FALSE;
11797             }
11798         }
11799     }
11800     /* See if this file is FEN or old-style xboard */
11801     if (fgets(line, MSG_SIZ, f) == NULL) {
11802         DisplayError(_("Position not found in file"), 0);
11803         return FALSE;
11804     }
11805     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11806     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11807
11808     if (pn >= 2) {
11809         if (fenMode || line[0] == '#') pn--;
11810         while (pn > 0) {
11811             /* skip positions before number pn */
11812             if (fgets(line, MSG_SIZ, f) == NULL) {
11813                 Reset(TRUE, TRUE);
11814                 DisplayError(_("Position not found in file"), 0);
11815                 return FALSE;
11816             }
11817             if (fenMode || line[0] == '#') pn--;
11818         }
11819     }
11820
11821     if (fenMode) {
11822         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11823             DisplayError(_("Bad FEN position in file"), 0);
11824             return FALSE;
11825         }
11826     } else {
11827         (void) fgets(line, MSG_SIZ, f);
11828         (void) fgets(line, MSG_SIZ, f);
11829
11830         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11831             (void) fgets(line, MSG_SIZ, f);
11832             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11833                 if (*p == ' ')
11834                   continue;
11835                 initial_position[i][j++] = CharToPiece(*p);
11836             }
11837         }
11838
11839         blackPlaysFirst = FALSE;
11840         if (!feof(f)) {
11841             (void) fgets(line, MSG_SIZ, f);
11842             if (strncmp(line, "black", strlen("black"))==0)
11843               blackPlaysFirst = TRUE;
11844         }
11845     }
11846     startedFromSetupPosition = TRUE;
11847
11848     SendToProgram("force\n", &first);
11849     CopyBoard(boards[0], initial_position);
11850     if (blackPlaysFirst) {
11851         currentMove = forwardMostMove = backwardMostMove = 1;
11852         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11853         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11854         CopyBoard(boards[1], initial_position);
11855         DisplayMessage("", _("Black to play"));
11856     } else {
11857         currentMove = forwardMostMove = backwardMostMove = 0;
11858         DisplayMessage("", _("White to play"));
11859     }
11860     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11861     SendBoard(&first, forwardMostMove);
11862     if (appData.debugMode) {
11863 int i, j;
11864   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11865   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11866         fprintf(debugFP, "Load Position\n");
11867     }
11868
11869     if (positionNumber > 1) {
11870       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11871         DisplayTitle(line);
11872     } else {
11873         DisplayTitle(title);
11874     }
11875     gameMode = EditGame;
11876     ModeHighlight();
11877     ResetClocks();
11878     timeRemaining[0][1] = whiteTimeRemaining;
11879     timeRemaining[1][1] = blackTimeRemaining;
11880     DrawPosition(FALSE, boards[currentMove]);
11881
11882     return TRUE;
11883 }
11884
11885
11886 void
11887 CopyPlayerNameIntoFileName(dest, src)
11888      char **dest, *src;
11889 {
11890     while (*src != NULLCHAR && *src != ',') {
11891         if (*src == ' ') {
11892             *(*dest)++ = '_';
11893             src++;
11894         } else {
11895             *(*dest)++ = *src++;
11896         }
11897     }
11898 }
11899
11900 char *DefaultFileName(ext)
11901      char *ext;
11902 {
11903     static char def[MSG_SIZ];
11904     char *p;
11905
11906     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11907         p = def;
11908         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11909         *p++ = '-';
11910         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11911         *p++ = '.';
11912         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11913     } else {
11914         def[0] = NULLCHAR;
11915     }
11916     return def;
11917 }
11918
11919 /* Save the current game to the given file */
11920 int
11921 SaveGameToFile(filename, append)
11922      char *filename;
11923      int append;
11924 {
11925     FILE *f;
11926     char buf[MSG_SIZ];
11927     int result;
11928
11929     if (strcmp(filename, "-") == 0) {
11930         return SaveGame(stdout, 0, NULL);
11931     } else {
11932         f = fopen(filename, append ? "a" : "w");
11933         if (f == NULL) {
11934             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11935             DisplayError(buf, errno);
11936             return FALSE;
11937         } else {
11938             safeStrCpy(buf, lastMsg, MSG_SIZ);
11939             DisplayMessage(_("Waiting for access to save file"), "");
11940             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11941             DisplayMessage(_("Saving game"), "");
11942             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11943             result = SaveGame(f, 0, NULL);
11944             DisplayMessage(buf, "");
11945             return result;
11946         }
11947     }
11948 }
11949
11950 char *
11951 SavePart(str)
11952      char *str;
11953 {
11954     static char buf[MSG_SIZ];
11955     char *p;
11956
11957     p = strchr(str, ' ');
11958     if (p == NULL) return str;
11959     strncpy(buf, str, p - str);
11960     buf[p - str] = NULLCHAR;
11961     return buf;
11962 }
11963
11964 #define PGN_MAX_LINE 75
11965
11966 #define PGN_SIDE_WHITE  0
11967 #define PGN_SIDE_BLACK  1
11968
11969 /* [AS] */
11970 static int FindFirstMoveOutOfBook( int side )
11971 {
11972     int result = -1;
11973
11974     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11975         int index = backwardMostMove;
11976         int has_book_hit = 0;
11977
11978         if( (index % 2) != side ) {
11979             index++;
11980         }
11981
11982         while( index < forwardMostMove ) {
11983             /* Check to see if engine is in book */
11984             int depth = pvInfoList[index].depth;
11985             int score = pvInfoList[index].score;
11986             int in_book = 0;
11987
11988             if( depth <= 2 ) {
11989                 in_book = 1;
11990             }
11991             else if( score == 0 && depth == 63 ) {
11992                 in_book = 1; /* Zappa */
11993             }
11994             else if( score == 2 && depth == 99 ) {
11995                 in_book = 1; /* Abrok */
11996             }
11997
11998             has_book_hit += in_book;
11999
12000             if( ! in_book ) {
12001                 result = index;
12002
12003                 break;
12004             }
12005
12006             index += 2;
12007         }
12008     }
12009
12010     return result;
12011 }
12012
12013 /* [AS] */
12014 void GetOutOfBookInfo( char * buf )
12015 {
12016     int oob[2];
12017     int i;
12018     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12019
12020     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12021     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12022
12023     *buf = '\0';
12024
12025     if( oob[0] >= 0 || oob[1] >= 0 ) {
12026         for( i=0; i<2; i++ ) {
12027             int idx = oob[i];
12028
12029             if( idx >= 0 ) {
12030                 if( i > 0 && oob[0] >= 0 ) {
12031                     strcat( buf, "   " );
12032                 }
12033
12034                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12035                 sprintf( buf+strlen(buf), "%s%.2f",
12036                     pvInfoList[idx].score >= 0 ? "+" : "",
12037                     pvInfoList[idx].score / 100.0 );
12038             }
12039         }
12040     }
12041 }
12042
12043 /* Save game in PGN style and close the file */
12044 int
12045 SaveGamePGN(f)
12046      FILE *f;
12047 {
12048     int i, offset, linelen, newblock;
12049     time_t tm;
12050 //    char *movetext;
12051     char numtext[32];
12052     int movelen, numlen, blank;
12053     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12054
12055     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12056
12057     tm = time((time_t *) NULL);
12058
12059     PrintPGNTags(f, &gameInfo);
12060
12061     if (backwardMostMove > 0 || startedFromSetupPosition) {
12062         char *fen = PositionToFEN(backwardMostMove, NULL);
12063         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12064         fprintf(f, "\n{--------------\n");
12065         PrintPosition(f, backwardMostMove);
12066         fprintf(f, "--------------}\n");
12067         free(fen);
12068     }
12069     else {
12070         /* [AS] Out of book annotation */
12071         if( appData.saveOutOfBookInfo ) {
12072             char buf[64];
12073
12074             GetOutOfBookInfo( buf );
12075
12076             if( buf[0] != '\0' ) {
12077                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12078             }
12079         }
12080
12081         fprintf(f, "\n");
12082     }
12083
12084     i = backwardMostMove;
12085     linelen = 0;
12086     newblock = TRUE;
12087
12088     while (i < forwardMostMove) {
12089         /* Print comments preceding this move */
12090         if (commentList[i] != NULL) {
12091             if (linelen > 0) fprintf(f, "\n");
12092             fprintf(f, "%s", commentList[i]);
12093             linelen = 0;
12094             newblock = TRUE;
12095         }
12096
12097         /* Format move number */
12098         if ((i % 2) == 0)
12099           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12100         else
12101           if (newblock)
12102             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12103           else
12104             numtext[0] = NULLCHAR;
12105
12106         numlen = strlen(numtext);
12107         newblock = FALSE;
12108
12109         /* Print move number */
12110         blank = linelen > 0 && numlen > 0;
12111         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12112             fprintf(f, "\n");
12113             linelen = 0;
12114             blank = 0;
12115         }
12116         if (blank) {
12117             fprintf(f, " ");
12118             linelen++;
12119         }
12120         fprintf(f, "%s", numtext);
12121         linelen += numlen;
12122
12123         /* Get move */
12124         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12125         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12126
12127         /* Print move */
12128         blank = linelen > 0 && movelen > 0;
12129         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12130             fprintf(f, "\n");
12131             linelen = 0;
12132             blank = 0;
12133         }
12134         if (blank) {
12135             fprintf(f, " ");
12136             linelen++;
12137         }
12138         fprintf(f, "%s", move_buffer);
12139         linelen += movelen;
12140
12141         /* [AS] Add PV info if present */
12142         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12143             /* [HGM] add time */
12144             char buf[MSG_SIZ]; int seconds;
12145
12146             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12147
12148             if( seconds <= 0)
12149               buf[0] = 0;
12150             else
12151               if( seconds < 30 )
12152                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12153               else
12154                 {
12155                   seconds = (seconds + 4)/10; // round to full seconds
12156                   if( seconds < 60 )
12157                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12158                   else
12159                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12160                 }
12161
12162             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12163                       pvInfoList[i].score >= 0 ? "+" : "",
12164                       pvInfoList[i].score / 100.0,
12165                       pvInfoList[i].depth,
12166                       buf );
12167
12168             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12169
12170             /* Print score/depth */
12171             blank = linelen > 0 && movelen > 0;
12172             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12173                 fprintf(f, "\n");
12174                 linelen = 0;
12175                 blank = 0;
12176             }
12177             if (blank) {
12178                 fprintf(f, " ");
12179                 linelen++;
12180             }
12181             fprintf(f, "%s", move_buffer);
12182             linelen += movelen;
12183         }
12184
12185         i++;
12186     }
12187
12188     /* Start a new line */
12189     if (linelen > 0) fprintf(f, "\n");
12190
12191     /* Print comments after last move */
12192     if (commentList[i] != NULL) {
12193         fprintf(f, "%s\n", commentList[i]);
12194     }
12195
12196     /* Print result */
12197     if (gameInfo.resultDetails != NULL &&
12198         gameInfo.resultDetails[0] != NULLCHAR) {
12199         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12200                 PGNResult(gameInfo.result));
12201     } else {
12202         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12203     }
12204
12205     fclose(f);
12206     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12207     return TRUE;
12208 }
12209
12210 /* Save game in old style and close the file */
12211 int
12212 SaveGameOldStyle(f)
12213      FILE *f;
12214 {
12215     int i, offset;
12216     time_t tm;
12217
12218     tm = time((time_t *) NULL);
12219
12220     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12221     PrintOpponents(f);
12222
12223     if (backwardMostMove > 0 || startedFromSetupPosition) {
12224         fprintf(f, "\n[--------------\n");
12225         PrintPosition(f, backwardMostMove);
12226         fprintf(f, "--------------]\n");
12227     } else {
12228         fprintf(f, "\n");
12229     }
12230
12231     i = backwardMostMove;
12232     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12233
12234     while (i < forwardMostMove) {
12235         if (commentList[i] != NULL) {
12236             fprintf(f, "[%s]\n", commentList[i]);
12237         }
12238
12239         if ((i % 2) == 1) {
12240             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12241             i++;
12242         } else {
12243             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12244             i++;
12245             if (commentList[i] != NULL) {
12246                 fprintf(f, "\n");
12247                 continue;
12248             }
12249             if (i >= forwardMostMove) {
12250                 fprintf(f, "\n");
12251                 break;
12252             }
12253             fprintf(f, "%s\n", parseList[i]);
12254             i++;
12255         }
12256     }
12257
12258     if (commentList[i] != NULL) {
12259         fprintf(f, "[%s]\n", commentList[i]);
12260     }
12261
12262     /* This isn't really the old style, but it's close enough */
12263     if (gameInfo.resultDetails != NULL &&
12264         gameInfo.resultDetails[0] != NULLCHAR) {
12265         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12266                 gameInfo.resultDetails);
12267     } else {
12268         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12269     }
12270
12271     fclose(f);
12272     return TRUE;
12273 }
12274
12275 /* Save the current game to open file f and close the file */
12276 int
12277 SaveGame(f, dummy, dummy2)
12278      FILE *f;
12279      int dummy;
12280      char *dummy2;
12281 {
12282     if (gameMode == EditPosition) EditPositionDone(TRUE);
12283     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12284     if (appData.oldSaveStyle)
12285       return SaveGameOldStyle(f);
12286     else
12287       return SaveGamePGN(f);
12288 }
12289
12290 /* Save the current position to the given file */
12291 int
12292 SavePositionToFile(filename)
12293      char *filename;
12294 {
12295     FILE *f;
12296     char buf[MSG_SIZ];
12297
12298     if (strcmp(filename, "-") == 0) {
12299         return SavePosition(stdout, 0, NULL);
12300     } else {
12301         f = fopen(filename, "a");
12302         if (f == NULL) {
12303             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12304             DisplayError(buf, errno);
12305             return FALSE;
12306         } else {
12307             safeStrCpy(buf, lastMsg, MSG_SIZ);
12308             DisplayMessage(_("Waiting for access to save file"), "");
12309             flock(fileno(f), LOCK_EX); // [HGM] lock
12310             DisplayMessage(_("Saving position"), "");
12311             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12312             SavePosition(f, 0, NULL);
12313             DisplayMessage(buf, "");
12314             return TRUE;
12315         }
12316     }
12317 }
12318
12319 /* Save the current position to the given open file and close the file */
12320 int
12321 SavePosition(f, dummy, dummy2)
12322      FILE *f;
12323      int dummy;
12324      char *dummy2;
12325 {
12326     time_t tm;
12327     char *fen;
12328
12329     if (gameMode == EditPosition) EditPositionDone(TRUE);
12330     if (appData.oldSaveStyle) {
12331         tm = time((time_t *) NULL);
12332
12333         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12334         PrintOpponents(f);
12335         fprintf(f, "[--------------\n");
12336         PrintPosition(f, currentMove);
12337         fprintf(f, "--------------]\n");
12338     } else {
12339         fen = PositionToFEN(currentMove, NULL);
12340         fprintf(f, "%s\n", fen);
12341         free(fen);
12342     }
12343     fclose(f);
12344     return TRUE;
12345 }
12346
12347 void
12348 ReloadCmailMsgEvent(unregister)
12349      int unregister;
12350 {
12351 #if !WIN32
12352     static char *inFilename = NULL;
12353     static char *outFilename;
12354     int i;
12355     struct stat inbuf, outbuf;
12356     int status;
12357
12358     /* Any registered moves are unregistered if unregister is set, */
12359     /* i.e. invoked by the signal handler */
12360     if (unregister) {
12361         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12362             cmailMoveRegistered[i] = FALSE;
12363             if (cmailCommentList[i] != NULL) {
12364                 free(cmailCommentList[i]);
12365                 cmailCommentList[i] = NULL;
12366             }
12367         }
12368         nCmailMovesRegistered = 0;
12369     }
12370
12371     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12372         cmailResult[i] = CMAIL_NOT_RESULT;
12373     }
12374     nCmailResults = 0;
12375
12376     if (inFilename == NULL) {
12377         /* Because the filenames are static they only get malloced once  */
12378         /* and they never get freed                                      */
12379         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12380         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12381
12382         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12383         sprintf(outFilename, "%s.out", appData.cmailGameName);
12384     }
12385
12386     status = stat(outFilename, &outbuf);
12387     if (status < 0) {
12388         cmailMailedMove = FALSE;
12389     } else {
12390         status = stat(inFilename, &inbuf);
12391         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12392     }
12393
12394     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12395        counts the games, notes how each one terminated, etc.
12396
12397        It would be nice to remove this kludge and instead gather all
12398        the information while building the game list.  (And to keep it
12399        in the game list nodes instead of having a bunch of fixed-size
12400        parallel arrays.)  Note this will require getting each game's
12401        termination from the PGN tags, as the game list builder does
12402        not process the game moves.  --mann
12403        */
12404     cmailMsgLoaded = TRUE;
12405     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12406
12407     /* Load first game in the file or popup game menu */
12408     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12409
12410 #endif /* !WIN32 */
12411     return;
12412 }
12413
12414 int
12415 RegisterMove()
12416 {
12417     FILE *f;
12418     char string[MSG_SIZ];
12419
12420     if (   cmailMailedMove
12421         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12422         return TRUE;            /* Allow free viewing  */
12423     }
12424
12425     /* Unregister move to ensure that we don't leave RegisterMove        */
12426     /* with the move registered when the conditions for registering no   */
12427     /* longer hold                                                       */
12428     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12429         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12430         nCmailMovesRegistered --;
12431
12432         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12433           {
12434               free(cmailCommentList[lastLoadGameNumber - 1]);
12435               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12436           }
12437     }
12438
12439     if (cmailOldMove == -1) {
12440         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12441         return FALSE;
12442     }
12443
12444     if (currentMove > cmailOldMove + 1) {
12445         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12446         return FALSE;
12447     }
12448
12449     if (currentMove < cmailOldMove) {
12450         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12451         return FALSE;
12452     }
12453
12454     if (forwardMostMove > currentMove) {
12455         /* Silently truncate extra moves */
12456         TruncateGame();
12457     }
12458
12459     if (   (currentMove == cmailOldMove + 1)
12460         || (   (currentMove == cmailOldMove)
12461             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12462                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12463         if (gameInfo.result != GameUnfinished) {
12464             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12465         }
12466
12467         if (commentList[currentMove] != NULL) {
12468             cmailCommentList[lastLoadGameNumber - 1]
12469               = StrSave(commentList[currentMove]);
12470         }
12471         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12472
12473         if (appData.debugMode)
12474           fprintf(debugFP, "Saving %s for game %d\n",
12475                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12476
12477         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12478
12479         f = fopen(string, "w");
12480         if (appData.oldSaveStyle) {
12481             SaveGameOldStyle(f); /* also closes the file */
12482
12483             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12484             f = fopen(string, "w");
12485             SavePosition(f, 0, NULL); /* also closes the file */
12486         } else {
12487             fprintf(f, "{--------------\n");
12488             PrintPosition(f, currentMove);
12489             fprintf(f, "--------------}\n\n");
12490
12491             SaveGame(f, 0, NULL); /* also closes the file*/
12492         }
12493
12494         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12495         nCmailMovesRegistered ++;
12496     } else if (nCmailGames == 1) {
12497         DisplayError(_("You have not made a move yet"), 0);
12498         return FALSE;
12499     }
12500
12501     return TRUE;
12502 }
12503
12504 void
12505 MailMoveEvent()
12506 {
12507 #if !WIN32
12508     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12509     FILE *commandOutput;
12510     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12511     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12512     int nBuffers;
12513     int i;
12514     int archived;
12515     char *arcDir;
12516
12517     if (! cmailMsgLoaded) {
12518         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12519         return;
12520     }
12521
12522     if (nCmailGames == nCmailResults) {
12523         DisplayError(_("No unfinished games"), 0);
12524         return;
12525     }
12526
12527 #if CMAIL_PROHIBIT_REMAIL
12528     if (cmailMailedMove) {
12529       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);
12530         DisplayError(msg, 0);
12531         return;
12532     }
12533 #endif
12534
12535     if (! (cmailMailedMove || RegisterMove())) return;
12536
12537     if (   cmailMailedMove
12538         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12539       snprintf(string, MSG_SIZ, partCommandString,
12540                appData.debugMode ? " -v" : "", appData.cmailGameName);
12541         commandOutput = popen(string, "r");
12542
12543         if (commandOutput == NULL) {
12544             DisplayError(_("Failed to invoke cmail"), 0);
12545         } else {
12546             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12547                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12548             }
12549             if (nBuffers > 1) {
12550                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12551                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12552                 nBytes = MSG_SIZ - 1;
12553             } else {
12554                 (void) memcpy(msg, buffer, nBytes);
12555             }
12556             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12557
12558             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12559                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12560
12561                 archived = TRUE;
12562                 for (i = 0; i < nCmailGames; i ++) {
12563                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12564                         archived = FALSE;
12565                     }
12566                 }
12567                 if (   archived
12568                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12569                         != NULL)) {
12570                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12571                            arcDir,
12572                            appData.cmailGameName,
12573                            gameInfo.date);
12574                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12575                     cmailMsgLoaded = FALSE;
12576                 }
12577             }
12578
12579             DisplayInformation(msg);
12580             pclose(commandOutput);
12581         }
12582     } else {
12583         if ((*cmailMsg) != '\0') {
12584             DisplayInformation(cmailMsg);
12585         }
12586     }
12587
12588     return;
12589 #endif /* !WIN32 */
12590 }
12591
12592 char *
12593 CmailMsg()
12594 {
12595 #if WIN32
12596     return NULL;
12597 #else
12598     int  prependComma = 0;
12599     char number[5];
12600     char string[MSG_SIZ];       /* Space for game-list */
12601     int  i;
12602
12603     if (!cmailMsgLoaded) return "";
12604
12605     if (cmailMailedMove) {
12606       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12607     } else {
12608         /* Create a list of games left */
12609       snprintf(string, MSG_SIZ, "[");
12610         for (i = 0; i < nCmailGames; i ++) {
12611             if (! (   cmailMoveRegistered[i]
12612                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12613                 if (prependComma) {
12614                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12615                 } else {
12616                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12617                     prependComma = 1;
12618                 }
12619
12620                 strcat(string, number);
12621             }
12622         }
12623         strcat(string, "]");
12624
12625         if (nCmailMovesRegistered + nCmailResults == 0) {
12626             switch (nCmailGames) {
12627               case 1:
12628                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12629                 break;
12630
12631               case 2:
12632                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12633                 break;
12634
12635               default:
12636                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12637                          nCmailGames);
12638                 break;
12639             }
12640         } else {
12641             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12642               case 1:
12643                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12644                          string);
12645                 break;
12646
12647               case 0:
12648                 if (nCmailResults == nCmailGames) {
12649                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12650                 } else {
12651                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12652                 }
12653                 break;
12654
12655               default:
12656                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12657                          string);
12658             }
12659         }
12660     }
12661     return cmailMsg;
12662 #endif /* WIN32 */
12663 }
12664
12665 void
12666 ResetGameEvent()
12667 {
12668     if (gameMode == Training)
12669       SetTrainingModeOff();
12670
12671     Reset(TRUE, TRUE);
12672     cmailMsgLoaded = FALSE;
12673     if (appData.icsActive) {
12674       SendToICS(ics_prefix);
12675       SendToICS("refresh\n");
12676     }
12677 }
12678
12679 void
12680 ExitEvent(status)
12681      int status;
12682 {
12683     exiting++;
12684     if (exiting > 2) {
12685       /* Give up on clean exit */
12686       exit(status);
12687     }
12688     if (exiting > 1) {
12689       /* Keep trying for clean exit */
12690       return;
12691     }
12692
12693     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12694
12695     if (telnetISR != NULL) {
12696       RemoveInputSource(telnetISR);
12697     }
12698     if (icsPR != NoProc) {
12699       DestroyChildProcess(icsPR, TRUE);
12700     }
12701
12702     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12703     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12704
12705     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12706     /* make sure this other one finishes before killing it!                  */
12707     if(endingGame) { int count = 0;
12708         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12709         while(endingGame && count++ < 10) DoSleep(1);
12710         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12711     }
12712
12713     /* Kill off chess programs */
12714     if (first.pr != NoProc) {
12715         ExitAnalyzeMode();
12716
12717         DoSleep( appData.delayBeforeQuit );
12718         SendToProgram("quit\n", &first);
12719         DoSleep( appData.delayAfterQuit );
12720         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12721     }
12722     if (second.pr != NoProc) {
12723         DoSleep( appData.delayBeforeQuit );
12724         SendToProgram("quit\n", &second);
12725         DoSleep( appData.delayAfterQuit );
12726         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12727     }
12728     if (first.isr != NULL) {
12729         RemoveInputSource(first.isr);
12730     }
12731     if (second.isr != NULL) {
12732         RemoveInputSource(second.isr);
12733     }
12734
12735     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12736     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12737
12738     ShutDownFrontEnd();
12739     exit(status);
12740 }
12741
12742 void
12743 PauseEvent()
12744 {
12745     if (appData.debugMode)
12746         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12747     if (pausing) {
12748         pausing = FALSE;
12749         ModeHighlight();
12750         if (gameMode == MachinePlaysWhite ||
12751             gameMode == MachinePlaysBlack) {
12752             StartClocks();
12753         } else {
12754             DisplayBothClocks();
12755         }
12756         if (gameMode == PlayFromGameFile) {
12757             if (appData.timeDelay >= 0)
12758                 AutoPlayGameLoop();
12759         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12760             Reset(FALSE, TRUE);
12761             SendToICS(ics_prefix);
12762             SendToICS("refresh\n");
12763         } else if (currentMove < forwardMostMove) {
12764             ForwardInner(forwardMostMove);
12765         }
12766         pauseExamInvalid = FALSE;
12767     } else {
12768         switch (gameMode) {
12769           default:
12770             return;
12771           case IcsExamining:
12772             pauseExamForwardMostMove = forwardMostMove;
12773             pauseExamInvalid = FALSE;
12774             /* fall through */
12775           case IcsObserving:
12776           case IcsPlayingWhite:
12777           case IcsPlayingBlack:
12778             pausing = TRUE;
12779             ModeHighlight();
12780             return;
12781           case PlayFromGameFile:
12782             (void) StopLoadGameTimer();
12783             pausing = TRUE;
12784             ModeHighlight();
12785             break;
12786           case BeginningOfGame:
12787             if (appData.icsActive) return;
12788             /* else fall through */
12789           case MachinePlaysWhite:
12790           case MachinePlaysBlack:
12791           case TwoMachinesPlay:
12792             if (forwardMostMove == 0)
12793               return;           /* don't pause if no one has moved */
12794             if ((gameMode == MachinePlaysWhite &&
12795                  !WhiteOnMove(forwardMostMove)) ||
12796                 (gameMode == MachinePlaysBlack &&
12797                  WhiteOnMove(forwardMostMove))) {
12798                 StopClocks();
12799             }
12800             pausing = TRUE;
12801             ModeHighlight();
12802             break;
12803         }
12804     }
12805 }
12806
12807 void
12808 EditCommentEvent()
12809 {
12810     char title[MSG_SIZ];
12811
12812     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12813       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12814     } else {
12815       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12816                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12817                parseList[currentMove - 1]);
12818     }
12819
12820     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12821 }
12822
12823
12824 void
12825 EditTagsEvent()
12826 {
12827     char *tags = PGNTags(&gameInfo);
12828     bookUp = FALSE;
12829     EditTagsPopUp(tags, NULL);
12830     free(tags);
12831 }
12832
12833 void
12834 AnalyzeModeEvent()
12835 {
12836     if (appData.noChessProgram || gameMode == AnalyzeMode)
12837       return;
12838
12839     if (gameMode != AnalyzeFile) {
12840         if (!appData.icsEngineAnalyze) {
12841                EditGameEvent();
12842                if (gameMode != EditGame) return;
12843         }
12844         ResurrectChessProgram();
12845         SendToProgram("analyze\n", &first);
12846         first.analyzing = TRUE;
12847         /*first.maybeThinking = TRUE;*/
12848         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12849         EngineOutputPopUp();
12850     }
12851     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12852     pausing = FALSE;
12853     ModeHighlight();
12854     SetGameInfo();
12855
12856     StartAnalysisClock();
12857     GetTimeMark(&lastNodeCountTime);
12858     lastNodeCount = 0;
12859 }
12860
12861 void
12862 AnalyzeFileEvent()
12863 {
12864     if (appData.noChessProgram || gameMode == AnalyzeFile)
12865       return;
12866
12867     if (gameMode != AnalyzeMode) {
12868         EditGameEvent();
12869         if (gameMode != EditGame) return;
12870         ResurrectChessProgram();
12871         SendToProgram("analyze\n", &first);
12872         first.analyzing = TRUE;
12873         /*first.maybeThinking = TRUE;*/
12874         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12875         EngineOutputPopUp();
12876     }
12877     gameMode = AnalyzeFile;
12878     pausing = FALSE;
12879     ModeHighlight();
12880     SetGameInfo();
12881
12882     StartAnalysisClock();
12883     GetTimeMark(&lastNodeCountTime);
12884     lastNodeCount = 0;
12885     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
12886 }
12887
12888 void
12889 MachineWhiteEvent()
12890 {
12891     char buf[MSG_SIZ];
12892     char *bookHit = NULL;
12893
12894     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12895       return;
12896
12897
12898     if (gameMode == PlayFromGameFile ||
12899         gameMode == TwoMachinesPlay  ||
12900         gameMode == Training         ||
12901         gameMode == AnalyzeMode      ||
12902         gameMode == EndOfGame)
12903         EditGameEvent();
12904
12905     if (gameMode == EditPosition)
12906         EditPositionDone(TRUE);
12907
12908     if (!WhiteOnMove(currentMove)) {
12909         DisplayError(_("It is not White's turn"), 0);
12910         return;
12911     }
12912
12913     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12914       ExitAnalyzeMode();
12915
12916     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12917         gameMode == AnalyzeFile)
12918         TruncateGame();
12919
12920     ResurrectChessProgram();    /* in case it isn't running */
12921     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12922         gameMode = MachinePlaysWhite;
12923         ResetClocks();
12924     } else
12925     gameMode = MachinePlaysWhite;
12926     pausing = FALSE;
12927     ModeHighlight();
12928     SetGameInfo();
12929     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12930     DisplayTitle(buf);
12931     if (first.sendName) {
12932       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12933       SendToProgram(buf, &first);
12934     }
12935     if (first.sendTime) {
12936       if (first.useColors) {
12937         SendToProgram("black\n", &first); /*gnu kludge*/
12938       }
12939       SendTimeRemaining(&first, TRUE);
12940     }
12941     if (first.useColors) {
12942       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12943     }
12944     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12945     SetMachineThinkingEnables();
12946     first.maybeThinking = TRUE;
12947     StartClocks();
12948     firstMove = FALSE;
12949
12950     if (appData.autoFlipView && !flipView) {
12951       flipView = !flipView;
12952       DrawPosition(FALSE, NULL);
12953       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12954     }
12955
12956     if(bookHit) { // [HGM] book: simulate book reply
12957         static char bookMove[MSG_SIZ]; // a bit generous?
12958
12959         programStats.nodes = programStats.depth = programStats.time =
12960         programStats.score = programStats.got_only_move = 0;
12961         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12962
12963         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12964         strcat(bookMove, bookHit);
12965         HandleMachineMove(bookMove, &first);
12966     }
12967 }
12968
12969 void
12970 MachineBlackEvent()
12971 {
12972   char buf[MSG_SIZ];
12973   char *bookHit = NULL;
12974
12975     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12976         return;
12977
12978
12979     if (gameMode == PlayFromGameFile ||
12980         gameMode == TwoMachinesPlay  ||
12981         gameMode == Training         ||
12982         gameMode == AnalyzeMode      ||
12983         gameMode == EndOfGame)
12984         EditGameEvent();
12985
12986     if (gameMode == EditPosition)
12987         EditPositionDone(TRUE);
12988
12989     if (WhiteOnMove(currentMove)) {
12990         DisplayError(_("It is not Black's turn"), 0);
12991         return;
12992     }
12993
12994     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12995       ExitAnalyzeMode();
12996
12997     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12998         gameMode == AnalyzeFile)
12999         TruncateGame();
13000
13001     ResurrectChessProgram();    /* in case it isn't running */
13002     gameMode = MachinePlaysBlack;
13003     pausing = FALSE;
13004     ModeHighlight();
13005     SetGameInfo();
13006     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13007     DisplayTitle(buf);
13008     if (first.sendName) {
13009       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13010       SendToProgram(buf, &first);
13011     }
13012     if (first.sendTime) {
13013       if (first.useColors) {
13014         SendToProgram("white\n", &first); /*gnu kludge*/
13015       }
13016       SendTimeRemaining(&first, FALSE);
13017     }
13018     if (first.useColors) {
13019       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13020     }
13021     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13022     SetMachineThinkingEnables();
13023     first.maybeThinking = TRUE;
13024     StartClocks();
13025
13026     if (appData.autoFlipView && flipView) {
13027       flipView = !flipView;
13028       DrawPosition(FALSE, NULL);
13029       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13030     }
13031     if(bookHit) { // [HGM] book: simulate book reply
13032         static char bookMove[MSG_SIZ]; // a bit generous?
13033
13034         programStats.nodes = programStats.depth = programStats.time =
13035         programStats.score = programStats.got_only_move = 0;
13036         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13037
13038         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13039         strcat(bookMove, bookHit);
13040         HandleMachineMove(bookMove, &first);
13041     }
13042 }
13043
13044
13045 void
13046 DisplayTwoMachinesTitle()
13047 {
13048     char buf[MSG_SIZ];
13049     if (appData.matchGames > 0) {
13050         if(appData.tourneyFile[0]) {
13051           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13052                    gameInfo.white, gameInfo.black,
13053                    nextGame+1, appData.matchGames+1,
13054                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13055         } else 
13056         if (first.twoMachinesColor[0] == 'w') {
13057           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13058                    gameInfo.white, gameInfo.black,
13059                    first.matchWins, second.matchWins,
13060                    matchGame - 1 - (first.matchWins + second.matchWins));
13061         } else {
13062           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13063                    gameInfo.white, gameInfo.black,
13064                    second.matchWins, first.matchWins,
13065                    matchGame - 1 - (first.matchWins + second.matchWins));
13066         }
13067     } else {
13068       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13069     }
13070     DisplayTitle(buf);
13071 }
13072
13073 void
13074 SettingsMenuIfReady()
13075 {
13076   if (second.lastPing != second.lastPong) {
13077     DisplayMessage("", _("Waiting for second chess program"));
13078     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13079     return;
13080   }
13081   ThawUI();
13082   DisplayMessage("", "");
13083   SettingsPopUp(&second);
13084 }
13085
13086 int
13087 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13088 {
13089     char buf[MSG_SIZ];
13090     if (cps->pr == NULL) {
13091         StartChessProgram(cps);
13092         if (cps->protocolVersion == 1) {
13093           retry();
13094         } else {
13095           /* kludge: allow timeout for initial "feature" command */
13096           FreezeUI();
13097           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13098           DisplayMessage("", buf);
13099           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13100         }
13101         return 1;
13102     }
13103     return 0;
13104 }
13105
13106 void
13107 TwoMachinesEvent P((void))
13108 {
13109     int i;
13110     char buf[MSG_SIZ];
13111     ChessProgramState *onmove;
13112     char *bookHit = NULL;
13113     static int stalling = 0;
13114     TimeMark now;
13115     long wait;
13116
13117     if (appData.noChessProgram) return;
13118
13119     switch (gameMode) {
13120       case TwoMachinesPlay:
13121         return;
13122       case MachinePlaysWhite:
13123       case MachinePlaysBlack:
13124         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13125             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13126             return;
13127         }
13128         /* fall through */
13129       case BeginningOfGame:
13130       case PlayFromGameFile:
13131       case EndOfGame:
13132         EditGameEvent();
13133         if (gameMode != EditGame) return;
13134         break;
13135       case EditPosition:
13136         EditPositionDone(TRUE);
13137         break;
13138       case AnalyzeMode:
13139       case AnalyzeFile:
13140         ExitAnalyzeMode();
13141         break;
13142       case EditGame:
13143       default:
13144         break;
13145     }
13146
13147 //    forwardMostMove = currentMove;
13148     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13149
13150     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13151
13152     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13153     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13154       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13155       return;
13156     }
13157     if(!stalling) {
13158       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13159       SendToProgram("force\n", &second);
13160       stalling = 1;
13161       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13162       return;
13163     }
13164     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13165     if(appData.matchPause>10000 || appData.matchPause<10)
13166                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13167     wait = SubtractTimeMarks(&now, &pauseStart);
13168     if(wait < appData.matchPause) {
13169         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13170         return;
13171     }
13172     stalling = 0;
13173     DisplayMessage("", "");
13174     if (startedFromSetupPosition) {
13175         SendBoard(&second, backwardMostMove);
13176     if (appData.debugMode) {
13177         fprintf(debugFP, "Two Machines\n");
13178     }
13179     }
13180     for (i = backwardMostMove; i < forwardMostMove; i++) {
13181         SendMoveToProgram(i, &second);
13182     }
13183
13184     gameMode = TwoMachinesPlay;
13185     pausing = FALSE;
13186     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13187     SetGameInfo();
13188     DisplayTwoMachinesTitle();
13189     firstMove = TRUE;
13190     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13191         onmove = &first;
13192     } else {
13193         onmove = &second;
13194     }
13195     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13196     SendToProgram(first.computerString, &first);
13197     if (first.sendName) {
13198       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13199       SendToProgram(buf, &first);
13200     }
13201     SendToProgram(second.computerString, &second);
13202     if (second.sendName) {
13203       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13204       SendToProgram(buf, &second);
13205     }
13206
13207     ResetClocks();
13208     if (!first.sendTime || !second.sendTime) {
13209         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13210         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13211     }
13212     if (onmove->sendTime) {
13213       if (onmove->useColors) {
13214         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13215       }
13216       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13217     }
13218     if (onmove->useColors) {
13219       SendToProgram(onmove->twoMachinesColor, onmove);
13220     }
13221     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13222 //    SendToProgram("go\n", onmove);
13223     onmove->maybeThinking = TRUE;
13224     SetMachineThinkingEnables();
13225
13226     StartClocks();
13227
13228     if(bookHit) { // [HGM] book: simulate book reply
13229         static char bookMove[MSG_SIZ]; // a bit generous?
13230
13231         programStats.nodes = programStats.depth = programStats.time =
13232         programStats.score = programStats.got_only_move = 0;
13233         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13234
13235         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13236         strcat(bookMove, bookHit);
13237         savedMessage = bookMove; // args for deferred call
13238         savedState = onmove;
13239         ScheduleDelayedEvent(DeferredBookMove, 1);
13240     }
13241 }
13242
13243 void
13244 TrainingEvent()
13245 {
13246     if (gameMode == Training) {
13247       SetTrainingModeOff();
13248       gameMode = PlayFromGameFile;
13249       DisplayMessage("", _("Training mode off"));
13250     } else {
13251       gameMode = Training;
13252       animateTraining = appData.animate;
13253
13254       /* make sure we are not already at the end of the game */
13255       if (currentMove < forwardMostMove) {
13256         SetTrainingModeOn();
13257         DisplayMessage("", _("Training mode on"));
13258       } else {
13259         gameMode = PlayFromGameFile;
13260         DisplayError(_("Already at end of game"), 0);
13261       }
13262     }
13263     ModeHighlight();
13264 }
13265
13266 void
13267 IcsClientEvent()
13268 {
13269     if (!appData.icsActive) return;
13270     switch (gameMode) {
13271       case IcsPlayingWhite:
13272       case IcsPlayingBlack:
13273       case IcsObserving:
13274       case IcsIdle:
13275       case BeginningOfGame:
13276       case IcsExamining:
13277         return;
13278
13279       case EditGame:
13280         break;
13281
13282       case EditPosition:
13283         EditPositionDone(TRUE);
13284         break;
13285
13286       case AnalyzeMode:
13287       case AnalyzeFile:
13288         ExitAnalyzeMode();
13289         break;
13290
13291       default:
13292         EditGameEvent();
13293         break;
13294     }
13295
13296     gameMode = IcsIdle;
13297     ModeHighlight();
13298     return;
13299 }
13300
13301
13302 void
13303 EditGameEvent()
13304 {
13305     int i;
13306
13307     switch (gameMode) {
13308       case Training:
13309         SetTrainingModeOff();
13310         break;
13311       case MachinePlaysWhite:
13312       case MachinePlaysBlack:
13313       case BeginningOfGame:
13314         SendToProgram("force\n", &first);
13315         SetUserThinkingEnables();
13316         break;
13317       case PlayFromGameFile:
13318         (void) StopLoadGameTimer();
13319         if (gameFileFP != NULL) {
13320             gameFileFP = NULL;
13321         }
13322         break;
13323       case EditPosition:
13324         EditPositionDone(TRUE);
13325         break;
13326       case AnalyzeMode:
13327       case AnalyzeFile:
13328         ExitAnalyzeMode();
13329         SendToProgram("force\n", &first);
13330         break;
13331       case TwoMachinesPlay:
13332         GameEnds(EndOfFile, NULL, GE_PLAYER);
13333         ResurrectChessProgram();
13334         SetUserThinkingEnables();
13335         break;
13336       case EndOfGame:
13337         ResurrectChessProgram();
13338         break;
13339       case IcsPlayingBlack:
13340       case IcsPlayingWhite:
13341         DisplayError(_("Warning: You are still playing a game"), 0);
13342         break;
13343       case IcsObserving:
13344         DisplayError(_("Warning: You are still observing a game"), 0);
13345         break;
13346       case IcsExamining:
13347         DisplayError(_("Warning: You are still examining a game"), 0);
13348         break;
13349       case IcsIdle:
13350         break;
13351       case EditGame:
13352       default:
13353         return;
13354     }
13355
13356     pausing = FALSE;
13357     StopClocks();
13358     first.offeredDraw = second.offeredDraw = 0;
13359
13360     if (gameMode == PlayFromGameFile) {
13361         whiteTimeRemaining = timeRemaining[0][currentMove];
13362         blackTimeRemaining = timeRemaining[1][currentMove];
13363         DisplayTitle("");
13364     }
13365
13366     if (gameMode == MachinePlaysWhite ||
13367         gameMode == MachinePlaysBlack ||
13368         gameMode == TwoMachinesPlay ||
13369         gameMode == EndOfGame) {
13370         i = forwardMostMove;
13371         while (i > currentMove) {
13372             SendToProgram("undo\n", &first);
13373             i--;
13374         }
13375         whiteTimeRemaining = timeRemaining[0][currentMove];
13376         blackTimeRemaining = timeRemaining[1][currentMove];
13377         DisplayBothClocks();
13378         if (whiteFlag || blackFlag) {
13379             whiteFlag = blackFlag = 0;
13380         }
13381         DisplayTitle("");
13382     }
13383
13384     gameMode = EditGame;
13385     ModeHighlight();
13386     SetGameInfo();
13387 }
13388
13389
13390 void
13391 EditPositionEvent()
13392 {
13393     if (gameMode == EditPosition) {
13394         EditGameEvent();
13395         return;
13396     }
13397
13398     EditGameEvent();
13399     if (gameMode != EditGame) return;
13400
13401     gameMode = EditPosition;
13402     ModeHighlight();
13403     SetGameInfo();
13404     if (currentMove > 0)
13405       CopyBoard(boards[0], boards[currentMove]);
13406
13407     blackPlaysFirst = !WhiteOnMove(currentMove);
13408     ResetClocks();
13409     currentMove = forwardMostMove = backwardMostMove = 0;
13410     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13411     DisplayMove(-1);
13412 }
13413
13414 void
13415 ExitAnalyzeMode()
13416 {
13417     /* [DM] icsEngineAnalyze - possible call from other functions */
13418     if (appData.icsEngineAnalyze) {
13419         appData.icsEngineAnalyze = FALSE;
13420
13421         DisplayMessage("",_("Close ICS engine analyze..."));
13422     }
13423     if (first.analysisSupport && first.analyzing) {
13424       SendToProgram("exit\n", &first);
13425       first.analyzing = FALSE;
13426     }
13427     thinkOutput[0] = NULLCHAR;
13428 }
13429
13430 void
13431 EditPositionDone(Boolean fakeRights)
13432 {
13433     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13434
13435     startedFromSetupPosition = TRUE;
13436     InitChessProgram(&first, FALSE);
13437     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13438       boards[0][EP_STATUS] = EP_NONE;
13439       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13440     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13441         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13442         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13443       } else boards[0][CASTLING][2] = NoRights;
13444     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13445         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13446         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13447       } else boards[0][CASTLING][5] = NoRights;
13448     }
13449     SendToProgram("force\n", &first);
13450     if (blackPlaysFirst) {
13451         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13452         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13453         currentMove = forwardMostMove = backwardMostMove = 1;
13454         CopyBoard(boards[1], boards[0]);
13455     } else {
13456         currentMove = forwardMostMove = backwardMostMove = 0;
13457     }
13458     SendBoard(&first, forwardMostMove);
13459     if (appData.debugMode) {
13460         fprintf(debugFP, "EditPosDone\n");
13461     }
13462     DisplayTitle("");
13463     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13464     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13465     gameMode = EditGame;
13466     ModeHighlight();
13467     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13468     ClearHighlights(); /* [AS] */
13469 }
13470
13471 /* Pause for `ms' milliseconds */
13472 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13473 void
13474 TimeDelay(ms)
13475      long ms;
13476 {
13477     TimeMark m1, m2;
13478
13479     GetTimeMark(&m1);
13480     do {
13481         GetTimeMark(&m2);
13482     } while (SubtractTimeMarks(&m2, &m1) < ms);
13483 }
13484
13485 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13486 void
13487 SendMultiLineToICS(buf)
13488      char *buf;
13489 {
13490     char temp[MSG_SIZ+1], *p;
13491     int len;
13492
13493     len = strlen(buf);
13494     if (len > MSG_SIZ)
13495       len = MSG_SIZ;
13496
13497     strncpy(temp, buf, len);
13498     temp[len] = 0;
13499
13500     p = temp;
13501     while (*p) {
13502         if (*p == '\n' || *p == '\r')
13503           *p = ' ';
13504         ++p;
13505     }
13506
13507     strcat(temp, "\n");
13508     SendToICS(temp);
13509     SendToPlayer(temp, strlen(temp));
13510 }
13511
13512 void
13513 SetWhiteToPlayEvent()
13514 {
13515     if (gameMode == EditPosition) {
13516         blackPlaysFirst = FALSE;
13517         DisplayBothClocks();    /* works because currentMove is 0 */
13518     } else if (gameMode == IcsExamining) {
13519         SendToICS(ics_prefix);
13520         SendToICS("tomove white\n");
13521     }
13522 }
13523
13524 void
13525 SetBlackToPlayEvent()
13526 {
13527     if (gameMode == EditPosition) {
13528         blackPlaysFirst = TRUE;
13529         currentMove = 1;        /* kludge */
13530         DisplayBothClocks();
13531         currentMove = 0;
13532     } else if (gameMode == IcsExamining) {
13533         SendToICS(ics_prefix);
13534         SendToICS("tomove black\n");
13535     }
13536 }
13537
13538 void
13539 EditPositionMenuEvent(selection, x, y)
13540      ChessSquare selection;
13541      int x, y;
13542 {
13543     char buf[MSG_SIZ];
13544     ChessSquare piece = boards[0][y][x];
13545
13546     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13547
13548     switch (selection) {
13549       case ClearBoard:
13550         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13551             SendToICS(ics_prefix);
13552             SendToICS("bsetup clear\n");
13553         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13554             SendToICS(ics_prefix);
13555             SendToICS("clearboard\n");
13556         } else {
13557             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13558                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13559                 for (y = 0; y < BOARD_HEIGHT; y++) {
13560                     if (gameMode == IcsExamining) {
13561                         if (boards[currentMove][y][x] != EmptySquare) {
13562                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13563                                     AAA + x, ONE + y);
13564                             SendToICS(buf);
13565                         }
13566                     } else {
13567                         boards[0][y][x] = p;
13568                     }
13569                 }
13570             }
13571         }
13572         if (gameMode == EditPosition) {
13573             DrawPosition(FALSE, boards[0]);
13574         }
13575         break;
13576
13577       case WhitePlay:
13578         SetWhiteToPlayEvent();
13579         break;
13580
13581       case BlackPlay:
13582         SetBlackToPlayEvent();
13583         break;
13584
13585       case EmptySquare:
13586         if (gameMode == IcsExamining) {
13587             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13588             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13589             SendToICS(buf);
13590         } else {
13591             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13592                 if(x == BOARD_LEFT-2) {
13593                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13594                     boards[0][y][1] = 0;
13595                 } else
13596                 if(x == BOARD_RGHT+1) {
13597                     if(y >= gameInfo.holdingsSize) break;
13598                     boards[0][y][BOARD_WIDTH-2] = 0;
13599                 } else break;
13600             }
13601             boards[0][y][x] = EmptySquare;
13602             DrawPosition(FALSE, boards[0]);
13603         }
13604         break;
13605
13606       case PromotePiece:
13607         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13608            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13609             selection = (ChessSquare) (PROMOTED piece);
13610         } else if(piece == EmptySquare) selection = WhiteSilver;
13611         else selection = (ChessSquare)((int)piece - 1);
13612         goto defaultlabel;
13613
13614       case DemotePiece:
13615         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13616            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13617             selection = (ChessSquare) (DEMOTED piece);
13618         } else if(piece == EmptySquare) selection = BlackSilver;
13619         else selection = (ChessSquare)((int)piece + 1);
13620         goto defaultlabel;
13621
13622       case WhiteQueen:
13623       case BlackQueen:
13624         if(gameInfo.variant == VariantShatranj ||
13625            gameInfo.variant == VariantXiangqi  ||
13626            gameInfo.variant == VariantCourier  ||
13627            gameInfo.variant == VariantMakruk     )
13628             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13629         goto defaultlabel;
13630
13631       case WhiteKing:
13632       case BlackKing:
13633         if(gameInfo.variant == VariantXiangqi)
13634             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13635         if(gameInfo.variant == VariantKnightmate)
13636             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13637       default:
13638         defaultlabel:
13639         if (gameMode == IcsExamining) {
13640             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13641             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13642                      PieceToChar(selection), AAA + x, ONE + y);
13643             SendToICS(buf);
13644         } else {
13645             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13646                 int n;
13647                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13648                     n = PieceToNumber(selection - BlackPawn);
13649                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13650                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13651                     boards[0][BOARD_HEIGHT-1-n][1]++;
13652                 } else
13653                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13654                     n = PieceToNumber(selection);
13655                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13656                     boards[0][n][BOARD_WIDTH-1] = selection;
13657                     boards[0][n][BOARD_WIDTH-2]++;
13658                 }
13659             } else
13660             boards[0][y][x] = selection;
13661             DrawPosition(TRUE, boards[0]);
13662         }
13663         break;
13664     }
13665 }
13666
13667
13668 void
13669 DropMenuEvent(selection, x, y)
13670      ChessSquare selection;
13671      int x, y;
13672 {
13673     ChessMove moveType;
13674
13675     switch (gameMode) {
13676       case IcsPlayingWhite:
13677       case MachinePlaysBlack:
13678         if (!WhiteOnMove(currentMove)) {
13679             DisplayMoveError(_("It is Black's turn"));
13680             return;
13681         }
13682         moveType = WhiteDrop;
13683         break;
13684       case IcsPlayingBlack:
13685       case MachinePlaysWhite:
13686         if (WhiteOnMove(currentMove)) {
13687             DisplayMoveError(_("It is White's turn"));
13688             return;
13689         }
13690         moveType = BlackDrop;
13691         break;
13692       case EditGame:
13693         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13694         break;
13695       default:
13696         return;
13697     }
13698
13699     if (moveType == BlackDrop && selection < BlackPawn) {
13700       selection = (ChessSquare) ((int) selection
13701                                  + (int) BlackPawn - (int) WhitePawn);
13702     }
13703     if (boards[currentMove][y][x] != EmptySquare) {
13704         DisplayMoveError(_("That square is occupied"));
13705         return;
13706     }
13707
13708     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13709 }
13710
13711 void
13712 AcceptEvent()
13713 {
13714     /* Accept a pending offer of any kind from opponent */
13715
13716     if (appData.icsActive) {
13717         SendToICS(ics_prefix);
13718         SendToICS("accept\n");
13719     } else if (cmailMsgLoaded) {
13720         if (currentMove == cmailOldMove &&
13721             commentList[cmailOldMove] != NULL &&
13722             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13723                    "Black offers a draw" : "White offers a draw")) {
13724             TruncateGame();
13725             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13726             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13727         } else {
13728             DisplayError(_("There is no pending offer on this move"), 0);
13729             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13730         }
13731     } else {
13732         /* Not used for offers from chess program */
13733     }
13734 }
13735
13736 void
13737 DeclineEvent()
13738 {
13739     /* Decline a pending offer of any kind from opponent */
13740
13741     if (appData.icsActive) {
13742         SendToICS(ics_prefix);
13743         SendToICS("decline\n");
13744     } else if (cmailMsgLoaded) {
13745         if (currentMove == cmailOldMove &&
13746             commentList[cmailOldMove] != NULL &&
13747             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13748                    "Black offers a draw" : "White offers a draw")) {
13749 #ifdef NOTDEF
13750             AppendComment(cmailOldMove, "Draw declined", TRUE);
13751             DisplayComment(cmailOldMove - 1, "Draw declined");
13752 #endif /*NOTDEF*/
13753         } else {
13754             DisplayError(_("There is no pending offer on this move"), 0);
13755         }
13756     } else {
13757         /* Not used for offers from chess program */
13758     }
13759 }
13760
13761 void
13762 RematchEvent()
13763 {
13764     /* Issue ICS rematch command */
13765     if (appData.icsActive) {
13766         SendToICS(ics_prefix);
13767         SendToICS("rematch\n");
13768     }
13769 }
13770
13771 void
13772 CallFlagEvent()
13773 {
13774     /* Call your opponent's flag (claim a win on time) */
13775     if (appData.icsActive) {
13776         SendToICS(ics_prefix);
13777         SendToICS("flag\n");
13778     } else {
13779         switch (gameMode) {
13780           default:
13781             return;
13782           case MachinePlaysWhite:
13783             if (whiteFlag) {
13784                 if (blackFlag)
13785                   GameEnds(GameIsDrawn, "Both players ran out of time",
13786                            GE_PLAYER);
13787                 else
13788                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13789             } else {
13790                 DisplayError(_("Your opponent is not out of time"), 0);
13791             }
13792             break;
13793           case MachinePlaysBlack:
13794             if (blackFlag) {
13795                 if (whiteFlag)
13796                   GameEnds(GameIsDrawn, "Both players ran out of time",
13797                            GE_PLAYER);
13798                 else
13799                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13800             } else {
13801                 DisplayError(_("Your opponent is not out of time"), 0);
13802             }
13803             break;
13804         }
13805     }
13806 }
13807
13808 void
13809 ClockClick(int which)
13810 {       // [HGM] code moved to back-end from winboard.c
13811         if(which) { // black clock
13812           if (gameMode == EditPosition || gameMode == IcsExamining) {
13813             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13814             SetBlackToPlayEvent();
13815           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13816           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13817           } else if (shiftKey) {
13818             AdjustClock(which, -1);
13819           } else if (gameMode == IcsPlayingWhite ||
13820                      gameMode == MachinePlaysBlack) {
13821             CallFlagEvent();
13822           }
13823         } else { // white clock
13824           if (gameMode == EditPosition || gameMode == IcsExamining) {
13825             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13826             SetWhiteToPlayEvent();
13827           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13828           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13829           } else if (shiftKey) {
13830             AdjustClock(which, -1);
13831           } else if (gameMode == IcsPlayingBlack ||
13832                    gameMode == MachinePlaysWhite) {
13833             CallFlagEvent();
13834           }
13835         }
13836 }
13837
13838 void
13839 DrawEvent()
13840 {
13841     /* Offer draw or accept pending draw offer from opponent */
13842
13843     if (appData.icsActive) {
13844         /* Note: tournament rules require draw offers to be
13845            made after you make your move but before you punch
13846            your clock.  Currently ICS doesn't let you do that;
13847            instead, you immediately punch your clock after making
13848            a move, but you can offer a draw at any time. */
13849
13850         SendToICS(ics_prefix);
13851         SendToICS("draw\n");
13852         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13853     } else if (cmailMsgLoaded) {
13854         if (currentMove == cmailOldMove &&
13855             commentList[cmailOldMove] != NULL &&
13856             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13857                    "Black offers a draw" : "White offers a draw")) {
13858             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13859             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13860         } else if (currentMove == cmailOldMove + 1) {
13861             char *offer = WhiteOnMove(cmailOldMove) ?
13862               "White offers a draw" : "Black offers a draw";
13863             AppendComment(currentMove, offer, TRUE);
13864             DisplayComment(currentMove - 1, offer);
13865             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13866         } else {
13867             DisplayError(_("You must make your move before offering a draw"), 0);
13868             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13869         }
13870     } else if (first.offeredDraw) {
13871         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13872     } else {
13873         if (first.sendDrawOffers) {
13874             SendToProgram("draw\n", &first);
13875             userOfferedDraw = TRUE;
13876         }
13877     }
13878 }
13879
13880 void
13881 AdjournEvent()
13882 {
13883     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13884
13885     if (appData.icsActive) {
13886         SendToICS(ics_prefix);
13887         SendToICS("adjourn\n");
13888     } else {
13889         /* Currently GNU Chess doesn't offer or accept Adjourns */
13890     }
13891 }
13892
13893
13894 void
13895 AbortEvent()
13896 {
13897     /* Offer Abort or accept pending Abort offer from opponent */
13898
13899     if (appData.icsActive) {
13900         SendToICS(ics_prefix);
13901         SendToICS("abort\n");
13902     } else {
13903         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13904     }
13905 }
13906
13907 void
13908 ResignEvent()
13909 {
13910     /* Resign.  You can do this even if it's not your turn. */
13911
13912     if (appData.icsActive) {
13913         SendToICS(ics_prefix);
13914         SendToICS("resign\n");
13915     } else {
13916         switch (gameMode) {
13917           case MachinePlaysWhite:
13918             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13919             break;
13920           case MachinePlaysBlack:
13921             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13922             break;
13923           case EditGame:
13924             if (cmailMsgLoaded) {
13925                 TruncateGame();
13926                 if (WhiteOnMove(cmailOldMove)) {
13927                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13928                 } else {
13929                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13930                 }
13931                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13932             }
13933             break;
13934           default:
13935             break;
13936         }
13937     }
13938 }
13939
13940
13941 void
13942 StopObservingEvent()
13943 {
13944     /* Stop observing current games */
13945     SendToICS(ics_prefix);
13946     SendToICS("unobserve\n");
13947 }
13948
13949 void
13950 StopExaminingEvent()
13951 {
13952     /* Stop observing current game */
13953     SendToICS(ics_prefix);
13954     SendToICS("unexamine\n");
13955 }
13956
13957 void
13958 ForwardInner(target)
13959      int target;
13960 {
13961     int limit;
13962
13963     if (appData.debugMode)
13964         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13965                 target, currentMove, forwardMostMove);
13966
13967     if (gameMode == EditPosition)
13968       return;
13969
13970     if (gameMode == PlayFromGameFile && !pausing)
13971       PauseEvent();
13972
13973     if (gameMode == IcsExamining && pausing)
13974       limit = pauseExamForwardMostMove;
13975     else
13976       limit = forwardMostMove;
13977
13978     if (target > limit) target = limit;
13979
13980     if (target > 0 && moveList[target - 1][0]) {
13981         int fromX, fromY, toX, toY;
13982         toX = moveList[target - 1][2] - AAA;
13983         toY = moveList[target - 1][3] - ONE;
13984         if (moveList[target - 1][1] == '@') {
13985             if (appData.highlightLastMove) {
13986                 SetHighlights(-1, -1, toX, toY);
13987             }
13988         } else {
13989             fromX = moveList[target - 1][0] - AAA;
13990             fromY = moveList[target - 1][1] - ONE;
13991             if (target == currentMove + 1) {
13992                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13993             }
13994             if (appData.highlightLastMove) {
13995                 SetHighlights(fromX, fromY, toX, toY);
13996             }
13997         }
13998     }
13999     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14000         gameMode == Training || gameMode == PlayFromGameFile ||
14001         gameMode == AnalyzeFile) {
14002         while (currentMove < target) {
14003             SendMoveToProgram(currentMove++, &first);
14004         }
14005     } else {
14006         currentMove = target;
14007     }
14008
14009     if (gameMode == EditGame || gameMode == EndOfGame) {
14010         whiteTimeRemaining = timeRemaining[0][currentMove];
14011         blackTimeRemaining = timeRemaining[1][currentMove];
14012     }
14013     DisplayBothClocks();
14014     DisplayMove(currentMove - 1);
14015     DrawPosition(FALSE, boards[currentMove]);
14016     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14017     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14018         DisplayComment(currentMove - 1, commentList[currentMove]);
14019     }
14020     DisplayBook(currentMove);
14021 }
14022
14023
14024 void
14025 ForwardEvent()
14026 {
14027     if (gameMode == IcsExamining && !pausing) {
14028         SendToICS(ics_prefix);
14029         SendToICS("forward\n");
14030     } else {
14031         ForwardInner(currentMove + 1);
14032     }
14033 }
14034
14035 void
14036 ToEndEvent()
14037 {
14038     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14039         /* to optimze, we temporarily turn off analysis mode while we feed
14040          * the remaining moves to the engine. Otherwise we get analysis output
14041          * after each move.
14042          */
14043         if (first.analysisSupport) {
14044           SendToProgram("exit\nforce\n", &first);
14045           first.analyzing = FALSE;
14046         }
14047     }
14048
14049     if (gameMode == IcsExamining && !pausing) {
14050         SendToICS(ics_prefix);
14051         SendToICS("forward 999999\n");
14052     } else {
14053         ForwardInner(forwardMostMove);
14054     }
14055
14056     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14057         /* we have fed all the moves, so reactivate analysis mode */
14058         SendToProgram("analyze\n", &first);
14059         first.analyzing = TRUE;
14060         /*first.maybeThinking = TRUE;*/
14061         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14062     }
14063 }
14064
14065 void
14066 BackwardInner(target)
14067      int target;
14068 {
14069     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14070
14071     if (appData.debugMode)
14072         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14073                 target, currentMove, forwardMostMove);
14074
14075     if (gameMode == EditPosition) return;
14076     if (currentMove <= backwardMostMove) {
14077         ClearHighlights();
14078         DrawPosition(full_redraw, boards[currentMove]);
14079         return;
14080     }
14081     if (gameMode == PlayFromGameFile && !pausing)
14082       PauseEvent();
14083
14084     if (moveList[target][0]) {
14085         int fromX, fromY, toX, toY;
14086         toX = moveList[target][2] - AAA;
14087         toY = moveList[target][3] - ONE;
14088         if (moveList[target][1] == '@') {
14089             if (appData.highlightLastMove) {
14090                 SetHighlights(-1, -1, toX, toY);
14091             }
14092         } else {
14093             fromX = moveList[target][0] - AAA;
14094             fromY = moveList[target][1] - ONE;
14095             if (target == currentMove - 1) {
14096                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14097             }
14098             if (appData.highlightLastMove) {
14099                 SetHighlights(fromX, fromY, toX, toY);
14100             }
14101         }
14102     }
14103     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14104         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14105         while (currentMove > target) {
14106             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14107                 // null move cannot be undone. Reload program with move history before it.
14108                 int i;
14109                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14110                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14111                 }
14112                 SendBoard(&first, i); 
14113                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14114                 break;
14115             }
14116             SendToProgram("undo\n", &first);
14117             currentMove--;
14118         }
14119     } else {
14120         currentMove = target;
14121     }
14122
14123     if (gameMode == EditGame || gameMode == EndOfGame) {
14124         whiteTimeRemaining = timeRemaining[0][currentMove];
14125         blackTimeRemaining = timeRemaining[1][currentMove];
14126     }
14127     DisplayBothClocks();
14128     DisplayMove(currentMove - 1);
14129     DrawPosition(full_redraw, boards[currentMove]);
14130     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14131     // [HGM] PV info: routine tests if comment empty
14132     DisplayComment(currentMove - 1, commentList[currentMove]);
14133     DisplayBook(currentMove);
14134 }
14135
14136 void
14137 BackwardEvent()
14138 {
14139     if (gameMode == IcsExamining && !pausing) {
14140         SendToICS(ics_prefix);
14141         SendToICS("backward\n");
14142     } else {
14143         BackwardInner(currentMove - 1);
14144     }
14145 }
14146
14147 void
14148 ToStartEvent()
14149 {
14150     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14151         /* to optimize, we temporarily turn off analysis mode while we undo
14152          * all the moves. Otherwise we get analysis output after each undo.
14153          */
14154         if (first.analysisSupport) {
14155           SendToProgram("exit\nforce\n", &first);
14156           first.analyzing = FALSE;
14157         }
14158     }
14159
14160     if (gameMode == IcsExamining && !pausing) {
14161         SendToICS(ics_prefix);
14162         SendToICS("backward 999999\n");
14163     } else {
14164         BackwardInner(backwardMostMove);
14165     }
14166
14167     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14168         /* we have fed all the moves, so reactivate analysis mode */
14169         SendToProgram("analyze\n", &first);
14170         first.analyzing = TRUE;
14171         /*first.maybeThinking = TRUE;*/
14172         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14173     }
14174 }
14175
14176 void
14177 ToNrEvent(int to)
14178 {
14179   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14180   if (to >= forwardMostMove) to = forwardMostMove;
14181   if (to <= backwardMostMove) to = backwardMostMove;
14182   if (to < currentMove) {
14183     BackwardInner(to);
14184   } else {
14185     ForwardInner(to);
14186   }
14187 }
14188
14189 void
14190 RevertEvent(Boolean annotate)
14191 {
14192     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14193         return;
14194     }
14195     if (gameMode != IcsExamining) {
14196         DisplayError(_("You are not examining a game"), 0);
14197         return;
14198     }
14199     if (pausing) {
14200         DisplayError(_("You can't revert while pausing"), 0);
14201         return;
14202     }
14203     SendToICS(ics_prefix);
14204     SendToICS("revert\n");
14205 }
14206
14207 void
14208 RetractMoveEvent()
14209 {
14210     switch (gameMode) {
14211       case MachinePlaysWhite:
14212       case MachinePlaysBlack:
14213         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14214             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14215             return;
14216         }
14217         if (forwardMostMove < 2) return;
14218         currentMove = forwardMostMove = forwardMostMove - 2;
14219         whiteTimeRemaining = timeRemaining[0][currentMove];
14220         blackTimeRemaining = timeRemaining[1][currentMove];
14221         DisplayBothClocks();
14222         DisplayMove(currentMove - 1);
14223         ClearHighlights();/*!! could figure this out*/
14224         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14225         SendToProgram("remove\n", &first);
14226         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14227         break;
14228
14229       case BeginningOfGame:
14230       default:
14231         break;
14232
14233       case IcsPlayingWhite:
14234       case IcsPlayingBlack:
14235         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14236             SendToICS(ics_prefix);
14237             SendToICS("takeback 2\n");
14238         } else {
14239             SendToICS(ics_prefix);
14240             SendToICS("takeback 1\n");
14241         }
14242         break;
14243     }
14244 }
14245
14246 void
14247 MoveNowEvent()
14248 {
14249     ChessProgramState *cps;
14250
14251     switch (gameMode) {
14252       case MachinePlaysWhite:
14253         if (!WhiteOnMove(forwardMostMove)) {
14254             DisplayError(_("It is your turn"), 0);
14255             return;
14256         }
14257         cps = &first;
14258         break;
14259       case MachinePlaysBlack:
14260         if (WhiteOnMove(forwardMostMove)) {
14261             DisplayError(_("It is your turn"), 0);
14262             return;
14263         }
14264         cps = &first;
14265         break;
14266       case TwoMachinesPlay:
14267         if (WhiteOnMove(forwardMostMove) ==
14268             (first.twoMachinesColor[0] == 'w')) {
14269             cps = &first;
14270         } else {
14271             cps = &second;
14272         }
14273         break;
14274       case BeginningOfGame:
14275       default:
14276         return;
14277     }
14278     SendToProgram("?\n", cps);
14279 }
14280
14281 void
14282 TruncateGameEvent()
14283 {
14284     EditGameEvent();
14285     if (gameMode != EditGame) return;
14286     TruncateGame();
14287 }
14288
14289 void
14290 TruncateGame()
14291 {
14292     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14293     if (forwardMostMove > currentMove) {
14294         if (gameInfo.resultDetails != NULL) {
14295             free(gameInfo.resultDetails);
14296             gameInfo.resultDetails = NULL;
14297             gameInfo.result = GameUnfinished;
14298         }
14299         forwardMostMove = currentMove;
14300         HistorySet(parseList, backwardMostMove, forwardMostMove,
14301                    currentMove-1);
14302     }
14303 }
14304
14305 void
14306 HintEvent()
14307 {
14308     if (appData.noChessProgram) return;
14309     switch (gameMode) {
14310       case MachinePlaysWhite:
14311         if (WhiteOnMove(forwardMostMove)) {
14312             DisplayError(_("Wait until your turn"), 0);
14313             return;
14314         }
14315         break;
14316       case BeginningOfGame:
14317       case MachinePlaysBlack:
14318         if (!WhiteOnMove(forwardMostMove)) {
14319             DisplayError(_("Wait until your turn"), 0);
14320             return;
14321         }
14322         break;
14323       default:
14324         DisplayError(_("No hint available"), 0);
14325         return;
14326     }
14327     SendToProgram("hint\n", &first);
14328     hintRequested = TRUE;
14329 }
14330
14331 void
14332 BookEvent()
14333 {
14334     if (appData.noChessProgram) return;
14335     switch (gameMode) {
14336       case MachinePlaysWhite:
14337         if (WhiteOnMove(forwardMostMove)) {
14338             DisplayError(_("Wait until your turn"), 0);
14339             return;
14340         }
14341         break;
14342       case BeginningOfGame:
14343       case MachinePlaysBlack:
14344         if (!WhiteOnMove(forwardMostMove)) {
14345             DisplayError(_("Wait until your turn"), 0);
14346             return;
14347         }
14348         break;
14349       case EditPosition:
14350         EditPositionDone(TRUE);
14351         break;
14352       case TwoMachinesPlay:
14353         return;
14354       default:
14355         break;
14356     }
14357     SendToProgram("bk\n", &first);
14358     bookOutput[0] = NULLCHAR;
14359     bookRequested = TRUE;
14360 }
14361
14362 void
14363 AboutGameEvent()
14364 {
14365     char *tags = PGNTags(&gameInfo);
14366     TagsPopUp(tags, CmailMsg());
14367     free(tags);
14368 }
14369
14370 /* end button procedures */
14371
14372 void
14373 PrintPosition(fp, move)
14374      FILE *fp;
14375      int move;
14376 {
14377     int i, j;
14378
14379     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14380         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14381             char c = PieceToChar(boards[move][i][j]);
14382             fputc(c == 'x' ? '.' : c, fp);
14383             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14384         }
14385     }
14386     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14387       fprintf(fp, "white to play\n");
14388     else
14389       fprintf(fp, "black to play\n");
14390 }
14391
14392 void
14393 PrintOpponents(fp)
14394      FILE *fp;
14395 {
14396     if (gameInfo.white != NULL) {
14397         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14398     } else {
14399         fprintf(fp, "\n");
14400     }
14401 }
14402
14403 /* Find last component of program's own name, using some heuristics */
14404 void
14405 TidyProgramName(prog, host, buf)
14406      char *prog, *host, buf[MSG_SIZ];
14407 {
14408     char *p, *q;
14409     int local = (strcmp(host, "localhost") == 0);
14410     while (!local && (p = strchr(prog, ';')) != NULL) {
14411         p++;
14412         while (*p == ' ') p++;
14413         prog = p;
14414     }
14415     if (*prog == '"' || *prog == '\'') {
14416         q = strchr(prog + 1, *prog);
14417     } else {
14418         q = strchr(prog, ' ');
14419     }
14420     if (q == NULL) q = prog + strlen(prog);
14421     p = q;
14422     while (p >= prog && *p != '/' && *p != '\\') p--;
14423     p++;
14424     if(p == prog && *p == '"') p++;
14425     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14426     memcpy(buf, p, q - p);
14427     buf[q - p] = NULLCHAR;
14428     if (!local) {
14429         strcat(buf, "@");
14430         strcat(buf, host);
14431     }
14432 }
14433
14434 char *
14435 TimeControlTagValue()
14436 {
14437     char buf[MSG_SIZ];
14438     if (!appData.clockMode) {
14439       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14440     } else if (movesPerSession > 0) {
14441       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14442     } else if (timeIncrement == 0) {
14443       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14444     } else {
14445       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14446     }
14447     return StrSave(buf);
14448 }
14449
14450 void
14451 SetGameInfo()
14452 {
14453     /* This routine is used only for certain modes */
14454     VariantClass v = gameInfo.variant;
14455     ChessMove r = GameUnfinished;
14456     char *p = NULL;
14457
14458     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14459         r = gameInfo.result;
14460         p = gameInfo.resultDetails;
14461         gameInfo.resultDetails = NULL;
14462     }
14463     ClearGameInfo(&gameInfo);
14464     gameInfo.variant = v;
14465
14466     switch (gameMode) {
14467       case MachinePlaysWhite:
14468         gameInfo.event = StrSave( appData.pgnEventHeader );
14469         gameInfo.site = StrSave(HostName());
14470         gameInfo.date = PGNDate();
14471         gameInfo.round = StrSave("-");
14472         gameInfo.white = StrSave(first.tidy);
14473         gameInfo.black = StrSave(UserName());
14474         gameInfo.timeControl = TimeControlTagValue();
14475         break;
14476
14477       case MachinePlaysBlack:
14478         gameInfo.event = StrSave( appData.pgnEventHeader );
14479         gameInfo.site = StrSave(HostName());
14480         gameInfo.date = PGNDate();
14481         gameInfo.round = StrSave("-");
14482         gameInfo.white = StrSave(UserName());
14483         gameInfo.black = StrSave(first.tidy);
14484         gameInfo.timeControl = TimeControlTagValue();
14485         break;
14486
14487       case TwoMachinesPlay:
14488         gameInfo.event = StrSave( appData.pgnEventHeader );
14489         gameInfo.site = StrSave(HostName());
14490         gameInfo.date = PGNDate();
14491         if (roundNr > 0) {
14492             char buf[MSG_SIZ];
14493             snprintf(buf, MSG_SIZ, "%d", roundNr);
14494             gameInfo.round = StrSave(buf);
14495         } else {
14496             gameInfo.round = StrSave("-");
14497         }
14498         if (first.twoMachinesColor[0] == 'w') {
14499             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14500             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14501         } else {
14502             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14503             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14504         }
14505         gameInfo.timeControl = TimeControlTagValue();
14506         break;
14507
14508       case EditGame:
14509         gameInfo.event = StrSave("Edited game");
14510         gameInfo.site = StrSave(HostName());
14511         gameInfo.date = PGNDate();
14512         gameInfo.round = StrSave("-");
14513         gameInfo.white = StrSave("-");
14514         gameInfo.black = StrSave("-");
14515         gameInfo.result = r;
14516         gameInfo.resultDetails = p;
14517         break;
14518
14519       case EditPosition:
14520         gameInfo.event = StrSave("Edited position");
14521         gameInfo.site = StrSave(HostName());
14522         gameInfo.date = PGNDate();
14523         gameInfo.round = StrSave("-");
14524         gameInfo.white = StrSave("-");
14525         gameInfo.black = StrSave("-");
14526         break;
14527
14528       case IcsPlayingWhite:
14529       case IcsPlayingBlack:
14530       case IcsObserving:
14531       case IcsExamining:
14532         break;
14533
14534       case PlayFromGameFile:
14535         gameInfo.event = StrSave("Game from non-PGN file");
14536         gameInfo.site = StrSave(HostName());
14537         gameInfo.date = PGNDate();
14538         gameInfo.round = StrSave("-");
14539         gameInfo.white = StrSave("?");
14540         gameInfo.black = StrSave("?");
14541         break;
14542
14543       default:
14544         break;
14545     }
14546 }
14547
14548 void
14549 ReplaceComment(index, text)
14550      int index;
14551      char *text;
14552 {
14553     int len;
14554     char *p;
14555     float score;
14556
14557     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14558        pvInfoList[index-1].depth == len &&
14559        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14560        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14561     while (*text == '\n') text++;
14562     len = strlen(text);
14563     while (len > 0 && text[len - 1] == '\n') len--;
14564
14565     if (commentList[index] != NULL)
14566       free(commentList[index]);
14567
14568     if (len == 0) {
14569         commentList[index] = NULL;
14570         return;
14571     }
14572   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14573       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14574       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14575     commentList[index] = (char *) malloc(len + 2);
14576     strncpy(commentList[index], text, len);
14577     commentList[index][len] = '\n';
14578     commentList[index][len + 1] = NULLCHAR;
14579   } else {
14580     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14581     char *p;
14582     commentList[index] = (char *) malloc(len + 7);
14583     safeStrCpy(commentList[index], "{\n", 3);
14584     safeStrCpy(commentList[index]+2, text, len+1);
14585     commentList[index][len+2] = NULLCHAR;
14586     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14587     strcat(commentList[index], "\n}\n");
14588   }
14589 }
14590
14591 void
14592 CrushCRs(text)
14593      char *text;
14594 {
14595   char *p = text;
14596   char *q = text;
14597   char ch;
14598
14599   do {
14600     ch = *p++;
14601     if (ch == '\r') continue;
14602     *q++ = ch;
14603   } while (ch != '\0');
14604 }
14605
14606 void
14607 AppendComment(index, text, addBraces)
14608      int index;
14609      char *text;
14610      Boolean addBraces; // [HGM] braces: tells if we should add {}
14611 {
14612     int oldlen, len;
14613     char *old;
14614
14615 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14616     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14617
14618     CrushCRs(text);
14619     while (*text == '\n') text++;
14620     len = strlen(text);
14621     while (len > 0 && text[len - 1] == '\n') len--;
14622
14623     if (len == 0) return;
14624
14625     if (commentList[index] != NULL) {
14626         old = commentList[index];
14627         oldlen = strlen(old);
14628         while(commentList[index][oldlen-1] ==  '\n')
14629           commentList[index][--oldlen] = NULLCHAR;
14630         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14631         safeStrCpy(commentList[index], old, oldlen + len + 6);
14632         free(old);
14633         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14634         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14635           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14636           while (*text == '\n') { text++; len--; }
14637           commentList[index][--oldlen] = NULLCHAR;
14638       }
14639         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14640         else          strcat(commentList[index], "\n");
14641         strcat(commentList[index], text);
14642         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14643         else          strcat(commentList[index], "\n");
14644     } else {
14645         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14646         if(addBraces)
14647           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14648         else commentList[index][0] = NULLCHAR;
14649         strcat(commentList[index], text);
14650         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14651         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14652     }
14653 }
14654
14655 static char * FindStr( char * text, char * sub_text )
14656 {
14657     char * result = strstr( text, sub_text );
14658
14659     if( result != NULL ) {
14660         result += strlen( sub_text );
14661     }
14662
14663     return result;
14664 }
14665
14666 /* [AS] Try to extract PV info from PGN comment */
14667 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14668 char *GetInfoFromComment( int index, char * text )
14669 {
14670     char * sep = text, *p;
14671
14672     if( text != NULL && index > 0 ) {
14673         int score = 0;
14674         int depth = 0;
14675         int time = -1, sec = 0, deci;
14676         char * s_eval = FindStr( text, "[%eval " );
14677         char * s_emt = FindStr( text, "[%emt " );
14678
14679         if( s_eval != NULL || s_emt != NULL ) {
14680             /* New style */
14681             char delim;
14682
14683             if( s_eval != NULL ) {
14684                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14685                     return text;
14686                 }
14687
14688                 if( delim != ']' ) {
14689                     return text;
14690                 }
14691             }
14692
14693             if( s_emt != NULL ) {
14694             }
14695                 return text;
14696         }
14697         else {
14698             /* We expect something like: [+|-]nnn.nn/dd */
14699             int score_lo = 0;
14700
14701             if(*text != '{') return text; // [HGM] braces: must be normal comment
14702
14703             sep = strchr( text, '/' );
14704             if( sep == NULL || sep < (text+4) ) {
14705                 return text;
14706             }
14707
14708             p = text;
14709             if(p[1] == '(') { // comment starts with PV
14710                p = strchr(p, ')'); // locate end of PV
14711                if(p == NULL || sep < p+5) return text;
14712                // at this point we have something like "{(.*) +0.23/6 ..."
14713                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14714                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14715                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14716             }
14717             time = -1; sec = -1; deci = -1;
14718             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14719                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14720                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14721                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14722                 return text;
14723             }
14724
14725             if( score_lo < 0 || score_lo >= 100 ) {
14726                 return text;
14727             }
14728
14729             if(sec >= 0) time = 600*time + 10*sec; else
14730             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14731
14732             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14733
14734             /* [HGM] PV time: now locate end of PV info */
14735             while( *++sep >= '0' && *sep <= '9'); // strip depth
14736             if(time >= 0)
14737             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14738             if(sec >= 0)
14739             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14740             if(deci >= 0)
14741             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14742             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14743         }
14744
14745         if( depth <= 0 ) {
14746             return text;
14747         }
14748
14749         if( time < 0 ) {
14750             time = -1;
14751         }
14752
14753         pvInfoList[index-1].depth = depth;
14754         pvInfoList[index-1].score = score;
14755         pvInfoList[index-1].time  = 10*time; // centi-sec
14756         if(*sep == '}') *sep = 0; else *--sep = '{';
14757         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14758     }
14759     return sep;
14760 }
14761
14762 void
14763 SendToProgram(message, cps)
14764      char *message;
14765      ChessProgramState *cps;
14766 {
14767     int count, outCount, error;
14768     char buf[MSG_SIZ];
14769
14770     if (cps->pr == NULL) return;
14771     Attention(cps);
14772
14773     if (appData.debugMode) {
14774         TimeMark now;
14775         GetTimeMark(&now);
14776         fprintf(debugFP, "%ld >%-6s: %s",
14777                 SubtractTimeMarks(&now, &programStartTime),
14778                 cps->which, message);
14779     }
14780
14781     count = strlen(message);
14782     outCount = OutputToProcess(cps->pr, message, count, &error);
14783     if (outCount < count && !exiting
14784                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14785       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14786       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14787         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14788             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14789                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14790                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14791                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14792             } else {
14793                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14794                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14795                 gameInfo.result = res;
14796             }
14797             gameInfo.resultDetails = StrSave(buf);
14798         }
14799         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14800         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14801     }
14802 }
14803
14804 void
14805 ReceiveFromProgram(isr, closure, message, count, error)
14806      InputSourceRef isr;
14807      VOIDSTAR closure;
14808      char *message;
14809      int count;
14810      int error;
14811 {
14812     char *end_str;
14813     char buf[MSG_SIZ];
14814     ChessProgramState *cps = (ChessProgramState *)closure;
14815
14816     if (isr != cps->isr) return; /* Killed intentionally */
14817     if (count <= 0) {
14818         if (count == 0) {
14819             RemoveInputSource(cps->isr);
14820             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14821             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14822                     _(cps->which), cps->program);
14823         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14824                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14825                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14826                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14827                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14828                 } else {
14829                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14830                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14831                     gameInfo.result = res;
14832                 }
14833                 gameInfo.resultDetails = StrSave(buf);
14834             }
14835             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14836             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14837         } else {
14838             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14839                     _(cps->which), cps->program);
14840             RemoveInputSource(cps->isr);
14841
14842             /* [AS] Program is misbehaving badly... kill it */
14843             if( count == -2 ) {
14844                 DestroyChildProcess( cps->pr, 9 );
14845                 cps->pr = NoProc;
14846             }
14847
14848             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14849         }
14850         return;
14851     }
14852
14853     if ((end_str = strchr(message, '\r')) != NULL)
14854       *end_str = NULLCHAR;
14855     if ((end_str = strchr(message, '\n')) != NULL)
14856       *end_str = NULLCHAR;
14857
14858     if (appData.debugMode) {
14859         TimeMark now; int print = 1;
14860         char *quote = ""; char c; int i;
14861
14862         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14863                 char start = message[0];
14864                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14865                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14866                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14867                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14868                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14869                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14870                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14871                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14872                    sscanf(message, "hint: %c", &c)!=1 && 
14873                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14874                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14875                     print = (appData.engineComments >= 2);
14876                 }
14877                 message[0] = start; // restore original message
14878         }
14879         if(print) {
14880                 GetTimeMark(&now);
14881                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14882                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14883                         quote,
14884                         message);
14885         }
14886     }
14887
14888     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14889     if (appData.icsEngineAnalyze) {
14890         if (strstr(message, "whisper") != NULL ||
14891              strstr(message, "kibitz") != NULL ||
14892             strstr(message, "tellics") != NULL) return;
14893     }
14894
14895     HandleMachineMove(message, cps);
14896 }
14897
14898
14899 void
14900 SendTimeControl(cps, mps, tc, inc, sd, st)
14901      ChessProgramState *cps;
14902      int mps, inc, sd, st;
14903      long tc;
14904 {
14905     char buf[MSG_SIZ];
14906     int seconds;
14907
14908     if( timeControl_2 > 0 ) {
14909         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14910             tc = timeControl_2;
14911         }
14912     }
14913     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14914     inc /= cps->timeOdds;
14915     st  /= cps->timeOdds;
14916
14917     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14918
14919     if (st > 0) {
14920       /* Set exact time per move, normally using st command */
14921       if (cps->stKludge) {
14922         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14923         seconds = st % 60;
14924         if (seconds == 0) {
14925           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14926         } else {
14927           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14928         }
14929       } else {
14930         snprintf(buf, MSG_SIZ, "st %d\n", st);
14931       }
14932     } else {
14933       /* Set conventional or incremental time control, using level command */
14934       if (seconds == 0) {
14935         /* Note old gnuchess bug -- minutes:seconds used to not work.
14936            Fixed in later versions, but still avoid :seconds
14937            when seconds is 0. */
14938         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14939       } else {
14940         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14941                  seconds, inc/1000.);
14942       }
14943     }
14944     SendToProgram(buf, cps);
14945
14946     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14947     /* Orthogonally, limit search to given depth */
14948     if (sd > 0) {
14949       if (cps->sdKludge) {
14950         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14951       } else {
14952         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14953       }
14954       SendToProgram(buf, cps);
14955     }
14956
14957     if(cps->nps >= 0) { /* [HGM] nps */
14958         if(cps->supportsNPS == FALSE)
14959           cps->nps = -1; // don't use if engine explicitly says not supported!
14960         else {
14961           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14962           SendToProgram(buf, cps);
14963         }
14964     }
14965 }
14966
14967 ChessProgramState *WhitePlayer()
14968 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14969 {
14970     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14971        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14972         return &second;
14973     return &first;
14974 }
14975
14976 void
14977 SendTimeRemaining(cps, machineWhite)
14978      ChessProgramState *cps;
14979      int /*boolean*/ machineWhite;
14980 {
14981     char message[MSG_SIZ];
14982     long time, otime;
14983
14984     /* Note: this routine must be called when the clocks are stopped
14985        or when they have *just* been set or switched; otherwise
14986        it will be off by the time since the current tick started.
14987     */
14988     if (machineWhite) {
14989         time = whiteTimeRemaining / 10;
14990         otime = blackTimeRemaining / 10;
14991     } else {
14992         time = blackTimeRemaining / 10;
14993         otime = whiteTimeRemaining / 10;
14994     }
14995     /* [HGM] translate opponent's time by time-odds factor */
14996     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14997     if (appData.debugMode) {
14998         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14999     }
15000
15001     if (time <= 0) time = 1;
15002     if (otime <= 0) otime = 1;
15003
15004     snprintf(message, MSG_SIZ, "time %ld\n", time);
15005     SendToProgram(message, cps);
15006
15007     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15008     SendToProgram(message, cps);
15009 }
15010
15011 int
15012 BoolFeature(p, name, loc, cps)
15013      char **p;
15014      char *name;
15015      int *loc;
15016      ChessProgramState *cps;
15017 {
15018   char buf[MSG_SIZ];
15019   int len = strlen(name);
15020   int val;
15021
15022   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15023     (*p) += len + 1;
15024     sscanf(*p, "%d", &val);
15025     *loc = (val != 0);
15026     while (**p && **p != ' ')
15027       (*p)++;
15028     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15029     SendToProgram(buf, cps);
15030     return TRUE;
15031   }
15032   return FALSE;
15033 }
15034
15035 int
15036 IntFeature(p, name, loc, cps)
15037      char **p;
15038      char *name;
15039      int *loc;
15040      ChessProgramState *cps;
15041 {
15042   char buf[MSG_SIZ];
15043   int len = strlen(name);
15044   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15045     (*p) += len + 1;
15046     sscanf(*p, "%d", loc);
15047     while (**p && **p != ' ') (*p)++;
15048     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15049     SendToProgram(buf, cps);
15050     return TRUE;
15051   }
15052   return FALSE;
15053 }
15054
15055 int
15056 StringFeature(p, name, loc, cps)
15057      char **p;
15058      char *name;
15059      char loc[];
15060      ChessProgramState *cps;
15061 {
15062   char buf[MSG_SIZ];
15063   int len = strlen(name);
15064   if (strncmp((*p), name, len) == 0
15065       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15066     (*p) += len + 2;
15067     sscanf(*p, "%[^\"]", loc);
15068     while (**p && **p != '\"') (*p)++;
15069     if (**p == '\"') (*p)++;
15070     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15071     SendToProgram(buf, cps);
15072     return TRUE;
15073   }
15074   return FALSE;
15075 }
15076
15077 int
15078 ParseOption(Option *opt, ChessProgramState *cps)
15079 // [HGM] options: process the string that defines an engine option, and determine
15080 // name, type, default value, and allowed value range
15081 {
15082         char *p, *q, buf[MSG_SIZ];
15083         int n, min = (-1)<<31, max = 1<<31, def;
15084
15085         if(p = strstr(opt->name, " -spin ")) {
15086             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15087             if(max < min) max = min; // enforce consistency
15088             if(def < min) def = min;
15089             if(def > max) def = max;
15090             opt->value = def;
15091             opt->min = min;
15092             opt->max = max;
15093             opt->type = Spin;
15094         } else if((p = strstr(opt->name, " -slider "))) {
15095             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15096             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15097             if(max < min) max = min; // enforce consistency
15098             if(def < min) def = min;
15099             if(def > max) def = max;
15100             opt->value = def;
15101             opt->min = min;
15102             opt->max = max;
15103             opt->type = Spin; // Slider;
15104         } else if((p = strstr(opt->name, " -string "))) {
15105             opt->textValue = p+9;
15106             opt->type = TextBox;
15107         } else if((p = strstr(opt->name, " -file "))) {
15108             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15109             opt->textValue = p+7;
15110             opt->type = FileName; // FileName;
15111         } else if((p = strstr(opt->name, " -path "))) {
15112             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15113             opt->textValue = p+7;
15114             opt->type = PathName; // PathName;
15115         } else if(p = strstr(opt->name, " -check ")) {
15116             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15117             opt->value = (def != 0);
15118             opt->type = CheckBox;
15119         } else if(p = strstr(opt->name, " -combo ")) {
15120             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15121             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15122             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15123             opt->value = n = 0;
15124             while(q = StrStr(q, " /// ")) {
15125                 n++; *q = 0;    // count choices, and null-terminate each of them
15126                 q += 5;
15127                 if(*q == '*') { // remember default, which is marked with * prefix
15128                     q++;
15129                     opt->value = n;
15130                 }
15131                 cps->comboList[cps->comboCnt++] = q;
15132             }
15133             cps->comboList[cps->comboCnt++] = NULL;
15134             opt->max = n + 1;
15135             opt->type = ComboBox;
15136         } else if(p = strstr(opt->name, " -button")) {
15137             opt->type = Button;
15138         } else if(p = strstr(opt->name, " -save")) {
15139             opt->type = SaveButton;
15140         } else return FALSE;
15141         *p = 0; // terminate option name
15142         // now look if the command-line options define a setting for this engine option.
15143         if(cps->optionSettings && cps->optionSettings[0])
15144             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15145         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15146           snprintf(buf, MSG_SIZ, "option %s", p);
15147                 if(p = strstr(buf, ",")) *p = 0;
15148                 if(q = strchr(buf, '=')) switch(opt->type) {
15149                     case ComboBox:
15150                         for(n=0; n<opt->max; n++)
15151                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15152                         break;
15153                     case TextBox:
15154                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15155                         break;
15156                     case Spin:
15157                     case CheckBox:
15158                         opt->value = atoi(q+1);
15159                     default:
15160                         break;
15161                 }
15162                 strcat(buf, "\n");
15163                 SendToProgram(buf, cps);
15164         }
15165         return TRUE;
15166 }
15167
15168 void
15169 FeatureDone(cps, val)
15170      ChessProgramState* cps;
15171      int val;
15172 {
15173   DelayedEventCallback cb = GetDelayedEvent();
15174   if ((cb == InitBackEnd3 && cps == &first) ||
15175       (cb == SettingsMenuIfReady && cps == &second) ||
15176       (cb == LoadEngine) ||
15177       (cb == TwoMachinesEventIfReady)) {
15178     CancelDelayedEvent();
15179     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15180   }
15181   cps->initDone = val;
15182 }
15183
15184 /* Parse feature command from engine */
15185 void
15186 ParseFeatures(args, cps)
15187      char* args;
15188      ChessProgramState *cps;
15189 {
15190   char *p = args;
15191   char *q;
15192   int val;
15193   char buf[MSG_SIZ];
15194
15195   for (;;) {
15196     while (*p == ' ') p++;
15197     if (*p == NULLCHAR) return;
15198
15199     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15200     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15201     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15202     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15203     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15204     if (BoolFeature(&p, "reuse", &val, cps)) {
15205       /* Engine can disable reuse, but can't enable it if user said no */
15206       if (!val) cps->reuse = FALSE;
15207       continue;
15208     }
15209     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15210     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15211       if (gameMode == TwoMachinesPlay) {
15212         DisplayTwoMachinesTitle();
15213       } else {
15214         DisplayTitle("");
15215       }
15216       continue;
15217     }
15218     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15219     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15220     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15221     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15222     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15223     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15224     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15225     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15226     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15227     if (IntFeature(&p, "done", &val, cps)) {
15228       FeatureDone(cps, val);
15229       continue;
15230     }
15231     /* Added by Tord: */
15232     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15233     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15234     /* End of additions by Tord */
15235
15236     /* [HGM] added features: */
15237     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15238     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15239     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15240     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15241     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15242     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15243     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15244         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15245           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15246             SendToProgram(buf, cps);
15247             continue;
15248         }
15249         if(cps->nrOptions >= MAX_OPTIONS) {
15250             cps->nrOptions--;
15251             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15252             DisplayError(buf, 0);
15253         }
15254         continue;
15255     }
15256     /* End of additions by HGM */
15257
15258     /* unknown feature: complain and skip */
15259     q = p;
15260     while (*q && *q != '=') q++;
15261     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15262     SendToProgram(buf, cps);
15263     p = q;
15264     if (*p == '=') {
15265       p++;
15266       if (*p == '\"') {
15267         p++;
15268         while (*p && *p != '\"') p++;
15269         if (*p == '\"') p++;
15270       } else {
15271         while (*p && *p != ' ') p++;
15272       }
15273     }
15274   }
15275
15276 }
15277
15278 void
15279 PeriodicUpdatesEvent(newState)
15280      int newState;
15281 {
15282     if (newState == appData.periodicUpdates)
15283       return;
15284
15285     appData.periodicUpdates=newState;
15286
15287     /* Display type changes, so update it now */
15288 //    DisplayAnalysis();
15289
15290     /* Get the ball rolling again... */
15291     if (newState) {
15292         AnalysisPeriodicEvent(1);
15293         StartAnalysisClock();
15294     }
15295 }
15296
15297 void
15298 PonderNextMoveEvent(newState)
15299      int newState;
15300 {
15301     if (newState == appData.ponderNextMove) return;
15302     if (gameMode == EditPosition) EditPositionDone(TRUE);
15303     if (newState) {
15304         SendToProgram("hard\n", &first);
15305         if (gameMode == TwoMachinesPlay) {
15306             SendToProgram("hard\n", &second);
15307         }
15308     } else {
15309         SendToProgram("easy\n", &first);
15310         thinkOutput[0] = NULLCHAR;
15311         if (gameMode == TwoMachinesPlay) {
15312             SendToProgram("easy\n", &second);
15313         }
15314     }
15315     appData.ponderNextMove = newState;
15316 }
15317
15318 void
15319 NewSettingEvent(option, feature, command, value)
15320      char *command;
15321      int option, value, *feature;
15322 {
15323     char buf[MSG_SIZ];
15324
15325     if (gameMode == EditPosition) EditPositionDone(TRUE);
15326     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15327     if(feature == NULL || *feature) SendToProgram(buf, &first);
15328     if (gameMode == TwoMachinesPlay) {
15329         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15330     }
15331 }
15332
15333 void
15334 ShowThinkingEvent()
15335 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15336 {
15337     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15338     int newState = appData.showThinking
15339         // [HGM] thinking: other features now need thinking output as well
15340         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15341
15342     if (oldState == newState) return;
15343     oldState = newState;
15344     if (gameMode == EditPosition) EditPositionDone(TRUE);
15345     if (oldState) {
15346         SendToProgram("post\n", &first);
15347         if (gameMode == TwoMachinesPlay) {
15348             SendToProgram("post\n", &second);
15349         }
15350     } else {
15351         SendToProgram("nopost\n", &first);
15352         thinkOutput[0] = NULLCHAR;
15353         if (gameMode == TwoMachinesPlay) {
15354             SendToProgram("nopost\n", &second);
15355         }
15356     }
15357 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15358 }
15359
15360 void
15361 AskQuestionEvent(title, question, replyPrefix, which)
15362      char *title; char *question; char *replyPrefix; char *which;
15363 {
15364   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15365   if (pr == NoProc) return;
15366   AskQuestion(title, question, replyPrefix, pr);
15367 }
15368
15369 void
15370 TypeInEvent(char firstChar)
15371 {
15372     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15373         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15374         gameMode == AnalyzeMode || gameMode == EditGame || 
15375         gameMode == EditPosition || gameMode == IcsExamining ||
15376         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15377         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15378                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15379                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15380         gameMode == Training) PopUpMoveDialog(firstChar);
15381 }
15382
15383 void
15384 TypeInDoneEvent(char *move)
15385 {
15386         Board board;
15387         int n, fromX, fromY, toX, toY;
15388         char promoChar;
15389         ChessMove moveType;
15390
15391         // [HGM] FENedit
15392         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15393                 EditPositionPasteFEN(move);
15394                 return;
15395         }
15396         // [HGM] movenum: allow move number to be typed in any mode
15397         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15398           ToNrEvent(2*n-1);
15399           return;
15400         }
15401
15402       if (gameMode != EditGame && currentMove != forwardMostMove && 
15403         gameMode != Training) {
15404         DisplayMoveError(_("Displayed move is not current"));
15405       } else {
15406         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15407           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15408         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15409         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15410           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15411           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15412         } else {
15413           DisplayMoveError(_("Could not parse move"));
15414         }
15415       }
15416 }
15417
15418 void
15419 DisplayMove(moveNumber)
15420      int moveNumber;
15421 {
15422     char message[MSG_SIZ];
15423     char res[MSG_SIZ];
15424     char cpThinkOutput[MSG_SIZ];
15425
15426     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15427
15428     if (moveNumber == forwardMostMove - 1 ||
15429         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15430
15431         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15432
15433         if (strchr(cpThinkOutput, '\n')) {
15434             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15435         }
15436     } else {
15437         *cpThinkOutput = NULLCHAR;
15438     }
15439
15440     /* [AS] Hide thinking from human user */
15441     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15442         *cpThinkOutput = NULLCHAR;
15443         if( thinkOutput[0] != NULLCHAR ) {
15444             int i;
15445
15446             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15447                 cpThinkOutput[i] = '.';
15448             }
15449             cpThinkOutput[i] = NULLCHAR;
15450             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15451         }
15452     }
15453
15454     if (moveNumber == forwardMostMove - 1 &&
15455         gameInfo.resultDetails != NULL) {
15456         if (gameInfo.resultDetails[0] == NULLCHAR) {
15457           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15458         } else {
15459           snprintf(res, MSG_SIZ, " {%s} %s",
15460                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15461         }
15462     } else {
15463         res[0] = NULLCHAR;
15464     }
15465
15466     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15467         DisplayMessage(res, cpThinkOutput);
15468     } else {
15469       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15470                 WhiteOnMove(moveNumber) ? " " : ".. ",
15471                 parseList[moveNumber], res);
15472         DisplayMessage(message, cpThinkOutput);
15473     }
15474 }
15475
15476 void
15477 DisplayComment(moveNumber, text)
15478      int moveNumber;
15479      char *text;
15480 {
15481     char title[MSG_SIZ];
15482
15483     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15484       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15485     } else {
15486       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15487               WhiteOnMove(moveNumber) ? " " : ".. ",
15488               parseList[moveNumber]);
15489     }
15490     if (text != NULL && (appData.autoDisplayComment || commentUp))
15491         CommentPopUp(title, text);
15492 }
15493
15494 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15495  * might be busy thinking or pondering.  It can be omitted if your
15496  * gnuchess is configured to stop thinking immediately on any user
15497  * input.  However, that gnuchess feature depends on the FIONREAD
15498  * ioctl, which does not work properly on some flavors of Unix.
15499  */
15500 void
15501 Attention(cps)
15502      ChessProgramState *cps;
15503 {
15504 #if ATTENTION
15505     if (!cps->useSigint) return;
15506     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15507     switch (gameMode) {
15508       case MachinePlaysWhite:
15509       case MachinePlaysBlack:
15510       case TwoMachinesPlay:
15511       case IcsPlayingWhite:
15512       case IcsPlayingBlack:
15513       case AnalyzeMode:
15514       case AnalyzeFile:
15515         /* Skip if we know it isn't thinking */
15516         if (!cps->maybeThinking) return;
15517         if (appData.debugMode)
15518           fprintf(debugFP, "Interrupting %s\n", cps->which);
15519         InterruptChildProcess(cps->pr);
15520         cps->maybeThinking = FALSE;
15521         break;
15522       default:
15523         break;
15524     }
15525 #endif /*ATTENTION*/
15526 }
15527
15528 int
15529 CheckFlags()
15530 {
15531     if (whiteTimeRemaining <= 0) {
15532         if (!whiteFlag) {
15533             whiteFlag = TRUE;
15534             if (appData.icsActive) {
15535                 if (appData.autoCallFlag &&
15536                     gameMode == IcsPlayingBlack && !blackFlag) {
15537                   SendToICS(ics_prefix);
15538                   SendToICS("flag\n");
15539                 }
15540             } else {
15541                 if (blackFlag) {
15542                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15543                 } else {
15544                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15545                     if (appData.autoCallFlag) {
15546                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15547                         return TRUE;
15548                     }
15549                 }
15550             }
15551         }
15552     }
15553     if (blackTimeRemaining <= 0) {
15554         if (!blackFlag) {
15555             blackFlag = TRUE;
15556             if (appData.icsActive) {
15557                 if (appData.autoCallFlag &&
15558                     gameMode == IcsPlayingWhite && !whiteFlag) {
15559                   SendToICS(ics_prefix);
15560                   SendToICS("flag\n");
15561                 }
15562             } else {
15563                 if (whiteFlag) {
15564                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15565                 } else {
15566                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15567                     if (appData.autoCallFlag) {
15568                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15569                         return TRUE;
15570                     }
15571                 }
15572             }
15573         }
15574     }
15575     return FALSE;
15576 }
15577
15578 void
15579 CheckTimeControl()
15580 {
15581     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15582         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15583
15584     /*
15585      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15586      */
15587     if ( !WhiteOnMove(forwardMostMove) ) {
15588         /* White made time control */
15589         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15590         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15591         /* [HGM] time odds: correct new time quota for time odds! */
15592                                             / WhitePlayer()->timeOdds;
15593         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15594     } else {
15595         lastBlack -= blackTimeRemaining;
15596         /* Black made time control */
15597         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15598                                             / WhitePlayer()->other->timeOdds;
15599         lastWhite = whiteTimeRemaining;
15600     }
15601 }
15602
15603 void
15604 DisplayBothClocks()
15605 {
15606     int wom = gameMode == EditPosition ?
15607       !blackPlaysFirst : WhiteOnMove(currentMove);
15608     DisplayWhiteClock(whiteTimeRemaining, wom);
15609     DisplayBlackClock(blackTimeRemaining, !wom);
15610 }
15611
15612
15613 /* Timekeeping seems to be a portability nightmare.  I think everyone
15614    has ftime(), but I'm really not sure, so I'm including some ifdefs
15615    to use other calls if you don't.  Clocks will be less accurate if
15616    you have neither ftime nor gettimeofday.
15617 */
15618
15619 /* VS 2008 requires the #include outside of the function */
15620 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15621 #include <sys/timeb.h>
15622 #endif
15623
15624 /* Get the current time as a TimeMark */
15625 void
15626 GetTimeMark(tm)
15627      TimeMark *tm;
15628 {
15629 #if HAVE_GETTIMEOFDAY
15630
15631     struct timeval timeVal;
15632     struct timezone timeZone;
15633
15634     gettimeofday(&timeVal, &timeZone);
15635     tm->sec = (long) timeVal.tv_sec;
15636     tm->ms = (int) (timeVal.tv_usec / 1000L);
15637
15638 #else /*!HAVE_GETTIMEOFDAY*/
15639 #if HAVE_FTIME
15640
15641 // include <sys/timeb.h> / moved to just above start of function
15642     struct timeb timeB;
15643
15644     ftime(&timeB);
15645     tm->sec = (long) timeB.time;
15646     tm->ms = (int) timeB.millitm;
15647
15648 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15649     tm->sec = (long) time(NULL);
15650     tm->ms = 0;
15651 #endif
15652 #endif
15653 }
15654
15655 /* Return the difference in milliseconds between two
15656    time marks.  We assume the difference will fit in a long!
15657 */
15658 long
15659 SubtractTimeMarks(tm2, tm1)
15660      TimeMark *tm2, *tm1;
15661 {
15662     return 1000L*(tm2->sec - tm1->sec) +
15663            (long) (tm2->ms - tm1->ms);
15664 }
15665
15666
15667 /*
15668  * Code to manage the game clocks.
15669  *
15670  * In tournament play, black starts the clock and then white makes a move.
15671  * We give the human user a slight advantage if he is playing white---the
15672  * clocks don't run until he makes his first move, so it takes zero time.
15673  * Also, we don't account for network lag, so we could get out of sync
15674  * with GNU Chess's clock -- but then, referees are always right.
15675  */
15676
15677 static TimeMark tickStartTM;
15678 static long intendedTickLength;
15679
15680 long
15681 NextTickLength(timeRemaining)
15682      long timeRemaining;
15683 {
15684     long nominalTickLength, nextTickLength;
15685
15686     if (timeRemaining > 0L && timeRemaining <= 10000L)
15687       nominalTickLength = 100L;
15688     else
15689       nominalTickLength = 1000L;
15690     nextTickLength = timeRemaining % nominalTickLength;
15691     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15692
15693     return nextTickLength;
15694 }
15695
15696 /* Adjust clock one minute up or down */
15697 void
15698 AdjustClock(Boolean which, int dir)
15699 {
15700     if(which) blackTimeRemaining += 60000*dir;
15701     else      whiteTimeRemaining += 60000*dir;
15702     DisplayBothClocks();
15703 }
15704
15705 /* Stop clocks and reset to a fresh time control */
15706 void
15707 ResetClocks()
15708 {
15709     (void) StopClockTimer();
15710     if (appData.icsActive) {
15711         whiteTimeRemaining = blackTimeRemaining = 0;
15712     } else if (searchTime) {
15713         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15714         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15715     } else { /* [HGM] correct new time quote for time odds */
15716         whiteTC = blackTC = fullTimeControlString;
15717         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15718         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15719     }
15720     if (whiteFlag || blackFlag) {
15721         DisplayTitle("");
15722         whiteFlag = blackFlag = FALSE;
15723     }
15724     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15725     DisplayBothClocks();
15726 }
15727
15728 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15729
15730 /* Decrement running clock by amount of time that has passed */
15731 void
15732 DecrementClocks()
15733 {
15734     long timeRemaining;
15735     long lastTickLength, fudge;
15736     TimeMark now;
15737
15738     if (!appData.clockMode) return;
15739     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15740
15741     GetTimeMark(&now);
15742
15743     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15744
15745     /* Fudge if we woke up a little too soon */
15746     fudge = intendedTickLength - lastTickLength;
15747     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15748
15749     if (WhiteOnMove(forwardMostMove)) {
15750         if(whiteNPS >= 0) lastTickLength = 0;
15751         timeRemaining = whiteTimeRemaining -= lastTickLength;
15752         if(timeRemaining < 0 && !appData.icsActive) {
15753             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15754             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15755                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15756                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15757             }
15758         }
15759         DisplayWhiteClock(whiteTimeRemaining - fudge,
15760                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15761     } else {
15762         if(blackNPS >= 0) lastTickLength = 0;
15763         timeRemaining = blackTimeRemaining -= lastTickLength;
15764         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15765             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15766             if(suddenDeath) {
15767                 blackStartMove = forwardMostMove;
15768                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15769             }
15770         }
15771         DisplayBlackClock(blackTimeRemaining - fudge,
15772                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15773     }
15774     if (CheckFlags()) return;
15775
15776     tickStartTM = now;
15777     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15778     StartClockTimer(intendedTickLength);
15779
15780     /* if the time remaining has fallen below the alarm threshold, sound the
15781      * alarm. if the alarm has sounded and (due to a takeback or time control
15782      * with increment) the time remaining has increased to a level above the
15783      * threshold, reset the alarm so it can sound again.
15784      */
15785
15786     if (appData.icsActive && appData.icsAlarm) {
15787
15788         /* make sure we are dealing with the user's clock */
15789         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15790                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15791            )) return;
15792
15793         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15794             alarmSounded = FALSE;
15795         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15796             PlayAlarmSound();
15797             alarmSounded = TRUE;
15798         }
15799     }
15800 }
15801
15802
15803 /* A player has just moved, so stop the previously running
15804    clock and (if in clock mode) start the other one.
15805    We redisplay both clocks in case we're in ICS mode, because
15806    ICS gives us an update to both clocks after every move.
15807    Note that this routine is called *after* forwardMostMove
15808    is updated, so the last fractional tick must be subtracted
15809    from the color that is *not* on move now.
15810 */
15811 void
15812 SwitchClocks(int newMoveNr)
15813 {
15814     long lastTickLength;
15815     TimeMark now;
15816     int flagged = FALSE;
15817
15818     GetTimeMark(&now);
15819
15820     if (StopClockTimer() && appData.clockMode) {
15821         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15822         if (!WhiteOnMove(forwardMostMove)) {
15823             if(blackNPS >= 0) lastTickLength = 0;
15824             blackTimeRemaining -= lastTickLength;
15825            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15826 //         if(pvInfoList[forwardMostMove].time == -1)
15827                  pvInfoList[forwardMostMove].time =               // use GUI time
15828                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15829         } else {
15830            if(whiteNPS >= 0) lastTickLength = 0;
15831            whiteTimeRemaining -= lastTickLength;
15832            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15833 //         if(pvInfoList[forwardMostMove].time == -1)
15834                  pvInfoList[forwardMostMove].time =
15835                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15836         }
15837         flagged = CheckFlags();
15838     }
15839     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15840     CheckTimeControl();
15841
15842     if (flagged || !appData.clockMode) return;
15843
15844     switch (gameMode) {
15845       case MachinePlaysBlack:
15846       case MachinePlaysWhite:
15847       case BeginningOfGame:
15848         if (pausing) return;
15849         break;
15850
15851       case EditGame:
15852       case PlayFromGameFile:
15853       case IcsExamining:
15854         return;
15855
15856       default:
15857         break;
15858     }
15859
15860     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15861         if(WhiteOnMove(forwardMostMove))
15862              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15863         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15864     }
15865
15866     tickStartTM = now;
15867     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15868       whiteTimeRemaining : blackTimeRemaining);
15869     StartClockTimer(intendedTickLength);
15870 }
15871
15872
15873 /* Stop both clocks */
15874 void
15875 StopClocks()
15876 {
15877     long lastTickLength;
15878     TimeMark now;
15879
15880     if (!StopClockTimer()) return;
15881     if (!appData.clockMode) return;
15882
15883     GetTimeMark(&now);
15884
15885     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15886     if (WhiteOnMove(forwardMostMove)) {
15887         if(whiteNPS >= 0) lastTickLength = 0;
15888         whiteTimeRemaining -= lastTickLength;
15889         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15890     } else {
15891         if(blackNPS >= 0) lastTickLength = 0;
15892         blackTimeRemaining -= lastTickLength;
15893         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15894     }
15895     CheckFlags();
15896 }
15897
15898 /* Start clock of player on move.  Time may have been reset, so
15899    if clock is already running, stop and restart it. */
15900 void
15901 StartClocks()
15902 {
15903     (void) StopClockTimer(); /* in case it was running already */
15904     DisplayBothClocks();
15905     if (CheckFlags()) return;
15906
15907     if (!appData.clockMode) return;
15908     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15909
15910     GetTimeMark(&tickStartTM);
15911     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15912       whiteTimeRemaining : blackTimeRemaining);
15913
15914    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15915     whiteNPS = blackNPS = -1;
15916     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15917        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15918         whiteNPS = first.nps;
15919     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15920        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15921         blackNPS = first.nps;
15922     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15923         whiteNPS = second.nps;
15924     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15925         blackNPS = second.nps;
15926     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15927
15928     StartClockTimer(intendedTickLength);
15929 }
15930
15931 char *
15932 TimeString(ms)
15933      long ms;
15934 {
15935     long second, minute, hour, day;
15936     char *sign = "";
15937     static char buf[32];
15938
15939     if (ms > 0 && ms <= 9900) {
15940       /* convert milliseconds to tenths, rounding up */
15941       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15942
15943       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15944       return buf;
15945     }
15946
15947     /* convert milliseconds to seconds, rounding up */
15948     /* use floating point to avoid strangeness of integer division
15949        with negative dividends on many machines */
15950     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15951
15952     if (second < 0) {
15953         sign = "-";
15954         second = -second;
15955     }
15956
15957     day = second / (60 * 60 * 24);
15958     second = second % (60 * 60 * 24);
15959     hour = second / (60 * 60);
15960     second = second % (60 * 60);
15961     minute = second / 60;
15962     second = second % 60;
15963
15964     if (day > 0)
15965       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15966               sign, day, hour, minute, second);
15967     else if (hour > 0)
15968       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15969     else
15970       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15971
15972     return buf;
15973 }
15974
15975
15976 /*
15977  * This is necessary because some C libraries aren't ANSI C compliant yet.
15978  */
15979 char *
15980 StrStr(string, match)
15981      char *string, *match;
15982 {
15983     int i, length;
15984
15985     length = strlen(match);
15986
15987     for (i = strlen(string) - length; i >= 0; i--, string++)
15988       if (!strncmp(match, string, length))
15989         return string;
15990
15991     return NULL;
15992 }
15993
15994 char *
15995 StrCaseStr(string, match)
15996      char *string, *match;
15997 {
15998     int i, j, length;
15999
16000     length = strlen(match);
16001
16002     for (i = strlen(string) - length; i >= 0; i--, string++) {
16003         for (j = 0; j < length; j++) {
16004             if (ToLower(match[j]) != ToLower(string[j]))
16005               break;
16006         }
16007         if (j == length) return string;
16008     }
16009
16010     return NULL;
16011 }
16012
16013 #ifndef _amigados
16014 int
16015 StrCaseCmp(s1, s2)
16016      char *s1, *s2;
16017 {
16018     char c1, c2;
16019
16020     for (;;) {
16021         c1 = ToLower(*s1++);
16022         c2 = ToLower(*s2++);
16023         if (c1 > c2) return 1;
16024         if (c1 < c2) return -1;
16025         if (c1 == NULLCHAR) return 0;
16026     }
16027 }
16028
16029
16030 int
16031 ToLower(c)
16032      int c;
16033 {
16034     return isupper(c) ? tolower(c) : c;
16035 }
16036
16037
16038 int
16039 ToUpper(c)
16040      int c;
16041 {
16042     return islower(c) ? toupper(c) : c;
16043 }
16044 #endif /* !_amigados    */
16045
16046 char *
16047 StrSave(s)
16048      char *s;
16049 {
16050   char *ret;
16051
16052   if ((ret = (char *) malloc(strlen(s) + 1)))
16053     {
16054       safeStrCpy(ret, s, strlen(s)+1);
16055     }
16056   return ret;
16057 }
16058
16059 char *
16060 StrSavePtr(s, savePtr)
16061      char *s, **savePtr;
16062 {
16063     if (*savePtr) {
16064         free(*savePtr);
16065     }
16066     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16067       safeStrCpy(*savePtr, s, strlen(s)+1);
16068     }
16069     return(*savePtr);
16070 }
16071
16072 char *
16073 PGNDate()
16074 {
16075     time_t clock;
16076     struct tm *tm;
16077     char buf[MSG_SIZ];
16078
16079     clock = time((time_t *)NULL);
16080     tm = localtime(&clock);
16081     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16082             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16083     return StrSave(buf);
16084 }
16085
16086
16087 char *
16088 PositionToFEN(move, overrideCastling)
16089      int move;
16090      char *overrideCastling;
16091 {
16092     int i, j, fromX, fromY, toX, toY;
16093     int whiteToPlay;
16094     char buf[MSG_SIZ];
16095     char *p, *q;
16096     int emptycount;
16097     ChessSquare piece;
16098
16099     whiteToPlay = (gameMode == EditPosition) ?
16100       !blackPlaysFirst : (move % 2 == 0);
16101     p = buf;
16102
16103     /* Piece placement data */
16104     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16105         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16106         emptycount = 0;
16107         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16108             if (boards[move][i][j] == EmptySquare) {
16109                 emptycount++;
16110             } else { ChessSquare piece = boards[move][i][j];
16111                 if (emptycount > 0) {
16112                     if(emptycount<10) /* [HGM] can be >= 10 */
16113                         *p++ = '0' + emptycount;
16114                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16115                     emptycount = 0;
16116                 }
16117                 if(PieceToChar(piece) == '+') {
16118                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16119                     *p++ = '+';
16120                     piece = (ChessSquare)(DEMOTED piece);
16121                 }
16122                 *p++ = PieceToChar(piece);
16123                 if(p[-1] == '~') {
16124                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16125                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16126                     *p++ = '~';
16127                 }
16128             }
16129         }
16130         if (emptycount > 0) {
16131             if(emptycount<10) /* [HGM] can be >= 10 */
16132                 *p++ = '0' + emptycount;
16133             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16134             emptycount = 0;
16135         }
16136         *p++ = '/';
16137     }
16138     *(p - 1) = ' ';
16139
16140     /* [HGM] print Crazyhouse or Shogi holdings */
16141     if( gameInfo.holdingsWidth ) {
16142         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16143         q = p;
16144         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16145             piece = boards[move][i][BOARD_WIDTH-1];
16146             if( piece != EmptySquare )
16147               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16148                   *p++ = PieceToChar(piece);
16149         }
16150         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16151             piece = boards[move][BOARD_HEIGHT-i-1][0];
16152             if( piece != EmptySquare )
16153               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16154                   *p++ = PieceToChar(piece);
16155         }
16156
16157         if( q == p ) *p++ = '-';
16158         *p++ = ']';
16159         *p++ = ' ';
16160     }
16161
16162     /* Active color */
16163     *p++ = whiteToPlay ? 'w' : 'b';
16164     *p++ = ' ';
16165
16166   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16167     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16168   } else {
16169   if(nrCastlingRights) {
16170      q = p;
16171      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16172        /* [HGM] write directly from rights */
16173            if(boards[move][CASTLING][2] != NoRights &&
16174               boards[move][CASTLING][0] != NoRights   )
16175                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16176            if(boards[move][CASTLING][2] != NoRights &&
16177               boards[move][CASTLING][1] != NoRights   )
16178                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16179            if(boards[move][CASTLING][5] != NoRights &&
16180               boards[move][CASTLING][3] != NoRights   )
16181                 *p++ = boards[move][CASTLING][3] + AAA;
16182            if(boards[move][CASTLING][5] != NoRights &&
16183               boards[move][CASTLING][4] != NoRights   )
16184                 *p++ = boards[move][CASTLING][4] + AAA;
16185      } else {
16186
16187         /* [HGM] write true castling rights */
16188         if( nrCastlingRights == 6 ) {
16189             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16190                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16191             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16192                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16193             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16194                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16195             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16196                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16197         }
16198      }
16199      if (q == p) *p++ = '-'; /* No castling rights */
16200      *p++ = ' ';
16201   }
16202
16203   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16204      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16205     /* En passant target square */
16206     if (move > backwardMostMove) {
16207         fromX = moveList[move - 1][0] - AAA;
16208         fromY = moveList[move - 1][1] - ONE;
16209         toX = moveList[move - 1][2] - AAA;
16210         toY = moveList[move - 1][3] - ONE;
16211         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16212             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16213             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16214             fromX == toX) {
16215             /* 2-square pawn move just happened */
16216             *p++ = toX + AAA;
16217             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16218         } else {
16219             *p++ = '-';
16220         }
16221     } else if(move == backwardMostMove) {
16222         // [HGM] perhaps we should always do it like this, and forget the above?
16223         if((signed char)boards[move][EP_STATUS] >= 0) {
16224             *p++ = boards[move][EP_STATUS] + AAA;
16225             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16226         } else {
16227             *p++ = '-';
16228         }
16229     } else {
16230         *p++ = '-';
16231     }
16232     *p++ = ' ';
16233   }
16234   }
16235
16236     /* [HGM] find reversible plies */
16237     {   int i = 0, j=move;
16238
16239         if (appData.debugMode) { int k;
16240             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16241             for(k=backwardMostMove; k<=forwardMostMove; k++)
16242                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16243
16244         }
16245
16246         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16247         if( j == backwardMostMove ) i += initialRulePlies;
16248         sprintf(p, "%d ", i);
16249         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16250     }
16251     /* Fullmove number */
16252     sprintf(p, "%d", (move / 2) + 1);
16253
16254     return StrSave(buf);
16255 }
16256
16257 Boolean
16258 ParseFEN(board, blackPlaysFirst, fen)
16259     Board board;
16260      int *blackPlaysFirst;
16261      char *fen;
16262 {
16263     int i, j;
16264     char *p, c;
16265     int emptycount;
16266     ChessSquare piece;
16267
16268     p = fen;
16269
16270     /* [HGM] by default clear Crazyhouse holdings, if present */
16271     if(gameInfo.holdingsWidth) {
16272        for(i=0; i<BOARD_HEIGHT; i++) {
16273            board[i][0]             = EmptySquare; /* black holdings */
16274            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16275            board[i][1]             = (ChessSquare) 0; /* black counts */
16276            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16277        }
16278     }
16279
16280     /* Piece placement data */
16281     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16282         j = 0;
16283         for (;;) {
16284             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16285                 if (*p == '/') p++;
16286                 emptycount = gameInfo.boardWidth - j;
16287                 while (emptycount--)
16288                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16289                 break;
16290 #if(BOARD_FILES >= 10)
16291             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16292                 p++; emptycount=10;
16293                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16294                 while (emptycount--)
16295                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16296 #endif
16297             } else if (isdigit(*p)) {
16298                 emptycount = *p++ - '0';
16299                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16300                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16301                 while (emptycount--)
16302                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16303             } else if (*p == '+' || isalpha(*p)) {
16304                 if (j >= gameInfo.boardWidth) return FALSE;
16305                 if(*p=='+') {
16306                     piece = CharToPiece(*++p);
16307                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16308                     piece = (ChessSquare) (PROMOTED piece ); p++;
16309                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16310                 } else piece = CharToPiece(*p++);
16311
16312                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16313                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16314                     piece = (ChessSquare) (PROMOTED piece);
16315                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16316                     p++;
16317                 }
16318                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16319             } else {
16320                 return FALSE;
16321             }
16322         }
16323     }
16324     while (*p == '/' || *p == ' ') p++;
16325
16326     /* [HGM] look for Crazyhouse holdings here */
16327     while(*p==' ') p++;
16328     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16329         if(*p == '[') p++;
16330         if(*p == '-' ) p++; /* empty holdings */ else {
16331             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16332             /* if we would allow FEN reading to set board size, we would   */
16333             /* have to add holdings and shift the board read so far here   */
16334             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16335                 p++;
16336                 if((int) piece >= (int) BlackPawn ) {
16337                     i = (int)piece - (int)BlackPawn;
16338                     i = PieceToNumber((ChessSquare)i);
16339                     if( i >= gameInfo.holdingsSize ) return FALSE;
16340                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16341                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16342                 } else {
16343                     i = (int)piece - (int)WhitePawn;
16344                     i = PieceToNumber((ChessSquare)i);
16345                     if( i >= gameInfo.holdingsSize ) return FALSE;
16346                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16347                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16348                 }
16349             }
16350         }
16351         if(*p == ']') p++;
16352     }
16353
16354     while(*p == ' ') p++;
16355
16356     /* Active color */
16357     c = *p++;
16358     if(appData.colorNickNames) {
16359       if( c == appData.colorNickNames[0] ) c = 'w'; else
16360       if( c == appData.colorNickNames[1] ) c = 'b';
16361     }
16362     switch (c) {
16363       case 'w':
16364         *blackPlaysFirst = FALSE;
16365         break;
16366       case 'b':
16367         *blackPlaysFirst = TRUE;
16368         break;
16369       default:
16370         return FALSE;
16371     }
16372
16373     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16374     /* return the extra info in global variiables             */
16375
16376     /* set defaults in case FEN is incomplete */
16377     board[EP_STATUS] = EP_UNKNOWN;
16378     for(i=0; i<nrCastlingRights; i++ ) {
16379         board[CASTLING][i] =
16380             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16381     }   /* assume possible unless obviously impossible */
16382     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16383     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16384     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16385                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16386     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16387     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16388     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16389                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16390     FENrulePlies = 0;
16391
16392     while(*p==' ') p++;
16393     if(nrCastlingRights) {
16394       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16395           /* castling indicator present, so default becomes no castlings */
16396           for(i=0; i<nrCastlingRights; i++ ) {
16397                  board[CASTLING][i] = NoRights;
16398           }
16399       }
16400       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16401              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16402              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16403              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16404         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16405
16406         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16407             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16408             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16409         }
16410         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16411             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16412         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16413                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16414         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16415                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16416         switch(c) {
16417           case'K':
16418               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16419               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16420               board[CASTLING][2] = whiteKingFile;
16421               break;
16422           case'Q':
16423               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16424               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16425               board[CASTLING][2] = whiteKingFile;
16426               break;
16427           case'k':
16428               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16429               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16430               board[CASTLING][5] = blackKingFile;
16431               break;
16432           case'q':
16433               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16434               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16435               board[CASTLING][5] = blackKingFile;
16436           case '-':
16437               break;
16438           default: /* FRC castlings */
16439               if(c >= 'a') { /* black rights */
16440                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16441                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16442                   if(i == BOARD_RGHT) break;
16443                   board[CASTLING][5] = i;
16444                   c -= AAA;
16445                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16446                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16447                   if(c > i)
16448                       board[CASTLING][3] = c;
16449                   else
16450                       board[CASTLING][4] = c;
16451               } else { /* white rights */
16452                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16453                     if(board[0][i] == WhiteKing) break;
16454                   if(i == BOARD_RGHT) break;
16455                   board[CASTLING][2] = i;
16456                   c -= AAA - 'a' + 'A';
16457                   if(board[0][c] >= WhiteKing) break;
16458                   if(c > i)
16459                       board[CASTLING][0] = c;
16460                   else
16461                       board[CASTLING][1] = c;
16462               }
16463         }
16464       }
16465       for(i=0; i<nrCastlingRights; i++)
16466         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16467     if (appData.debugMode) {
16468         fprintf(debugFP, "FEN castling rights:");
16469         for(i=0; i<nrCastlingRights; i++)
16470         fprintf(debugFP, " %d", board[CASTLING][i]);
16471         fprintf(debugFP, "\n");
16472     }
16473
16474       while(*p==' ') p++;
16475     }
16476
16477     /* read e.p. field in games that know e.p. capture */
16478     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16479        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16480       if(*p=='-') {
16481         p++; board[EP_STATUS] = EP_NONE;
16482       } else {
16483          char c = *p++ - AAA;
16484
16485          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16486          if(*p >= '0' && *p <='9') p++;
16487          board[EP_STATUS] = c;
16488       }
16489     }
16490
16491
16492     if(sscanf(p, "%d", &i) == 1) {
16493         FENrulePlies = i; /* 50-move ply counter */
16494         /* (The move number is still ignored)    */
16495     }
16496
16497     return TRUE;
16498 }
16499
16500 void
16501 EditPositionPasteFEN(char *fen)
16502 {
16503   if (fen != NULL) {
16504     Board initial_position;
16505
16506     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16507       DisplayError(_("Bad FEN position in clipboard"), 0);
16508       return ;
16509     } else {
16510       int savedBlackPlaysFirst = blackPlaysFirst;
16511       EditPositionEvent();
16512       blackPlaysFirst = savedBlackPlaysFirst;
16513       CopyBoard(boards[0], initial_position);
16514       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16515       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16516       DisplayBothClocks();
16517       DrawPosition(FALSE, boards[currentMove]);
16518     }
16519   }
16520 }
16521
16522 static char cseq[12] = "\\   ";
16523
16524 Boolean set_cont_sequence(char *new_seq)
16525 {
16526     int len;
16527     Boolean ret;
16528
16529     // handle bad attempts to set the sequence
16530         if (!new_seq)
16531                 return 0; // acceptable error - no debug
16532
16533     len = strlen(new_seq);
16534     ret = (len > 0) && (len < sizeof(cseq));
16535     if (ret)
16536       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16537     else if (appData.debugMode)
16538       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16539     return ret;
16540 }
16541
16542 /*
16543     reformat a source message so words don't cross the width boundary.  internal
16544     newlines are not removed.  returns the wrapped size (no null character unless
16545     included in source message).  If dest is NULL, only calculate the size required
16546     for the dest buffer.  lp argument indicats line position upon entry, and it's
16547     passed back upon exit.
16548 */
16549 int wrap(char *dest, char *src, int count, int width, int *lp)
16550 {
16551     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16552
16553     cseq_len = strlen(cseq);
16554     old_line = line = *lp;
16555     ansi = len = clen = 0;
16556
16557     for (i=0; i < count; i++)
16558     {
16559         if (src[i] == '\033')
16560             ansi = 1;
16561
16562         // if we hit the width, back up
16563         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16564         {
16565             // store i & len in case the word is too long
16566             old_i = i, old_len = len;
16567
16568             // find the end of the last word
16569             while (i && src[i] != ' ' && src[i] != '\n')
16570             {
16571                 i--;
16572                 len--;
16573             }
16574
16575             // word too long?  restore i & len before splitting it
16576             if ((old_i-i+clen) >= width)
16577             {
16578                 i = old_i;
16579                 len = old_len;
16580             }
16581
16582             // extra space?
16583             if (i && src[i-1] == ' ')
16584                 len--;
16585
16586             if (src[i] != ' ' && src[i] != '\n')
16587             {
16588                 i--;
16589                 if (len)
16590                     len--;
16591             }
16592
16593             // now append the newline and continuation sequence
16594             if (dest)
16595                 dest[len] = '\n';
16596             len++;
16597             if (dest)
16598                 strncpy(dest+len, cseq, cseq_len);
16599             len += cseq_len;
16600             line = cseq_len;
16601             clen = cseq_len;
16602             continue;
16603         }
16604
16605         if (dest)
16606             dest[len] = src[i];
16607         len++;
16608         if (!ansi)
16609             line++;
16610         if (src[i] == '\n')
16611             line = 0;
16612         if (src[i] == 'm')
16613             ansi = 0;
16614     }
16615     if (dest && appData.debugMode)
16616     {
16617         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16618             count, width, line, len, *lp);
16619         show_bytes(debugFP, src, count);
16620         fprintf(debugFP, "\ndest: ");
16621         show_bytes(debugFP, dest, len);
16622         fprintf(debugFP, "\n");
16623     }
16624     *lp = dest ? line : old_line;
16625
16626     return len;
16627 }
16628
16629 // [HGM] vari: routines for shelving variations
16630 Boolean modeRestore = FALSE;
16631
16632 void
16633 PushInner(int firstMove, int lastMove)
16634 {
16635         int i, j, nrMoves = lastMove - firstMove;
16636
16637         // push current tail of game on stack
16638         savedResult[storedGames] = gameInfo.result;
16639         savedDetails[storedGames] = gameInfo.resultDetails;
16640         gameInfo.resultDetails = NULL;
16641         savedFirst[storedGames] = firstMove;
16642         savedLast [storedGames] = lastMove;
16643         savedFramePtr[storedGames] = framePtr;
16644         framePtr -= nrMoves; // reserve space for the boards
16645         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16646             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16647             for(j=0; j<MOVE_LEN; j++)
16648                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16649             for(j=0; j<2*MOVE_LEN; j++)
16650                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16651             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16652             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16653             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16654             pvInfoList[firstMove+i-1].depth = 0;
16655             commentList[framePtr+i] = commentList[firstMove+i];
16656             commentList[firstMove+i] = NULL;
16657         }
16658
16659         storedGames++;
16660         forwardMostMove = firstMove; // truncate game so we can start variation
16661 }
16662
16663 void
16664 PushTail(int firstMove, int lastMove)
16665 {
16666         if(appData.icsActive) { // only in local mode
16667                 forwardMostMove = currentMove; // mimic old ICS behavior
16668                 return;
16669         }
16670         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16671
16672         PushInner(firstMove, lastMove);
16673         if(storedGames == 1) GreyRevert(FALSE);
16674         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16675 }
16676
16677 void
16678 PopInner(Boolean annotate)
16679 {
16680         int i, j, nrMoves;
16681         char buf[8000], moveBuf[20];
16682
16683         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16684         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16685         nrMoves = savedLast[storedGames] - currentMove;
16686         if(annotate) {
16687                 int cnt = 10;
16688                 if(!WhiteOnMove(currentMove))
16689                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16690                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16691                 for(i=currentMove; i<forwardMostMove; i++) {
16692                         if(WhiteOnMove(i))
16693                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16694                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16695                         strcat(buf, moveBuf);
16696                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16697                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16698                 }
16699                 strcat(buf, ")");
16700         }
16701         for(i=1; i<=nrMoves; i++) { // copy last variation back
16702             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16703             for(j=0; j<MOVE_LEN; j++)
16704                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16705             for(j=0; j<2*MOVE_LEN; j++)
16706                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16707             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16708             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16709             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16710             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16711             commentList[currentMove+i] = commentList[framePtr+i];
16712             commentList[framePtr+i] = NULL;
16713         }
16714         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16715         framePtr = savedFramePtr[storedGames];
16716         gameInfo.result = savedResult[storedGames];
16717         if(gameInfo.resultDetails != NULL) {
16718             free(gameInfo.resultDetails);
16719       }
16720         gameInfo.resultDetails = savedDetails[storedGames];
16721         forwardMostMove = currentMove + nrMoves;
16722 }
16723
16724 Boolean
16725 PopTail(Boolean annotate)
16726 {
16727         if(appData.icsActive) return FALSE; // only in local mode
16728         if(!storedGames) return FALSE; // sanity
16729         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16730
16731         PopInner(annotate);
16732         if(currentMove < forwardMostMove) ForwardEvent(); else
16733         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16734
16735         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16736         return TRUE;
16737 }
16738
16739 void
16740 CleanupTail()
16741 {       // remove all shelved variations
16742         int i;
16743         for(i=0; i<storedGames; i++) {
16744             if(savedDetails[i])
16745                 free(savedDetails[i]);
16746             savedDetails[i] = NULL;
16747         }
16748         for(i=framePtr; i<MAX_MOVES; i++) {
16749                 if(commentList[i]) free(commentList[i]);
16750                 commentList[i] = NULL;
16751         }
16752         framePtr = MAX_MOVES-1;
16753         storedGames = 0;
16754 }
16755
16756 void
16757 LoadVariation(int index, char *text)
16758 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16759         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16760         int level = 0, move;
16761
16762         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16763         // first find outermost bracketing variation
16764         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16765             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16766                 if(*p == '{') wait = '}'; else
16767                 if(*p == '[') wait = ']'; else
16768                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16769                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16770             }
16771             if(*p == wait) wait = NULLCHAR; // closing ]} found
16772             p++;
16773         }
16774         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16775         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16776         end[1] = NULLCHAR; // clip off comment beyond variation
16777         ToNrEvent(currentMove-1);
16778         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16779         // kludge: use ParsePV() to append variation to game
16780         move = currentMove;
16781         ParsePV(start, TRUE, TRUE);
16782         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16783         ClearPremoveHighlights();
16784         CommentPopDown();
16785         ToNrEvent(currentMove+1);
16786 }
16787