e3b32e6aa68e004a27daa7963b2cbf7a7ff0ea70
[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) < 25 && abs(y - lastY) < 25) 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, threshold = (pieceSweep == EmptySquare ? 10 : 15);
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) < threshold && abs(y - lastY) < threshold) 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                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8638                         char buf[MSG_SIZ];
8639                         FILE *f;
8640                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8641                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8642                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8643                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8644                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8645                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8646                                 fclose(f);
8647                         } else DisplayError("failed writing PV", 0);
8648                 }
8649
8650                 tempStats.depth = plylev;
8651                 tempStats.nodes = nodes;
8652                 tempStats.time = time;
8653                 tempStats.score = curscore;
8654                 tempStats.got_only_move = 0;
8655
8656                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8657                         int ticklen;
8658
8659                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8660                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8661                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8662                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8663                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8664                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8665                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8666                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8667                 }
8668
8669                 /* Buffer overflow protection */
8670                 if (pv[0] != NULLCHAR) {
8671                     if (strlen(pv) >= sizeof(tempStats.movelist)
8672                         && appData.debugMode) {
8673                         fprintf(debugFP,
8674                                 "PV is too long; using the first %u bytes.\n",
8675                                 (unsigned) sizeof(tempStats.movelist) - 1);
8676                     }
8677
8678                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8679                 } else {
8680                     sprintf(tempStats.movelist, " no PV\n");
8681                 }
8682
8683                 if (tempStats.seen_stat) {
8684                     tempStats.ok_to_send = 1;
8685                 }
8686
8687                 if (strchr(tempStats.movelist, '(') != NULL) {
8688                     tempStats.line_is_book = 1;
8689                     tempStats.nr_moves = 0;
8690                     tempStats.moves_left = 0;
8691                 } else {
8692                     tempStats.line_is_book = 0;
8693                 }
8694
8695                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8696                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8697
8698                 SendProgramStatsToFrontend( cps, &tempStats );
8699
8700                 /*
8701                     [AS] Protect the thinkOutput buffer from overflow... this
8702                     is only useful if buf1 hasn't overflowed first!
8703                 */
8704                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8705                          plylev,
8706                          (gameMode == TwoMachinesPlay ?
8707                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8708                          ((double) curscore) / 100.0,
8709                          prefixHint ? lastHint : "",
8710                          prefixHint ? " " : "" );
8711
8712                 if( buf1[0] != NULLCHAR ) {
8713                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8714
8715                     if( strlen(pv) > max_len ) {
8716                         if( appData.debugMode) {
8717                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8718                         }
8719                         pv[max_len+1] = '\0';
8720                     }
8721
8722                     strcat( thinkOutput, pv);
8723                 }
8724
8725                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8726                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8727                     DisplayMove(currentMove - 1);
8728                 }
8729                 return;
8730
8731             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8732                 /* crafty (9.25+) says "(only move) <move>"
8733                  * if there is only 1 legal move
8734                  */
8735                 sscanf(p, "(only move) %s", buf1);
8736                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8737                 sprintf(programStats.movelist, "%s (only move)", buf1);
8738                 programStats.depth = 1;
8739                 programStats.nr_moves = 1;
8740                 programStats.moves_left = 1;
8741                 programStats.nodes = 1;
8742                 programStats.time = 1;
8743                 programStats.got_only_move = 1;
8744
8745                 /* Not really, but we also use this member to
8746                    mean "line isn't going to change" (Crafty
8747                    isn't searching, so stats won't change) */
8748                 programStats.line_is_book = 1;
8749
8750                 SendProgramStatsToFrontend( cps, &programStats );
8751
8752                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8753                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8754                     DisplayMove(currentMove - 1);
8755                 }
8756                 return;
8757             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8758                               &time, &nodes, &plylev, &mvleft,
8759                               &mvtot, mvname) >= 5) {
8760                 /* The stat01: line is from Crafty (9.29+) in response
8761                    to the "." command */
8762                 programStats.seen_stat = 1;
8763                 cps->maybeThinking = TRUE;
8764
8765                 if (programStats.got_only_move || !appData.periodicUpdates)
8766                   return;
8767
8768                 programStats.depth = plylev;
8769                 programStats.time = time;
8770                 programStats.nodes = nodes;
8771                 programStats.moves_left = mvleft;
8772                 programStats.nr_moves = mvtot;
8773                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8774                 programStats.ok_to_send = 1;
8775                 programStats.movelist[0] = '\0';
8776
8777                 SendProgramStatsToFrontend( cps, &programStats );
8778
8779                 return;
8780
8781             } else if (strncmp(message,"++",2) == 0) {
8782                 /* Crafty 9.29+ outputs this */
8783                 programStats.got_fail = 2;
8784                 return;
8785
8786             } else if (strncmp(message,"--",2) == 0) {
8787                 /* Crafty 9.29+ outputs this */
8788                 programStats.got_fail = 1;
8789                 return;
8790
8791             } else if (thinkOutput[0] != NULLCHAR &&
8792                        strncmp(message, "    ", 4) == 0) {
8793                 unsigned message_len;
8794
8795                 p = message;
8796                 while (*p && *p == ' ') p++;
8797
8798                 message_len = strlen( p );
8799
8800                 /* [AS] Avoid buffer overflow */
8801                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8802                     strcat(thinkOutput, " ");
8803                     strcat(thinkOutput, p);
8804                 }
8805
8806                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8807                     strcat(programStats.movelist, " ");
8808                     strcat(programStats.movelist, p);
8809                 }
8810
8811                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8812                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8813                     DisplayMove(currentMove - 1);
8814                 }
8815                 return;
8816             }
8817         }
8818         else {
8819             buf1[0] = NULLCHAR;
8820
8821             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8822                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8823             {
8824                 ChessProgramStats cpstats;
8825
8826                 if (plyext != ' ' && plyext != '\t') {
8827                     time *= 100;
8828                 }
8829
8830                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8831                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8832                     curscore = -curscore;
8833                 }
8834
8835                 cpstats.depth = plylev;
8836                 cpstats.nodes = nodes;
8837                 cpstats.time = time;
8838                 cpstats.score = curscore;
8839                 cpstats.got_only_move = 0;
8840                 cpstats.movelist[0] = '\0';
8841
8842                 if (buf1[0] != NULLCHAR) {
8843                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8844                 }
8845
8846                 cpstats.ok_to_send = 0;
8847                 cpstats.line_is_book = 0;
8848                 cpstats.nr_moves = 0;
8849                 cpstats.moves_left = 0;
8850
8851                 SendProgramStatsToFrontend( cps, &cpstats );
8852             }
8853         }
8854     }
8855 }
8856
8857
8858 /* Parse a game score from the character string "game", and
8859    record it as the history of the current game.  The game
8860    score is NOT assumed to start from the standard position.
8861    The display is not updated in any way.
8862    */
8863 void
8864 ParseGameHistory(game)
8865      char *game;
8866 {
8867     ChessMove moveType;
8868     int fromX, fromY, toX, toY, boardIndex;
8869     char promoChar;
8870     char *p, *q;
8871     char buf[MSG_SIZ];
8872
8873     if (appData.debugMode)
8874       fprintf(debugFP, "Parsing game history: %s\n", game);
8875
8876     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8877     gameInfo.site = StrSave(appData.icsHost);
8878     gameInfo.date = PGNDate();
8879     gameInfo.round = StrSave("-");
8880
8881     /* Parse out names of players */
8882     while (*game == ' ') game++;
8883     p = buf;
8884     while (*game != ' ') *p++ = *game++;
8885     *p = NULLCHAR;
8886     gameInfo.white = StrSave(buf);
8887     while (*game == ' ') game++;
8888     p = buf;
8889     while (*game != ' ' && *game != '\n') *p++ = *game++;
8890     *p = NULLCHAR;
8891     gameInfo.black = StrSave(buf);
8892
8893     /* Parse moves */
8894     boardIndex = blackPlaysFirst ? 1 : 0;
8895     yynewstr(game);
8896     for (;;) {
8897         yyboardindex = boardIndex;
8898         moveType = (ChessMove) Myylex();
8899         switch (moveType) {
8900           case IllegalMove:             /* maybe suicide chess, etc. */
8901   if (appData.debugMode) {
8902     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8903     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8904     setbuf(debugFP, NULL);
8905   }
8906           case WhitePromotion:
8907           case BlackPromotion:
8908           case WhiteNonPromotion:
8909           case BlackNonPromotion:
8910           case NormalMove:
8911           case WhiteCapturesEnPassant:
8912           case BlackCapturesEnPassant:
8913           case WhiteKingSideCastle:
8914           case WhiteQueenSideCastle:
8915           case BlackKingSideCastle:
8916           case BlackQueenSideCastle:
8917           case WhiteKingSideCastleWild:
8918           case WhiteQueenSideCastleWild:
8919           case BlackKingSideCastleWild:
8920           case BlackQueenSideCastleWild:
8921           /* PUSH Fabien */
8922           case WhiteHSideCastleFR:
8923           case WhiteASideCastleFR:
8924           case BlackHSideCastleFR:
8925           case BlackASideCastleFR:
8926           /* POP Fabien */
8927             fromX = currentMoveString[0] - AAA;
8928             fromY = currentMoveString[1] - ONE;
8929             toX = currentMoveString[2] - AAA;
8930             toY = currentMoveString[3] - ONE;
8931             promoChar = currentMoveString[4];
8932             break;
8933           case WhiteDrop:
8934           case BlackDrop:
8935             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8936             fromX = moveType == WhiteDrop ?
8937               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8938             (int) CharToPiece(ToLower(currentMoveString[0]));
8939             fromY = DROP_RANK;
8940             toX = currentMoveString[2] - AAA;
8941             toY = currentMoveString[3] - ONE;
8942             promoChar = NULLCHAR;
8943             break;
8944           case AmbiguousMove:
8945             /* bug? */
8946             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8947   if (appData.debugMode) {
8948     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8949     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8950     setbuf(debugFP, NULL);
8951   }
8952             DisplayError(buf, 0);
8953             return;
8954           case ImpossibleMove:
8955             /* bug? */
8956             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8957   if (appData.debugMode) {
8958     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8959     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8960     setbuf(debugFP, NULL);
8961   }
8962             DisplayError(buf, 0);
8963             return;
8964           case EndOfFile:
8965             if (boardIndex < backwardMostMove) {
8966                 /* Oops, gap.  How did that happen? */
8967                 DisplayError(_("Gap in move list"), 0);
8968                 return;
8969             }
8970             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8971             if (boardIndex > forwardMostMove) {
8972                 forwardMostMove = boardIndex;
8973             }
8974             return;
8975           case ElapsedTime:
8976             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8977                 strcat(parseList[boardIndex-1], " ");
8978                 strcat(parseList[boardIndex-1], yy_text);
8979             }
8980             continue;
8981           case Comment:
8982           case PGNTag:
8983           case NAG:
8984           default:
8985             /* ignore */
8986             continue;
8987           case WhiteWins:
8988           case BlackWins:
8989           case GameIsDrawn:
8990           case GameUnfinished:
8991             if (gameMode == IcsExamining) {
8992                 if (boardIndex < backwardMostMove) {
8993                     /* Oops, gap.  How did that happen? */
8994                     return;
8995                 }
8996                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8997                 return;
8998             }
8999             gameInfo.result = moveType;
9000             p = strchr(yy_text, '{');
9001             if (p == NULL) p = strchr(yy_text, '(');
9002             if (p == NULL) {
9003                 p = yy_text;
9004                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9005             } else {
9006                 q = strchr(p, *p == '{' ? '}' : ')');
9007                 if (q != NULL) *q = NULLCHAR;
9008                 p++;
9009             }
9010             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9011             gameInfo.resultDetails = StrSave(p);
9012             continue;
9013         }
9014         if (boardIndex >= forwardMostMove &&
9015             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9016             backwardMostMove = blackPlaysFirst ? 1 : 0;
9017             return;
9018         }
9019         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9020                                  fromY, fromX, toY, toX, promoChar,
9021                                  parseList[boardIndex]);
9022         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9023         /* currentMoveString is set as a side-effect of yylex */
9024         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9025         strcat(moveList[boardIndex], "\n");
9026         boardIndex++;
9027         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9028         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9029           case MT_NONE:
9030           case MT_STALEMATE:
9031           default:
9032             break;
9033           case MT_CHECK:
9034             if(gameInfo.variant != VariantShogi)
9035                 strcat(parseList[boardIndex - 1], "+");
9036             break;
9037           case MT_CHECKMATE:
9038           case MT_STAINMATE:
9039             strcat(parseList[boardIndex - 1], "#");
9040             break;
9041         }
9042     }
9043 }
9044
9045
9046 /* Apply a move to the given board  */
9047 void
9048 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9049      int fromX, fromY, toX, toY;
9050      int promoChar;
9051      Board board;
9052 {
9053   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9054   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9055
9056     /* [HGM] compute & store e.p. status and castling rights for new position */
9057     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9058
9059       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9060       oldEP = (signed char)board[EP_STATUS];
9061       board[EP_STATUS] = EP_NONE;
9062
9063   if (fromY == DROP_RANK) {
9064         /* must be first */
9065         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9066             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9067             return;
9068         }
9069         piece = board[toY][toX] = (ChessSquare) fromX;
9070   } else {
9071       int i;
9072
9073       if( board[toY][toX] != EmptySquare )
9074            board[EP_STATUS] = EP_CAPTURE;
9075
9076       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9077            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9078                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9079       } else
9080       if( board[fromY][fromX] == WhitePawn ) {
9081            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9082                board[EP_STATUS] = EP_PAWN_MOVE;
9083            if( toY-fromY==2) {
9084                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9085                         gameInfo.variant != VariantBerolina || toX < fromX)
9086                       board[EP_STATUS] = toX | berolina;
9087                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9088                         gameInfo.variant != VariantBerolina || toX > fromX)
9089                       board[EP_STATUS] = toX;
9090            }
9091       } else
9092       if( board[fromY][fromX] == BlackPawn ) {
9093            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9094                board[EP_STATUS] = EP_PAWN_MOVE;
9095            if( toY-fromY== -2) {
9096                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9097                         gameInfo.variant != VariantBerolina || toX < fromX)
9098                       board[EP_STATUS] = toX | berolina;
9099                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9100                         gameInfo.variant != VariantBerolina || toX > fromX)
9101                       board[EP_STATUS] = toX;
9102            }
9103        }
9104
9105        for(i=0; i<nrCastlingRights; i++) {
9106            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9107               board[CASTLING][i] == toX   && castlingRank[i] == toY
9108              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9109        }
9110
9111      if (fromX == toX && fromY == toY) return;
9112
9113      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9114      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9115      if(gameInfo.variant == VariantKnightmate)
9116          king += (int) WhiteUnicorn - (int) WhiteKing;
9117
9118     /* Code added by Tord: */
9119     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9120     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9121         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9122       board[fromY][fromX] = EmptySquare;
9123       board[toY][toX] = EmptySquare;
9124       if((toX > fromX) != (piece == WhiteRook)) {
9125         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9126       } else {
9127         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9128       }
9129     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9130                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9131       board[fromY][fromX] = EmptySquare;
9132       board[toY][toX] = EmptySquare;
9133       if((toX > fromX) != (piece == BlackRook)) {
9134         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9135       } else {
9136         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9137       }
9138     /* End of code added by Tord */
9139
9140     } else if (board[fromY][fromX] == king
9141         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9142         && toY == fromY && toX > fromX+1) {
9143         board[fromY][fromX] = EmptySquare;
9144         board[toY][toX] = king;
9145         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9146         board[fromY][BOARD_RGHT-1] = EmptySquare;
9147     } else if (board[fromY][fromX] == king
9148         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9149                && toY == fromY && toX < fromX-1) {
9150         board[fromY][fromX] = EmptySquare;
9151         board[toY][toX] = king;
9152         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9153         board[fromY][BOARD_LEFT] = EmptySquare;
9154     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9155                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9156                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9157                ) {
9158         /* white pawn promotion */
9159         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9160         if(gameInfo.variant==VariantBughouse ||
9161            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9162             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9163         board[fromY][fromX] = EmptySquare;
9164     } else if ((fromY >= BOARD_HEIGHT>>1)
9165                && (toX != fromX)
9166                && gameInfo.variant != VariantXiangqi
9167                && gameInfo.variant != VariantBerolina
9168                && (board[fromY][fromX] == WhitePawn)
9169                && (board[toY][toX] == EmptySquare)) {
9170         board[fromY][fromX] = EmptySquare;
9171         board[toY][toX] = WhitePawn;
9172         captured = board[toY - 1][toX];
9173         board[toY - 1][toX] = EmptySquare;
9174     } else if ((fromY == BOARD_HEIGHT-4)
9175                && (toX == fromX)
9176                && gameInfo.variant == VariantBerolina
9177                && (board[fromY][fromX] == WhitePawn)
9178                && (board[toY][toX] == EmptySquare)) {
9179         board[fromY][fromX] = EmptySquare;
9180         board[toY][toX] = WhitePawn;
9181         if(oldEP & EP_BEROLIN_A) {
9182                 captured = board[fromY][fromX-1];
9183                 board[fromY][fromX-1] = EmptySquare;
9184         }else{  captured = board[fromY][fromX+1];
9185                 board[fromY][fromX+1] = EmptySquare;
9186         }
9187     } else if (board[fromY][fromX] == king
9188         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9189                && toY == fromY && toX > fromX+1) {
9190         board[fromY][fromX] = EmptySquare;
9191         board[toY][toX] = king;
9192         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9193         board[fromY][BOARD_RGHT-1] = EmptySquare;
9194     } else if (board[fromY][fromX] == king
9195         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9196                && toY == fromY && toX < fromX-1) {
9197         board[fromY][fromX] = EmptySquare;
9198         board[toY][toX] = king;
9199         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9200         board[fromY][BOARD_LEFT] = EmptySquare;
9201     } else if (fromY == 7 && fromX == 3
9202                && board[fromY][fromX] == BlackKing
9203                && toY == 7 && toX == 5) {
9204         board[fromY][fromX] = EmptySquare;
9205         board[toY][toX] = BlackKing;
9206         board[fromY][7] = EmptySquare;
9207         board[toY][4] = BlackRook;
9208     } else if (fromY == 7 && fromX == 3
9209                && board[fromY][fromX] == BlackKing
9210                && toY == 7 && toX == 1) {
9211         board[fromY][fromX] = EmptySquare;
9212         board[toY][toX] = BlackKing;
9213         board[fromY][0] = EmptySquare;
9214         board[toY][2] = BlackRook;
9215     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9216                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9217                && toY < promoRank && promoChar
9218                ) {
9219         /* black pawn promotion */
9220         board[toY][toX] = CharToPiece(ToLower(promoChar));
9221         if(gameInfo.variant==VariantBughouse ||
9222            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9223             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9224         board[fromY][fromX] = EmptySquare;
9225     } else if ((fromY < BOARD_HEIGHT>>1)
9226                && (toX != fromX)
9227                && gameInfo.variant != VariantXiangqi
9228                && gameInfo.variant != VariantBerolina
9229                && (board[fromY][fromX] == BlackPawn)
9230                && (board[toY][toX] == EmptySquare)) {
9231         board[fromY][fromX] = EmptySquare;
9232         board[toY][toX] = BlackPawn;
9233         captured = board[toY + 1][toX];
9234         board[toY + 1][toX] = EmptySquare;
9235     } else if ((fromY == 3)
9236                && (toX == fromX)
9237                && gameInfo.variant == VariantBerolina
9238                && (board[fromY][fromX] == BlackPawn)
9239                && (board[toY][toX] == EmptySquare)) {
9240         board[fromY][fromX] = EmptySquare;
9241         board[toY][toX] = BlackPawn;
9242         if(oldEP & EP_BEROLIN_A) {
9243                 captured = board[fromY][fromX-1];
9244                 board[fromY][fromX-1] = EmptySquare;
9245         }else{  captured = board[fromY][fromX+1];
9246                 board[fromY][fromX+1] = EmptySquare;
9247         }
9248     } else {
9249         board[toY][toX] = board[fromY][fromX];
9250         board[fromY][fromX] = EmptySquare;
9251     }
9252   }
9253
9254     if (gameInfo.holdingsWidth != 0) {
9255
9256       /* !!A lot more code needs to be written to support holdings  */
9257       /* [HGM] OK, so I have written it. Holdings are stored in the */
9258       /* penultimate board files, so they are automaticlly stored   */
9259       /* in the game history.                                       */
9260       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9261                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9262         /* Delete from holdings, by decreasing count */
9263         /* and erasing image if necessary            */
9264         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9265         if(p < (int) BlackPawn) { /* white drop */
9266              p -= (int)WhitePawn;
9267                  p = PieceToNumber((ChessSquare)p);
9268              if(p >= gameInfo.holdingsSize) p = 0;
9269              if(--board[p][BOARD_WIDTH-2] <= 0)
9270                   board[p][BOARD_WIDTH-1] = EmptySquare;
9271              if((int)board[p][BOARD_WIDTH-2] < 0)
9272                         board[p][BOARD_WIDTH-2] = 0;
9273         } else {                  /* black drop */
9274              p -= (int)BlackPawn;
9275                  p = PieceToNumber((ChessSquare)p);
9276              if(p >= gameInfo.holdingsSize) p = 0;
9277              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9278                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9279              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9280                         board[BOARD_HEIGHT-1-p][1] = 0;
9281         }
9282       }
9283       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9284           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9285         /* [HGM] holdings: Add to holdings, if holdings exist */
9286         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9287                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9288                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9289         }
9290         p = (int) captured;
9291         if (p >= (int) BlackPawn) {
9292           p -= (int)BlackPawn;
9293           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9294                   /* in Shogi restore piece to its original  first */
9295                   captured = (ChessSquare) (DEMOTED captured);
9296                   p = DEMOTED p;
9297           }
9298           p = PieceToNumber((ChessSquare)p);
9299           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9300           board[p][BOARD_WIDTH-2]++;
9301           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9302         } else {
9303           p -= (int)WhitePawn;
9304           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9305                   captured = (ChessSquare) (DEMOTED captured);
9306                   p = DEMOTED p;
9307           }
9308           p = PieceToNumber((ChessSquare)p);
9309           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9310           board[BOARD_HEIGHT-1-p][1]++;
9311           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9312         }
9313       }
9314     } else if (gameInfo.variant == VariantAtomic) {
9315       if (captured != EmptySquare) {
9316         int y, x;
9317         for (y = toY-1; y <= toY+1; y++) {
9318           for (x = toX-1; x <= toX+1; x++) {
9319             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9320                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9321               board[y][x] = EmptySquare;
9322             }
9323           }
9324         }
9325         board[toY][toX] = EmptySquare;
9326       }
9327     }
9328     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9329         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9330     } else
9331     if(promoChar == '+') {
9332         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9333         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9334     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9335         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9336     }
9337     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9338                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9339         // [HGM] superchess: take promotion piece out of holdings
9340         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9341         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9342             if(!--board[k][BOARD_WIDTH-2])
9343                 board[k][BOARD_WIDTH-1] = EmptySquare;
9344         } else {
9345             if(!--board[BOARD_HEIGHT-1-k][1])
9346                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9347         }
9348     }
9349
9350 }
9351
9352 /* Updates forwardMostMove */
9353 void
9354 MakeMove(fromX, fromY, toX, toY, promoChar)
9355      int fromX, fromY, toX, toY;
9356      int promoChar;
9357 {
9358 //    forwardMostMove++; // [HGM] bare: moved downstream
9359
9360     (void) CoordsToAlgebraic(boards[forwardMostMove],
9361                              PosFlags(forwardMostMove),
9362                              fromY, fromX, toY, toX, promoChar,
9363                              parseList[forwardMostMove]);
9364
9365     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9366         int timeLeft; static int lastLoadFlag=0; int king, piece;
9367         piece = boards[forwardMostMove][fromY][fromX];
9368         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9369         if(gameInfo.variant == VariantKnightmate)
9370             king += (int) WhiteUnicorn - (int) WhiteKing;
9371         if(forwardMostMove == 0) {
9372             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9373                 fprintf(serverMoves, "%s;", UserName());
9374             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9375                 fprintf(serverMoves, "%s;", second.tidy);
9376             fprintf(serverMoves, "%s;", first.tidy);
9377             if(gameMode == MachinePlaysWhite)
9378                 fprintf(serverMoves, "%s;", UserName());
9379             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9380                 fprintf(serverMoves, "%s;", second.tidy);
9381         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9382         lastLoadFlag = loadFlag;
9383         // print base move
9384         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9385         // print castling suffix
9386         if( toY == fromY && piece == king ) {
9387             if(toX-fromX > 1)
9388                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9389             if(fromX-toX >1)
9390                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9391         }
9392         // e.p. suffix
9393         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9394              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9395              boards[forwardMostMove][toY][toX] == EmptySquare
9396              && fromX != toX && fromY != toY)
9397                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9398         // promotion suffix
9399         if(promoChar != NULLCHAR)
9400                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9401         if(!loadFlag) {
9402                 char buf[MOVE_LEN*2], *p; int len;
9403             fprintf(serverMoves, "/%d/%d",
9404                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9405             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9406             else                      timeLeft = blackTimeRemaining/1000;
9407             fprintf(serverMoves, "/%d", timeLeft);
9408                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9409                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9410                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9411             fprintf(serverMoves, "/%s", buf);
9412         }
9413         fflush(serverMoves);
9414     }
9415
9416     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9417       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9418                         0, 1);
9419       return;
9420     }
9421     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9422     if (commentList[forwardMostMove+1] != NULL) {
9423         free(commentList[forwardMostMove+1]);
9424         commentList[forwardMostMove+1] = NULL;
9425     }
9426     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9427     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9428     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9429     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9430     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9431     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9432     gameInfo.result = GameUnfinished;
9433     if (gameInfo.resultDetails != NULL) {
9434         free(gameInfo.resultDetails);
9435         gameInfo.resultDetails = NULL;
9436     }
9437     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9438                               moveList[forwardMostMove - 1]);
9439     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9440       case MT_NONE:
9441       case MT_STALEMATE:
9442       default:
9443         break;
9444       case MT_CHECK:
9445         if(gameInfo.variant != VariantShogi)
9446             strcat(parseList[forwardMostMove - 1], "+");
9447         break;
9448       case MT_CHECKMATE:
9449       case MT_STAINMATE:
9450         strcat(parseList[forwardMostMove - 1], "#");
9451         break;
9452     }
9453     if (appData.debugMode) {
9454         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9455     }
9456
9457 }
9458
9459 /* Updates currentMove if not pausing */
9460 void
9461 ShowMove(fromX, fromY, toX, toY)
9462 {
9463     int instant = (gameMode == PlayFromGameFile) ?
9464         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9465     if(appData.noGUI) return;
9466     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9467         if (!instant) {
9468             if (forwardMostMove == currentMove + 1) {
9469                 AnimateMove(boards[forwardMostMove - 1],
9470                             fromX, fromY, toX, toY);
9471             }
9472             if (appData.highlightLastMove) {
9473                 SetHighlights(fromX, fromY, toX, toY);
9474             }
9475         }
9476         currentMove = forwardMostMove;
9477     }
9478
9479     if (instant) return;
9480
9481     DisplayMove(currentMove - 1);
9482     DrawPosition(FALSE, boards[currentMove]);
9483     DisplayBothClocks();
9484     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9485     DisplayBook(currentMove);
9486 }
9487
9488 void SendEgtPath(ChessProgramState *cps)
9489 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9490         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9491
9492         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9493
9494         while(*p) {
9495             char c, *q = name+1, *r, *s;
9496
9497             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9498             while(*p && *p != ',') *q++ = *p++;
9499             *q++ = ':'; *q = 0;
9500             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9501                 strcmp(name, ",nalimov:") == 0 ) {
9502                 // take nalimov path from the menu-changeable option first, if it is defined
9503               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9504                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9505             } else
9506             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9507                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9508                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9509                 s = r = StrStr(s, ":") + 1; // beginning of path info
9510                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9511                 c = *r; *r = 0;             // temporarily null-terminate path info
9512                     *--q = 0;               // strip of trailig ':' from name
9513                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9514                 *r = c;
9515                 SendToProgram(buf,cps);     // send egtbpath command for this format
9516             }
9517             if(*p == ',') p++; // read away comma to position for next format name
9518         }
9519 }
9520
9521 void
9522 InitChessProgram(cps, setup)
9523      ChessProgramState *cps;
9524      int setup; /* [HGM] needed to setup FRC opening position */
9525 {
9526     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9527     if (appData.noChessProgram) return;
9528     hintRequested = FALSE;
9529     bookRequested = FALSE;
9530
9531     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9532     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9533     if(cps->memSize) { /* [HGM] memory */
9534       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9535         SendToProgram(buf, cps);
9536     }
9537     SendEgtPath(cps); /* [HGM] EGT */
9538     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9539       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9540         SendToProgram(buf, cps);
9541     }
9542
9543     SendToProgram(cps->initString, cps);
9544     if (gameInfo.variant != VariantNormal &&
9545         gameInfo.variant != VariantLoadable
9546         /* [HGM] also send variant if board size non-standard */
9547         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9548                                             ) {
9549       char *v = VariantName(gameInfo.variant);
9550       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9551         /* [HGM] in protocol 1 we have to assume all variants valid */
9552         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9553         DisplayFatalError(buf, 0, 1);
9554         return;
9555       }
9556
9557       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9558       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9559       if( gameInfo.variant == VariantXiangqi )
9560            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9561       if( gameInfo.variant == VariantShogi )
9562            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9563       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9564            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9565       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9566           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9567            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9568       if( gameInfo.variant == VariantCourier )
9569            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9570       if( gameInfo.variant == VariantSuper )
9571            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9572       if( gameInfo.variant == VariantGreat )
9573            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9574       if( gameInfo.variant == VariantSChess )
9575            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9576       if( gameInfo.variant == VariantGrand )
9577            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9578
9579       if(overruled) {
9580         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9581                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9582            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9583            if(StrStr(cps->variants, b) == NULL) {
9584                // specific sized variant not known, check if general sizing allowed
9585                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9586                    if(StrStr(cps->variants, "boardsize") == NULL) {
9587                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9588                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9589                        DisplayFatalError(buf, 0, 1);
9590                        return;
9591                    }
9592                    /* [HGM] here we really should compare with the maximum supported board size */
9593                }
9594            }
9595       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9596       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9597       SendToProgram(buf, cps);
9598     }
9599     currentlyInitializedVariant = gameInfo.variant;
9600
9601     /* [HGM] send opening position in FRC to first engine */
9602     if(setup) {
9603           SendToProgram("force\n", cps);
9604           SendBoard(cps, 0);
9605           /* engine is now in force mode! Set flag to wake it up after first move. */
9606           setboardSpoiledMachineBlack = 1;
9607     }
9608
9609     if (cps->sendICS) {
9610       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9611       SendToProgram(buf, cps);
9612     }
9613     cps->maybeThinking = FALSE;
9614     cps->offeredDraw = 0;
9615     if (!appData.icsActive) {
9616         SendTimeControl(cps, movesPerSession, timeControl,
9617                         timeIncrement, appData.searchDepth,
9618                         searchTime);
9619     }
9620     if (appData.showThinking
9621         // [HGM] thinking: four options require thinking output to be sent
9622         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9623                                 ) {
9624         SendToProgram("post\n", cps);
9625     }
9626     SendToProgram("hard\n", cps);
9627     if (!appData.ponderNextMove) {
9628         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9629            it without being sure what state we are in first.  "hard"
9630            is not a toggle, so that one is OK.
9631          */
9632         SendToProgram("easy\n", cps);
9633     }
9634     if (cps->usePing) {
9635       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9636       SendToProgram(buf, cps);
9637     }
9638     cps->initDone = TRUE;
9639     ClearEngineOutputPane(cps == &second);
9640 }
9641
9642
9643 void
9644 StartChessProgram(cps)
9645      ChessProgramState *cps;
9646 {
9647     char buf[MSG_SIZ];
9648     int err;
9649
9650     if (appData.noChessProgram) return;
9651     cps->initDone = FALSE;
9652
9653     if (strcmp(cps->host, "localhost") == 0) {
9654         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9655     } else if (*appData.remoteShell == NULLCHAR) {
9656         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9657     } else {
9658         if (*appData.remoteUser == NULLCHAR) {
9659           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9660                     cps->program);
9661         } else {
9662           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9663                     cps->host, appData.remoteUser, cps->program);
9664         }
9665         err = StartChildProcess(buf, "", &cps->pr);
9666     }
9667
9668     if (err != 0) {
9669       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9670         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9671         if(cps != &first) return;
9672         appData.noChessProgram = TRUE;
9673         ThawUI();
9674         SetNCPMode();
9675 //      DisplayFatalError(buf, err, 1);
9676 //      cps->pr = NoProc;
9677 //      cps->isr = NULL;
9678         return;
9679     }
9680
9681     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9682     if (cps->protocolVersion > 1) {
9683       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9684       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9685       cps->comboCnt = 0;  //                and values of combo boxes
9686       SendToProgram(buf, cps);
9687     } else {
9688       SendToProgram("xboard\n", cps);
9689     }
9690 }
9691
9692 void
9693 TwoMachinesEventIfReady P((void))
9694 {
9695   static int curMess = 0;
9696   if (first.lastPing != first.lastPong) {
9697     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9698     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9699     return;
9700   }
9701   if (second.lastPing != second.lastPong) {
9702     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9703     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9704     return;
9705   }
9706   DisplayMessage("", ""); curMess = 0;
9707   ThawUI();
9708   TwoMachinesEvent();
9709 }
9710
9711 char *
9712 MakeName(char *template)
9713 {
9714     time_t clock;
9715     struct tm *tm;
9716     static char buf[MSG_SIZ];
9717     char *p = buf;
9718     int i;
9719
9720     clock = time((time_t *)NULL);
9721     tm = localtime(&clock);
9722
9723     while(*p++ = *template++) if(p[-1] == '%') {
9724         switch(*template++) {
9725           case 0:   *p = 0; return buf;
9726           case 'Y': i = tm->tm_year+1900; break;
9727           case 'y': i = tm->tm_year-100; break;
9728           case 'M': i = tm->tm_mon+1; break;
9729           case 'd': i = tm->tm_mday; break;
9730           case 'h': i = tm->tm_hour; break;
9731           case 'm': i = tm->tm_min; break;
9732           case 's': i = tm->tm_sec; break;
9733           default:  i = 0;
9734         }
9735         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9736     }
9737     return buf;
9738 }
9739
9740 int
9741 CountPlayers(char *p)
9742 {
9743     int n = 0;
9744     while(p = strchr(p, '\n')) p++, n++; // count participants
9745     return n;
9746 }
9747
9748 FILE *
9749 WriteTourneyFile(char *results, FILE *f)
9750 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9751     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9752     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9753         // create a file with tournament description
9754         fprintf(f, "-participants {%s}\n", appData.participants);
9755         fprintf(f, "-seedBase %d\n", appData.seedBase);
9756         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9757         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9758         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9759         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9760         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9761         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9762         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9763         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9764         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9765         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9766         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9767         if(searchTime > 0)
9768                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9769         else {
9770                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9771                 fprintf(f, "-tc %s\n", appData.timeControl);
9772                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9773         }
9774         fprintf(f, "-results \"%s\"\n", results);
9775     }
9776     return f;
9777 }
9778
9779 #define MAXENGINES 1000
9780 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9781
9782 void Substitute(char *participants, int expunge)
9783 {
9784     int i, changed, changes=0, nPlayers=0;
9785     char *p, *q, *r, buf[MSG_SIZ];
9786     if(participants == NULL) return;
9787     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9788     r = p = participants; q = appData.participants;
9789     while(*p && *p == *q) {
9790         if(*p == '\n') r = p+1, nPlayers++;
9791         p++; q++;
9792     }
9793     if(*p) { // difference
9794         while(*p && *p++ != '\n');
9795         while(*q && *q++ != '\n');
9796       changed = nPlayers;
9797         changes = 1 + (strcmp(p, q) != 0);
9798     }
9799     if(changes == 1) { // a single engine mnemonic was changed
9800         q = r; while(*q) nPlayers += (*q++ == '\n');
9801         p = buf; while(*r && (*p = *r++) != '\n') p++;
9802         *p = NULLCHAR;
9803         NamesToList(firstChessProgramNames, command, mnemonic);
9804         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9805         if(mnemonic[i]) { // The substitute is valid
9806             FILE *f;
9807             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9808                 flock(fileno(f), LOCK_EX);
9809                 ParseArgsFromFile(f);
9810                 fseek(f, 0, SEEK_SET);
9811                 FREE(appData.participants); appData.participants = participants;
9812                 if(expunge) { // erase results of replaced engine
9813                     int len = strlen(appData.results), w, b, dummy;
9814                     for(i=0; i<len; i++) {
9815                         Pairing(i, nPlayers, &w, &b, &dummy);
9816                         if((w == changed || b == changed) && appData.results[i] == '*') {
9817                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9818                             fclose(f);
9819                             return;
9820                         }
9821                     }
9822                     for(i=0; i<len; i++) {
9823                         Pairing(i, nPlayers, &w, &b, &dummy);
9824                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9825                     }
9826                 }
9827                 WriteTourneyFile(appData.results, f);
9828                 fclose(f); // release lock
9829                 return;
9830             }
9831         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9832     }
9833     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9834     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9835     free(participants);
9836     return;
9837 }
9838
9839 int
9840 CreateTourney(char *name)
9841 {
9842         FILE *f;
9843         if(matchMode && strcmp(name, appData.tourneyFile)) {
9844              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9845         }
9846         if(name[0] == NULLCHAR) {
9847             if(appData.participants[0])
9848                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9849             return 0;
9850         }
9851         f = fopen(name, "r");
9852         if(f) { // file exists
9853             ASSIGN(appData.tourneyFile, name);
9854             ParseArgsFromFile(f); // parse it
9855         } else {
9856             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9857             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9858                 DisplayError(_("Not enough participants"), 0);
9859                 return 0;
9860             }
9861             ASSIGN(appData.tourneyFile, name);
9862             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9863             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9864         }
9865         fclose(f);
9866         appData.noChessProgram = FALSE;
9867         appData.clockMode = TRUE;
9868         SetGNUMode();
9869         return 1;
9870 }
9871
9872 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9873 {
9874     char buf[MSG_SIZ], *p, *q;
9875     int i=1;
9876     while(*names) {
9877         p = names; q = buf;
9878         while(*p && *p != '\n') *q++ = *p++;
9879         *q = 0;
9880         if(engineList[i]) free(engineList[i]);
9881         engineList[i] = strdup(buf);
9882         if(*p == '\n') p++;
9883         TidyProgramName(engineList[i], "localhost", buf);
9884         if(engineMnemonic[i]) free(engineMnemonic[i]);
9885         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9886             strcat(buf, " (");
9887             sscanf(q + 8, "%s", buf + strlen(buf));
9888             strcat(buf, ")");
9889         }
9890         engineMnemonic[i] = strdup(buf);
9891         names = p; i++;
9892       if(i > MAXENGINES - 2) break;
9893     }
9894     engineList[i] = engineMnemonic[i] = NULL;
9895 }
9896
9897 // following implemented as macro to avoid type limitations
9898 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9899
9900 void SwapEngines(int n)
9901 {   // swap settings for first engine and other engine (so far only some selected options)
9902     int h;
9903     char *p;
9904     if(n == 0) return;
9905     SWAP(directory, p)
9906     SWAP(chessProgram, p)
9907     SWAP(isUCI, h)
9908     SWAP(hasOwnBookUCI, h)
9909     SWAP(protocolVersion, h)
9910     SWAP(reuse, h)
9911     SWAP(scoreIsAbsolute, h)
9912     SWAP(timeOdds, h)
9913     SWAP(logo, p)
9914     SWAP(pgnName, p)
9915     SWAP(pvSAN, h)
9916 }
9917
9918 void
9919 SetPlayer(int player)
9920 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9921     int i;
9922     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9923     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9924     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9925     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9926     if(mnemonic[i]) {
9927         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9928         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9929         ParseArgsFromString(buf);
9930     }
9931     free(engineName);
9932 }
9933
9934 int
9935 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9936 {   // determine players from game number
9937     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9938
9939     if(appData.tourneyType == 0) {
9940         roundsPerCycle = (nPlayers - 1) | 1;
9941         pairingsPerRound = nPlayers / 2;
9942     } else if(appData.tourneyType > 0) {
9943         roundsPerCycle = nPlayers - appData.tourneyType;
9944         pairingsPerRound = appData.tourneyType;
9945     }
9946     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9947     gamesPerCycle = gamesPerRound * roundsPerCycle;
9948     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9949     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9950     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9951     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9952     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9953     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9954
9955     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9956     if(appData.roundSync) *syncInterval = gamesPerRound;
9957
9958     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9959
9960     if(appData.tourneyType == 0) {
9961         if(curPairing == (nPlayers-1)/2 ) {
9962             *whitePlayer = curRound;
9963             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9964         } else {
9965             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9966             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9967             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9968             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9969         }
9970     } else if(appData.tourneyType > 0) {
9971         *whitePlayer = curPairing;
9972         *blackPlayer = curRound + appData.tourneyType;
9973     }
9974
9975     // take care of white/black alternation per round. 
9976     // For cycles and games this is already taken care of by default, derived from matchGame!
9977     return curRound & 1;
9978 }
9979
9980 int
9981 NextTourneyGame(int nr, int *swapColors)
9982 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9983     char *p, *q;
9984     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9985     FILE *tf;
9986     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9987     tf = fopen(appData.tourneyFile, "r");
9988     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9989     ParseArgsFromFile(tf); fclose(tf);
9990     InitTimeControls(); // TC might be altered from tourney file
9991
9992     nPlayers = CountPlayers(appData.participants); // count participants
9993     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9994     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9995
9996     if(syncInterval) {
9997         p = q = appData.results;
9998         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9999         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10000             DisplayMessage(_("Waiting for other game(s)"),"");
10001             waitingForGame = TRUE;
10002             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10003             return 0;
10004         }
10005         waitingForGame = FALSE;
10006     }
10007
10008     if(appData.tourneyType < 0) {
10009         if(nr>=0 && !pairingReceived) {
10010             char buf[1<<16];
10011             if(pairing.pr == NoProc) {
10012                 if(!appData.pairingEngine[0]) {
10013                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10014                     return 0;
10015                 }
10016                 StartChessProgram(&pairing); // starts the pairing engine
10017             }
10018             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10019             SendToProgram(buf, &pairing);
10020             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10021             SendToProgram(buf, &pairing);
10022             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10023         }
10024         pairingReceived = 0;                              // ... so we continue here 
10025         *swapColors = 0;
10026         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10027         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10028         matchGame = 1; roundNr = nr / syncInterval + 1;
10029     }
10030
10031     if(first.pr != NoProc) return 1; // engines already loaded
10032
10033     // redefine engines, engine dir, etc.
10034     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10035     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10036     SwapEngines(1);
10037     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10038     SwapEngines(1);         // and make that valid for second engine by swapping
10039     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10040     InitEngine(&second, 1);
10041     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10042     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10043     return 1;
10044 }
10045
10046 void
10047 NextMatchGame()
10048 {   // performs game initialization that does not invoke engines, and then tries to start the game
10049     int firstWhite, swapColors = 0;
10050     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10051     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10052     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10053     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10054     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10055     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10056     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10057     Reset(FALSE, first.pr != NoProc);
10058     appData.noChessProgram = FALSE;
10059     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
10060     TwoMachinesEvent();
10061 }
10062
10063 void UserAdjudicationEvent( int result )
10064 {
10065     ChessMove gameResult = GameIsDrawn;
10066
10067     if( result > 0 ) {
10068         gameResult = WhiteWins;
10069     }
10070     else if( result < 0 ) {
10071         gameResult = BlackWins;
10072     }
10073
10074     if( gameMode == TwoMachinesPlay ) {
10075         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10076     }
10077 }
10078
10079
10080 // [HGM] save: calculate checksum of game to make games easily identifiable
10081 int StringCheckSum(char *s)
10082 {
10083         int i = 0;
10084         if(s==NULL) return 0;
10085         while(*s) i = i*259 + *s++;
10086         return i;
10087 }
10088
10089 int GameCheckSum()
10090 {
10091         int i, sum=0;
10092         for(i=backwardMostMove; i<forwardMostMove; i++) {
10093                 sum += pvInfoList[i].depth;
10094                 sum += StringCheckSum(parseList[i]);
10095                 sum += StringCheckSum(commentList[i]);
10096                 sum *= 261;
10097         }
10098         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10099         return sum + StringCheckSum(commentList[i]);
10100 } // end of save patch
10101
10102 void
10103 GameEnds(result, resultDetails, whosays)
10104      ChessMove result;
10105      char *resultDetails;
10106      int whosays;
10107 {
10108     GameMode nextGameMode;
10109     int isIcsGame;
10110     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10111
10112     if(endingGame) return; /* [HGM] crash: forbid recursion */
10113     endingGame = 1;
10114     if(twoBoards) { // [HGM] dual: switch back to one board
10115         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10116         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10117     }
10118     if (appData.debugMode) {
10119       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10120               result, resultDetails ? resultDetails : "(null)", whosays);
10121     }
10122
10123     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10124
10125     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10126         /* If we are playing on ICS, the server decides when the
10127            game is over, but the engine can offer to draw, claim
10128            a draw, or resign.
10129          */
10130 #if ZIPPY
10131         if (appData.zippyPlay && first.initDone) {
10132             if (result == GameIsDrawn) {
10133                 /* In case draw still needs to be claimed */
10134                 SendToICS(ics_prefix);
10135                 SendToICS("draw\n");
10136             } else if (StrCaseStr(resultDetails, "resign")) {
10137                 SendToICS(ics_prefix);
10138                 SendToICS("resign\n");
10139             }
10140         }
10141 #endif
10142         endingGame = 0; /* [HGM] crash */
10143         return;
10144     }
10145
10146     /* If we're loading the game from a file, stop */
10147     if (whosays == GE_FILE) {
10148       (void) StopLoadGameTimer();
10149       gameFileFP = NULL;
10150     }
10151
10152     /* Cancel draw offers */
10153     first.offeredDraw = second.offeredDraw = 0;
10154
10155     /* If this is an ICS game, only ICS can really say it's done;
10156        if not, anyone can. */
10157     isIcsGame = (gameMode == IcsPlayingWhite ||
10158                  gameMode == IcsPlayingBlack ||
10159                  gameMode == IcsObserving    ||
10160                  gameMode == IcsExamining);
10161
10162     if (!isIcsGame || whosays == GE_ICS) {
10163         /* OK -- not an ICS game, or ICS said it was done */
10164         StopClocks();
10165         if (!isIcsGame && !appData.noChessProgram)
10166           SetUserThinkingEnables();
10167
10168         /* [HGM] if a machine claims the game end we verify this claim */
10169         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10170             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10171                 char claimer;
10172                 ChessMove trueResult = (ChessMove) -1;
10173
10174                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10175                                             first.twoMachinesColor[0] :
10176                                             second.twoMachinesColor[0] ;
10177
10178                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10179                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10180                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10181                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10182                 } else
10183                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10184                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10185                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10186                 } else
10187                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10188                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10189                 }
10190
10191                 // now verify win claims, but not in drop games, as we don't understand those yet
10192                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10193                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10194                     (result == WhiteWins && claimer == 'w' ||
10195                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10196                       if (appData.debugMode) {
10197                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10198                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10199                       }
10200                       if(result != trueResult) {
10201                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10202                               result = claimer == 'w' ? BlackWins : WhiteWins;
10203                               resultDetails = buf;
10204                       }
10205                 } else
10206                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10207                     && (forwardMostMove <= backwardMostMove ||
10208                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10209                         (claimer=='b')==(forwardMostMove&1))
10210                                                                                   ) {
10211                       /* [HGM] verify: draws that were not flagged are false claims */
10212                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10213                       result = claimer == 'w' ? BlackWins : WhiteWins;
10214                       resultDetails = buf;
10215                 }
10216                 /* (Claiming a loss is accepted no questions asked!) */
10217             }
10218             /* [HGM] bare: don't allow bare King to win */
10219             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10220                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10221                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10222                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10223                && result != GameIsDrawn)
10224             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10225                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10226                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10227                         if(p >= 0 && p <= (int)WhiteKing) k++;
10228                 }
10229                 if (appData.debugMode) {
10230                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10231                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10232                 }
10233                 if(k <= 1) {
10234                         result = GameIsDrawn;
10235                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10236                         resultDetails = buf;
10237                 }
10238             }
10239         }
10240
10241
10242         if(serverMoves != NULL && !loadFlag) { char c = '=';
10243             if(result==WhiteWins) c = '+';
10244             if(result==BlackWins) c = '-';
10245             if(resultDetails != NULL)
10246                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10247         }
10248         if (resultDetails != NULL) {
10249             gameInfo.result = result;
10250             gameInfo.resultDetails = StrSave(resultDetails);
10251
10252             /* display last move only if game was not loaded from file */
10253             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10254                 DisplayMove(currentMove - 1);
10255
10256             if (forwardMostMove != 0) {
10257                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10258                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10259                                                                 ) {
10260                     if (*appData.saveGameFile != NULLCHAR) {
10261                         SaveGameToFile(appData.saveGameFile, TRUE);
10262                     } else if (appData.autoSaveGames) {
10263                         AutoSaveGame();
10264                     }
10265                     if (*appData.savePositionFile != NULLCHAR) {
10266                         SavePositionToFile(appData.savePositionFile);
10267                     }
10268                 }
10269             }
10270
10271             /* Tell program how game ended in case it is learning */
10272             /* [HGM] Moved this to after saving the PGN, just in case */
10273             /* engine died and we got here through time loss. In that */
10274             /* case we will get a fatal error writing the pipe, which */
10275             /* would otherwise lose us the PGN.                       */
10276             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10277             /* output during GameEnds should never be fatal anymore   */
10278             if (gameMode == MachinePlaysWhite ||
10279                 gameMode == MachinePlaysBlack ||
10280                 gameMode == TwoMachinesPlay ||
10281                 gameMode == IcsPlayingWhite ||
10282                 gameMode == IcsPlayingBlack ||
10283                 gameMode == BeginningOfGame) {
10284                 char buf[MSG_SIZ];
10285                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10286                         resultDetails);
10287                 if (first.pr != NoProc) {
10288                     SendToProgram(buf, &first);
10289                 }
10290                 if (second.pr != NoProc &&
10291                     gameMode == TwoMachinesPlay) {
10292                     SendToProgram(buf, &second);
10293                 }
10294             }
10295         }
10296
10297         if (appData.icsActive) {
10298             if (appData.quietPlay &&
10299                 (gameMode == IcsPlayingWhite ||
10300                  gameMode == IcsPlayingBlack)) {
10301                 SendToICS(ics_prefix);
10302                 SendToICS("set shout 1\n");
10303             }
10304             nextGameMode = IcsIdle;
10305             ics_user_moved = FALSE;
10306             /* clean up premove.  It's ugly when the game has ended and the
10307              * premove highlights are still on the board.
10308              */
10309             if (gotPremove) {
10310               gotPremove = FALSE;
10311               ClearPremoveHighlights();
10312               DrawPosition(FALSE, boards[currentMove]);
10313             }
10314             if (whosays == GE_ICS) {
10315                 switch (result) {
10316                 case WhiteWins:
10317                     if (gameMode == IcsPlayingWhite)
10318                         PlayIcsWinSound();
10319                     else if(gameMode == IcsPlayingBlack)
10320                         PlayIcsLossSound();
10321                     break;
10322                 case BlackWins:
10323                     if (gameMode == IcsPlayingBlack)
10324                         PlayIcsWinSound();
10325                     else if(gameMode == IcsPlayingWhite)
10326                         PlayIcsLossSound();
10327                     break;
10328                 case GameIsDrawn:
10329                     PlayIcsDrawSound();
10330                     break;
10331                 default:
10332                     PlayIcsUnfinishedSound();
10333                 }
10334             }
10335         } else if (gameMode == EditGame ||
10336                    gameMode == PlayFromGameFile ||
10337                    gameMode == AnalyzeMode ||
10338                    gameMode == AnalyzeFile) {
10339             nextGameMode = gameMode;
10340         } else {
10341             nextGameMode = EndOfGame;
10342         }
10343         pausing = FALSE;
10344         ModeHighlight();
10345     } else {
10346         nextGameMode = gameMode;
10347     }
10348
10349     if (appData.noChessProgram) {
10350         gameMode = nextGameMode;
10351         ModeHighlight();
10352         endingGame = 0; /* [HGM] crash */
10353         return;
10354     }
10355
10356     if (first.reuse) {
10357         /* Put first chess program into idle state */
10358         if (first.pr != NoProc &&
10359             (gameMode == MachinePlaysWhite ||
10360              gameMode == MachinePlaysBlack ||
10361              gameMode == TwoMachinesPlay ||
10362              gameMode == IcsPlayingWhite ||
10363              gameMode == IcsPlayingBlack ||
10364              gameMode == BeginningOfGame)) {
10365             SendToProgram("force\n", &first);
10366             if (first.usePing) {
10367               char buf[MSG_SIZ];
10368               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10369               SendToProgram(buf, &first);
10370             }
10371         }
10372     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10373         /* Kill off first chess program */
10374         if (first.isr != NULL)
10375           RemoveInputSource(first.isr);
10376         first.isr = NULL;
10377
10378         if (first.pr != NoProc) {
10379             ExitAnalyzeMode();
10380             DoSleep( appData.delayBeforeQuit );
10381             SendToProgram("quit\n", &first);
10382             DoSleep( appData.delayAfterQuit );
10383             DestroyChildProcess(first.pr, first.useSigterm);
10384         }
10385         first.pr = NoProc;
10386     }
10387     if (second.reuse) {
10388         /* Put second chess program into idle state */
10389         if (second.pr != NoProc &&
10390             gameMode == TwoMachinesPlay) {
10391             SendToProgram("force\n", &second);
10392             if (second.usePing) {
10393               char buf[MSG_SIZ];
10394               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10395               SendToProgram(buf, &second);
10396             }
10397         }
10398     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10399         /* Kill off second chess program */
10400         if (second.isr != NULL)
10401           RemoveInputSource(second.isr);
10402         second.isr = NULL;
10403
10404         if (second.pr != NoProc) {
10405             DoSleep( appData.delayBeforeQuit );
10406             SendToProgram("quit\n", &second);
10407             DoSleep( appData.delayAfterQuit );
10408             DestroyChildProcess(second.pr, second.useSigterm);
10409         }
10410         second.pr = NoProc;
10411     }
10412
10413     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10414         char resChar = '=';
10415         switch (result) {
10416         case WhiteWins:
10417           resChar = '+';
10418           if (first.twoMachinesColor[0] == 'w') {
10419             first.matchWins++;
10420           } else {
10421             second.matchWins++;
10422           }
10423           break;
10424         case BlackWins:
10425           resChar = '-';
10426           if (first.twoMachinesColor[0] == 'b') {
10427             first.matchWins++;
10428           } else {
10429             second.matchWins++;
10430           }
10431           break;
10432         case GameUnfinished:
10433           resChar = ' ';
10434         default:
10435           break;
10436         }
10437
10438         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10439         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10440             ReserveGame(nextGame, resChar); // sets nextGame
10441             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10442             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10443         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10444
10445         if (nextGame <= appData.matchGames && !abortMatch) {
10446             gameMode = nextGameMode;
10447             matchGame = nextGame; // this will be overruled in tourney mode!
10448             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10449             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10450             endingGame = 0; /* [HGM] crash */
10451             return;
10452         } else {
10453             gameMode = nextGameMode;
10454             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10455                      first.tidy, second.tidy,
10456                      first.matchWins, second.matchWins,
10457                      appData.matchGames - (first.matchWins + second.matchWins));
10458             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10459             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10460             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10461                 first.twoMachinesColor = "black\n";
10462                 second.twoMachinesColor = "white\n";
10463             } else {
10464                 first.twoMachinesColor = "white\n";
10465                 second.twoMachinesColor = "black\n";
10466             }
10467         }
10468     }
10469     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10470         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10471       ExitAnalyzeMode();
10472     gameMode = nextGameMode;
10473     ModeHighlight();
10474     endingGame = 0;  /* [HGM] crash */
10475     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10476         if(matchMode == TRUE) { // match through command line: exit with or without popup
10477             if(ranking) {
10478                 ToNrEvent(forwardMostMove);
10479                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10480                 else ExitEvent(0);
10481             } else DisplayFatalError(buf, 0, 0);
10482         } else { // match through menu; just stop, with or without popup
10483             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10484             ModeHighlight();
10485             if(ranking){
10486                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10487             } else DisplayNote(buf);
10488       }
10489       if(ranking) free(ranking);
10490     }
10491 }
10492
10493 /* Assumes program was just initialized (initString sent).
10494    Leaves program in force mode. */
10495 void
10496 FeedMovesToProgram(cps, upto)
10497      ChessProgramState *cps;
10498      int upto;
10499 {
10500     int i;
10501
10502     if (appData.debugMode)
10503       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10504               startedFromSetupPosition ? "position and " : "",
10505               backwardMostMove, upto, cps->which);
10506     if(currentlyInitializedVariant != gameInfo.variant) {
10507       char buf[MSG_SIZ];
10508         // [HGM] variantswitch: make engine aware of new variant
10509         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10510                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10511         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10512         SendToProgram(buf, cps);
10513         currentlyInitializedVariant = gameInfo.variant;
10514     }
10515     SendToProgram("force\n", cps);
10516     if (startedFromSetupPosition) {
10517         SendBoard(cps, backwardMostMove);
10518     if (appData.debugMode) {
10519         fprintf(debugFP, "feedMoves\n");
10520     }
10521     }
10522     for (i = backwardMostMove; i < upto; i++) {
10523         SendMoveToProgram(i, cps);
10524     }
10525 }
10526
10527
10528 int
10529 ResurrectChessProgram()
10530 {
10531      /* The chess program may have exited.
10532         If so, restart it and feed it all the moves made so far. */
10533     static int doInit = 0;
10534
10535     if (appData.noChessProgram) return 1;
10536
10537     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10538         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10539         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10540         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10541     } else {
10542         if (first.pr != NoProc) return 1;
10543         StartChessProgram(&first);
10544     }
10545     InitChessProgram(&first, FALSE);
10546     FeedMovesToProgram(&first, currentMove);
10547
10548     if (!first.sendTime) {
10549         /* can't tell gnuchess what its clock should read,
10550            so we bow to its notion. */
10551         ResetClocks();
10552         timeRemaining[0][currentMove] = whiteTimeRemaining;
10553         timeRemaining[1][currentMove] = blackTimeRemaining;
10554     }
10555
10556     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10557                 appData.icsEngineAnalyze) && first.analysisSupport) {
10558       SendToProgram("analyze\n", &first);
10559       first.analyzing = TRUE;
10560     }
10561     return 1;
10562 }
10563
10564 /*
10565  * Button procedures
10566  */
10567 void
10568 Reset(redraw, init)
10569      int redraw, init;
10570 {
10571     int i;
10572
10573     if (appData.debugMode) {
10574         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10575                 redraw, init, gameMode);
10576     }
10577     CleanupTail(); // [HGM] vari: delete any stored variations
10578     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10579     pausing = pauseExamInvalid = FALSE;
10580     startedFromSetupPosition = blackPlaysFirst = FALSE;
10581     firstMove = TRUE;
10582     whiteFlag = blackFlag = FALSE;
10583     userOfferedDraw = FALSE;
10584     hintRequested = bookRequested = FALSE;
10585     first.maybeThinking = FALSE;
10586     second.maybeThinking = FALSE;
10587     first.bookSuspend = FALSE; // [HGM] book
10588     second.bookSuspend = FALSE;
10589     thinkOutput[0] = NULLCHAR;
10590     lastHint[0] = NULLCHAR;
10591     ClearGameInfo(&gameInfo);
10592     gameInfo.variant = StringToVariant(appData.variant);
10593     ics_user_moved = ics_clock_paused = FALSE;
10594     ics_getting_history = H_FALSE;
10595     ics_gamenum = -1;
10596     white_holding[0] = black_holding[0] = NULLCHAR;
10597     ClearProgramStats();
10598     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10599
10600     ResetFrontEnd();
10601     ClearHighlights();
10602     flipView = appData.flipView;
10603     ClearPremoveHighlights();
10604     gotPremove = FALSE;
10605     alarmSounded = FALSE;
10606
10607     GameEnds(EndOfFile, NULL, GE_PLAYER);
10608     if(appData.serverMovesName != NULL) {
10609         /* [HGM] prepare to make moves file for broadcasting */
10610         clock_t t = clock();
10611         if(serverMoves != NULL) fclose(serverMoves);
10612         serverMoves = fopen(appData.serverMovesName, "r");
10613         if(serverMoves != NULL) {
10614             fclose(serverMoves);
10615             /* delay 15 sec before overwriting, so all clients can see end */
10616             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10617         }
10618         serverMoves = fopen(appData.serverMovesName, "w");
10619     }
10620
10621     ExitAnalyzeMode();
10622     gameMode = BeginningOfGame;
10623     ModeHighlight();
10624     if(appData.icsActive) gameInfo.variant = VariantNormal;
10625     currentMove = forwardMostMove = backwardMostMove = 0;
10626     InitPosition(redraw);
10627     for (i = 0; i < MAX_MOVES; i++) {
10628         if (commentList[i] != NULL) {
10629             free(commentList[i]);
10630             commentList[i] = NULL;
10631         }
10632     }
10633     ResetClocks();
10634     timeRemaining[0][0] = whiteTimeRemaining;
10635     timeRemaining[1][0] = blackTimeRemaining;
10636
10637     if (first.pr == NULL) {
10638         StartChessProgram(&first);
10639     }
10640     if (init) {
10641             InitChessProgram(&first, startedFromSetupPosition);
10642     }
10643     DisplayTitle("");
10644     DisplayMessage("", "");
10645     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10646     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10647 }
10648
10649 void
10650 AutoPlayGameLoop()
10651 {
10652     for (;;) {
10653         if (!AutoPlayOneMove())
10654           return;
10655         if (matchMode || appData.timeDelay == 0)
10656           continue;
10657         if (appData.timeDelay < 0)
10658           return;
10659         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10660         break;
10661     }
10662 }
10663
10664
10665 int
10666 AutoPlayOneMove()
10667 {
10668     int fromX, fromY, toX, toY;
10669
10670     if (appData.debugMode) {
10671       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10672     }
10673
10674     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10675       return FALSE;
10676
10677     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10678       pvInfoList[currentMove].depth = programStats.depth;
10679       pvInfoList[currentMove].score = programStats.score;
10680       pvInfoList[currentMove].time  = 0;
10681       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10682     }
10683
10684     if (currentMove >= forwardMostMove) {
10685       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10686 //      gameMode = EndOfGame;
10687 //      ModeHighlight();
10688
10689       /* [AS] Clear current move marker at the end of a game */
10690       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10691
10692       return FALSE;
10693     }
10694
10695     toX = moveList[currentMove][2] - AAA;
10696     toY = moveList[currentMove][3] - ONE;
10697
10698     if (moveList[currentMove][1] == '@') {
10699         if (appData.highlightLastMove) {
10700             SetHighlights(-1, -1, toX, toY);
10701         }
10702     } else {
10703         fromX = moveList[currentMove][0] - AAA;
10704         fromY = moveList[currentMove][1] - ONE;
10705
10706         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10707
10708         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10709
10710         if (appData.highlightLastMove) {
10711             SetHighlights(fromX, fromY, toX, toY);
10712         }
10713     }
10714     DisplayMove(currentMove);
10715     SendMoveToProgram(currentMove++, &first);
10716     DisplayBothClocks();
10717     DrawPosition(FALSE, boards[currentMove]);
10718     // [HGM] PV info: always display, routine tests if empty
10719     DisplayComment(currentMove - 1, commentList[currentMove]);
10720     return TRUE;
10721 }
10722
10723
10724 int
10725 LoadGameOneMove(readAhead)
10726      ChessMove readAhead;
10727 {
10728     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10729     char promoChar = NULLCHAR;
10730     ChessMove moveType;
10731     char move[MSG_SIZ];
10732     char *p, *q;
10733
10734     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10735         gameMode != AnalyzeMode && gameMode != Training) {
10736         gameFileFP = NULL;
10737         return FALSE;
10738     }
10739
10740     yyboardindex = forwardMostMove;
10741     if (readAhead != EndOfFile) {
10742       moveType = readAhead;
10743     } else {
10744       if (gameFileFP == NULL)
10745           return FALSE;
10746       moveType = (ChessMove) Myylex();
10747     }
10748
10749     done = FALSE;
10750     switch (moveType) {
10751       case Comment:
10752         if (appData.debugMode)
10753           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10754         p = yy_text;
10755
10756         /* append the comment but don't display it */
10757         AppendComment(currentMove, p, FALSE);
10758         return TRUE;
10759
10760       case WhiteCapturesEnPassant:
10761       case BlackCapturesEnPassant:
10762       case WhitePromotion:
10763       case BlackPromotion:
10764       case WhiteNonPromotion:
10765       case BlackNonPromotion:
10766       case NormalMove:
10767       case WhiteKingSideCastle:
10768       case WhiteQueenSideCastle:
10769       case BlackKingSideCastle:
10770       case BlackQueenSideCastle:
10771       case WhiteKingSideCastleWild:
10772       case WhiteQueenSideCastleWild:
10773       case BlackKingSideCastleWild:
10774       case BlackQueenSideCastleWild:
10775       /* PUSH Fabien */
10776       case WhiteHSideCastleFR:
10777       case WhiteASideCastleFR:
10778       case BlackHSideCastleFR:
10779       case BlackASideCastleFR:
10780       /* POP Fabien */
10781         if (appData.debugMode)
10782           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10783         fromX = currentMoveString[0] - AAA;
10784         fromY = currentMoveString[1] - ONE;
10785         toX = currentMoveString[2] - AAA;
10786         toY = currentMoveString[3] - ONE;
10787         promoChar = currentMoveString[4];
10788         break;
10789
10790       case WhiteDrop:
10791       case BlackDrop:
10792         if (appData.debugMode)
10793           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10794         fromX = moveType == WhiteDrop ?
10795           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10796         (int) CharToPiece(ToLower(currentMoveString[0]));
10797         fromY = DROP_RANK;
10798         toX = currentMoveString[2] - AAA;
10799         toY = currentMoveString[3] - ONE;
10800         break;
10801
10802       case WhiteWins:
10803       case BlackWins:
10804       case GameIsDrawn:
10805       case GameUnfinished:
10806         if (appData.debugMode)
10807           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10808         p = strchr(yy_text, '{');
10809         if (p == NULL) p = strchr(yy_text, '(');
10810         if (p == NULL) {
10811             p = yy_text;
10812             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10813         } else {
10814             q = strchr(p, *p == '{' ? '}' : ')');
10815             if (q != NULL) *q = NULLCHAR;
10816             p++;
10817         }
10818         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10819         GameEnds(moveType, p, GE_FILE);
10820         done = TRUE;
10821         if (cmailMsgLoaded) {
10822             ClearHighlights();
10823             flipView = WhiteOnMove(currentMove);
10824             if (moveType == GameUnfinished) flipView = !flipView;
10825             if (appData.debugMode)
10826               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10827         }
10828         break;
10829
10830       case EndOfFile:
10831         if (appData.debugMode)
10832           fprintf(debugFP, "Parser hit end of file\n");
10833         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10834           case MT_NONE:
10835           case MT_CHECK:
10836             break;
10837           case MT_CHECKMATE:
10838           case MT_STAINMATE:
10839             if (WhiteOnMove(currentMove)) {
10840                 GameEnds(BlackWins, "Black mates", GE_FILE);
10841             } else {
10842                 GameEnds(WhiteWins, "White mates", GE_FILE);
10843             }
10844             break;
10845           case MT_STALEMATE:
10846             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10847             break;
10848         }
10849         done = TRUE;
10850         break;
10851
10852       case MoveNumberOne:
10853         if (lastLoadGameStart == GNUChessGame) {
10854             /* GNUChessGames have numbers, but they aren't move numbers */
10855             if (appData.debugMode)
10856               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10857                       yy_text, (int) moveType);
10858             return LoadGameOneMove(EndOfFile); /* tail recursion */
10859         }
10860         /* else fall thru */
10861
10862       case XBoardGame:
10863       case GNUChessGame:
10864       case PGNTag:
10865         /* Reached start of next game in file */
10866         if (appData.debugMode)
10867           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10868         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10869           case MT_NONE:
10870           case MT_CHECK:
10871             break;
10872           case MT_CHECKMATE:
10873           case MT_STAINMATE:
10874             if (WhiteOnMove(currentMove)) {
10875                 GameEnds(BlackWins, "Black mates", GE_FILE);
10876             } else {
10877                 GameEnds(WhiteWins, "White mates", GE_FILE);
10878             }
10879             break;
10880           case MT_STALEMATE:
10881             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10882             break;
10883         }
10884         done = TRUE;
10885         break;
10886
10887       case PositionDiagram:     /* should not happen; ignore */
10888       case ElapsedTime:         /* ignore */
10889       case NAG:                 /* ignore */
10890         if (appData.debugMode)
10891           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10892                   yy_text, (int) moveType);
10893         return LoadGameOneMove(EndOfFile); /* tail recursion */
10894
10895       case IllegalMove:
10896         if (appData.testLegality) {
10897             if (appData.debugMode)
10898               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10899             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10900                     (forwardMostMove / 2) + 1,
10901                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10902             DisplayError(move, 0);
10903             done = TRUE;
10904         } else {
10905             if (appData.debugMode)
10906               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10907                       yy_text, currentMoveString);
10908             fromX = currentMoveString[0] - AAA;
10909             fromY = currentMoveString[1] - ONE;
10910             toX = currentMoveString[2] - AAA;
10911             toY = currentMoveString[3] - ONE;
10912             promoChar = currentMoveString[4];
10913         }
10914         break;
10915
10916       case AmbiguousMove:
10917         if (appData.debugMode)
10918           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10919         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10920                 (forwardMostMove / 2) + 1,
10921                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10922         DisplayError(move, 0);
10923         done = TRUE;
10924         break;
10925
10926       default:
10927       case ImpossibleMove:
10928         if (appData.debugMode)
10929           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10930         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10931                 (forwardMostMove / 2) + 1,
10932                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10933         DisplayError(move, 0);
10934         done = TRUE;
10935         break;
10936     }
10937
10938     if (done) {
10939         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10940             DrawPosition(FALSE, boards[currentMove]);
10941             DisplayBothClocks();
10942             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10943               DisplayComment(currentMove - 1, commentList[currentMove]);
10944         }
10945         (void) StopLoadGameTimer();
10946         gameFileFP = NULL;
10947         cmailOldMove = forwardMostMove;
10948         return FALSE;
10949     } else {
10950         /* currentMoveString is set as a side-effect of yylex */
10951
10952         thinkOutput[0] = NULLCHAR;
10953         MakeMove(fromX, fromY, toX, toY, promoChar);
10954         currentMove = forwardMostMove;
10955         return TRUE;
10956     }
10957 }
10958
10959 /* Load the nth game from the given file */
10960 int
10961 LoadGameFromFile(filename, n, title, useList)
10962      char *filename;
10963      int n;
10964      char *title;
10965      /*Boolean*/ int useList;
10966 {
10967     FILE *f;
10968     char buf[MSG_SIZ];
10969
10970     if (strcmp(filename, "-") == 0) {
10971         f = stdin;
10972         title = "stdin";
10973     } else {
10974         f = fopen(filename, "rb");
10975         if (f == NULL) {
10976           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10977             DisplayError(buf, errno);
10978             return FALSE;
10979         }
10980     }
10981     if (fseek(f, 0, 0) == -1) {
10982         /* f is not seekable; probably a pipe */
10983         useList = FALSE;
10984     }
10985     if (useList && n == 0) {
10986         int error = GameListBuild(f);
10987         if (error) {
10988             DisplayError(_("Cannot build game list"), error);
10989         } else if (!ListEmpty(&gameList) &&
10990                    ((ListGame *) gameList.tailPred)->number > 1) {
10991             GameListPopUp(f, title);
10992             return TRUE;
10993         }
10994         GameListDestroy();
10995         n = 1;
10996     }
10997     if (n == 0) n = 1;
10998     return LoadGame(f, n, title, FALSE);
10999 }
11000
11001
11002 void
11003 MakeRegisteredMove()
11004 {
11005     int fromX, fromY, toX, toY;
11006     char promoChar;
11007     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11008         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11009           case CMAIL_MOVE:
11010           case CMAIL_DRAW:
11011             if (appData.debugMode)
11012               fprintf(debugFP, "Restoring %s for game %d\n",
11013                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11014
11015             thinkOutput[0] = NULLCHAR;
11016             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11017             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11018             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11019             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11020             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11021             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11022             MakeMove(fromX, fromY, toX, toY, promoChar);
11023             ShowMove(fromX, fromY, toX, toY);
11024
11025             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11026               case MT_NONE:
11027               case MT_CHECK:
11028                 break;
11029
11030               case MT_CHECKMATE:
11031               case MT_STAINMATE:
11032                 if (WhiteOnMove(currentMove)) {
11033                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11034                 } else {
11035                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11036                 }
11037                 break;
11038
11039               case MT_STALEMATE:
11040                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11041                 break;
11042             }
11043
11044             break;
11045
11046           case CMAIL_RESIGN:
11047             if (WhiteOnMove(currentMove)) {
11048                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11049             } else {
11050                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11051             }
11052             break;
11053
11054           case CMAIL_ACCEPT:
11055             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11056             break;
11057
11058           default:
11059             break;
11060         }
11061     }
11062
11063     return;
11064 }
11065
11066 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11067 int
11068 CmailLoadGame(f, gameNumber, title, useList)
11069      FILE *f;
11070      int gameNumber;
11071      char *title;
11072      int useList;
11073 {
11074     int retVal;
11075
11076     if (gameNumber > nCmailGames) {
11077         DisplayError(_("No more games in this message"), 0);
11078         return FALSE;
11079     }
11080     if (f == lastLoadGameFP) {
11081         int offset = gameNumber - lastLoadGameNumber;
11082         if (offset == 0) {
11083             cmailMsg[0] = NULLCHAR;
11084             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11085                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11086                 nCmailMovesRegistered--;
11087             }
11088             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11089             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11090                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11091             }
11092         } else {
11093             if (! RegisterMove()) return FALSE;
11094         }
11095     }
11096
11097     retVal = LoadGame(f, gameNumber, title, useList);
11098
11099     /* Make move registered during previous look at this game, if any */
11100     MakeRegisteredMove();
11101
11102     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11103         commentList[currentMove]
11104           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11105         DisplayComment(currentMove - 1, commentList[currentMove]);
11106     }
11107
11108     return retVal;
11109 }
11110
11111 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11112 int
11113 ReloadGame(offset)
11114      int offset;
11115 {
11116     int gameNumber = lastLoadGameNumber + offset;
11117     if (lastLoadGameFP == NULL) {
11118         DisplayError(_("No game has been loaded yet"), 0);
11119         return FALSE;
11120     }
11121     if (gameNumber <= 0) {
11122         DisplayError(_("Can't back up any further"), 0);
11123         return FALSE;
11124     }
11125     if (cmailMsgLoaded) {
11126         return CmailLoadGame(lastLoadGameFP, gameNumber,
11127                              lastLoadGameTitle, lastLoadGameUseList);
11128     } else {
11129         return LoadGame(lastLoadGameFP, gameNumber,
11130                         lastLoadGameTitle, lastLoadGameUseList);
11131     }
11132 }
11133
11134 int keys[EmptySquare+1];
11135
11136 int
11137 PositionMatches(Board b1, Board b2)
11138 {
11139     int r, f, sum=0;
11140     switch(appData.searchMode) {
11141         case 1: return CompareWithRights(b1, b2);
11142         case 2:
11143             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11144                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11145             }
11146             return TRUE;
11147         case 3:
11148             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11149               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11150                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11151             }
11152             return sum==0;
11153         case 4:
11154             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11155                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11156             }
11157             return sum==0;
11158     }
11159     return TRUE;
11160 }
11161
11162 GameInfo dummyInfo;
11163
11164 int GameContainsPosition(FILE *f, ListGame *lg)
11165 {
11166     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11167     int fromX, fromY, toX, toY;
11168     char promoChar;
11169     static int initDone=FALSE;
11170
11171     if(!initDone) {
11172         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11173         initDone = TRUE;
11174     }
11175     dummyInfo.variant = VariantNormal;
11176     FREE(dummyInfo.fen); dummyInfo.fen = NULL;
11177     dummyInfo.whiteRating = 0;
11178     dummyInfo.blackRating = 0;
11179     FREE(dummyInfo.date); dummyInfo.date = NULL;
11180     fseek(f, lg->offset, 0);
11181     yynewfile(f);
11182     CopyBoard(boards[scratch], initialPosition); // default start position
11183     while(1) {
11184         yyboardindex = scratch + (plyNr&1);
11185       quickFlag = 1;
11186         next = Myylex();
11187       quickFlag = 0;
11188         switch(next) {
11189             case PGNTag:
11190                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11191 #if 0
11192                 ParsePGNTag(yy_text, &dummyInfo); // this has a bad memory leak...
11193                 if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL;
11194 #else
11195                 // do it ourselves avoiding malloc
11196                 { char *p = yy_text+1, *q;
11197                   while(!isdigit(*p) && !isalpha(*p)) p++;
11198                   q  = p; while(*p != ' ' && *p != '\t' && *p != '\n') p++;
11199                   *p = NULLCHAR;
11200                   if(!StrCaseCmp(q, "Date") && (p = strchr(p+1, '"'))) { if(atoi(p+1) < appData.dateThreshold) return -1; } else
11201                   if(!StrCaseCmp(q, "Variant")  &&  (p = strchr(p+1, '"'))) dummyInfo.variant = StringToVariant(p+1); else
11202                   if(!StrCaseCmp(q, "WhiteElo")  && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11203                   if(!StrCaseCmp(q, "BlackElo")  && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11204                   if(!StrCaseCmp(q, "WhiteUSCF") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11205                   if(!StrCaseCmp(q, "BlackUSCF") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11206                   if(!StrCaseCmp(q, "FEN")  && (p = strchr(p+1, '"'))) ParseFEN(boards[scratch], &btm, p+1);
11207                 }
11208 #endif
11209             default:
11210                 continue;
11211
11212             case XBoardGame:
11213             case GNUChessGame:
11214                 if(plyNr) return -1; // after we have seen moves, this is for new game
11215               continue;
11216
11217             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11218             case ImpossibleMove:
11219             case WhiteWins: // game ends here with these four
11220             case BlackWins:
11221             case GameIsDrawn:
11222             case GameUnfinished:
11223                 return -1;
11224
11225             case IllegalMove:
11226                 if(appData.testLegality) return -1;
11227             case WhiteCapturesEnPassant:
11228             case BlackCapturesEnPassant:
11229             case WhitePromotion:
11230             case BlackPromotion:
11231             case WhiteNonPromotion:
11232             case BlackNonPromotion:
11233             case NormalMove:
11234             case WhiteKingSideCastle:
11235             case WhiteQueenSideCastle:
11236             case BlackKingSideCastle:
11237             case BlackQueenSideCastle:
11238             case WhiteKingSideCastleWild:
11239             case WhiteQueenSideCastleWild:
11240             case BlackKingSideCastleWild:
11241             case BlackQueenSideCastleWild:
11242             case WhiteHSideCastleFR:
11243             case WhiteASideCastleFR:
11244             case BlackHSideCastleFR:
11245             case BlackASideCastleFR:
11246                 fromX = currentMoveString[0] - AAA;
11247                 fromY = currentMoveString[1] - ONE;
11248                 toX = currentMoveString[2] - AAA;
11249                 toY = currentMoveString[3] - ONE;
11250                 promoChar = currentMoveString[4];
11251                 break;
11252             case WhiteDrop:
11253             case BlackDrop:
11254                 fromX = next == WhiteDrop ?
11255                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11256                   (int) CharToPiece(ToLower(currentMoveString[0]));
11257                 fromY = DROP_RANK;
11258                 toX = currentMoveString[2] - AAA;
11259                 toY = currentMoveString[3] - ONE;
11260                 break;
11261         }
11262         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11263         if(plyNr == 0) { // but first figure out variant and initial position
11264             if(dummyInfo.variant != gameInfo.variant) return -1; // wrong variant
11265             if(appData.eloThreshold1 && (dummyInfo.whiteRating < appData.eloThreshold1 && dummyInfo.blackRating < appData.eloThreshold1)) return -1;
11266             if(appData.eloThreshold2 && (dummyInfo.whiteRating < appData.eloThreshold2 || dummyInfo.blackRating < appData.eloThreshold2)) return -1;
11267             if(appData.dateThreshold && (!dummyInfo.date || atoi(dummyInfo.date) < appData.dateThreshold)) return -1;
11268             if(btm) CopyBoard(boards[scratch+1], boards[scratch]), plyNr++;
11269             if(PositionMatches(boards[scratch + plyNr], boards[currentMove])) return plyNr;
11270         }
11271         CopyBoard(boards[scratch + (plyNr+1&1)], boards[scratch + (plyNr&1)]);
11272         plyNr++;
11273         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch + (plyNr&1)]);
11274         if(PositionMatches(boards[scratch + (plyNr&1)], boards[currentMove])) return plyNr;
11275     }
11276 }
11277
11278 /* Load the nth game from open file f */
11279 int
11280 LoadGame(f, gameNumber, title, useList)
11281      FILE *f;
11282      int gameNumber;
11283      char *title;
11284      int useList;
11285 {
11286     ChessMove cm;
11287     char buf[MSG_SIZ];
11288     int gn = gameNumber;
11289     ListGame *lg = NULL;
11290     int numPGNTags = 0;
11291     int err, pos = -1;
11292     GameMode oldGameMode;
11293     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11294
11295     if (appData.debugMode)
11296         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11297
11298     if (gameMode == Training )
11299         SetTrainingModeOff();
11300
11301     oldGameMode = gameMode;
11302     if (gameMode != BeginningOfGame) {
11303       Reset(FALSE, TRUE);
11304     }
11305
11306     gameFileFP = f;
11307     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11308         fclose(lastLoadGameFP);
11309     }
11310
11311     if (useList) {
11312         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11313
11314         if (lg) {
11315             fseek(f, lg->offset, 0);
11316             GameListHighlight(gameNumber);
11317             pos = lg->position;
11318             gn = 1;
11319         }
11320         else {
11321             DisplayError(_("Game number out of range"), 0);
11322             return FALSE;
11323         }
11324     } else {
11325         GameListDestroy();
11326         if (fseek(f, 0, 0) == -1) {
11327             if (f == lastLoadGameFP ?
11328                 gameNumber == lastLoadGameNumber + 1 :
11329                 gameNumber == 1) {
11330                 gn = 1;
11331             } else {
11332                 DisplayError(_("Can't seek on game file"), 0);
11333                 return FALSE;
11334             }
11335         }
11336     }
11337     lastLoadGameFP = f;
11338     lastLoadGameNumber = gameNumber;
11339     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11340     lastLoadGameUseList = useList;
11341
11342     yynewfile(f);
11343
11344     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11345       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11346                 lg->gameInfo.black);
11347             DisplayTitle(buf);
11348     } else if (*title != NULLCHAR) {
11349         if (gameNumber > 1) {
11350           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11351             DisplayTitle(buf);
11352         } else {
11353             DisplayTitle(title);
11354         }
11355     }
11356
11357     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11358         gameMode = PlayFromGameFile;
11359         ModeHighlight();
11360     }
11361
11362     currentMove = forwardMostMove = backwardMostMove = 0;
11363     CopyBoard(boards[0], initialPosition);
11364     StopClocks();
11365
11366     /*
11367      * Skip the first gn-1 games in the file.
11368      * Also skip over anything that precedes an identifiable
11369      * start of game marker, to avoid being confused by
11370      * garbage at the start of the file.  Currently
11371      * recognized start of game markers are the move number "1",
11372      * the pattern "gnuchess .* game", the pattern
11373      * "^[#;%] [^ ]* game file", and a PGN tag block.
11374      * A game that starts with one of the latter two patterns
11375      * will also have a move number 1, possibly
11376      * following a position diagram.
11377      * 5-4-02: Let's try being more lenient and allowing a game to
11378      * start with an unnumbered move.  Does that break anything?
11379      */
11380     cm = lastLoadGameStart = EndOfFile;
11381     while (gn > 0) {
11382         yyboardindex = forwardMostMove;
11383         cm = (ChessMove) Myylex();
11384         switch (cm) {
11385           case EndOfFile:
11386             if (cmailMsgLoaded) {
11387                 nCmailGames = CMAIL_MAX_GAMES - gn;
11388             } else {
11389                 Reset(TRUE, TRUE);
11390                 DisplayError(_("Game not found in file"), 0);
11391             }
11392             return FALSE;
11393
11394           case GNUChessGame:
11395           case XBoardGame:
11396             gn--;
11397             lastLoadGameStart = cm;
11398             break;
11399
11400           case MoveNumberOne:
11401             switch (lastLoadGameStart) {
11402               case GNUChessGame:
11403               case XBoardGame:
11404               case PGNTag:
11405                 break;
11406               case MoveNumberOne:
11407               case EndOfFile:
11408                 gn--;           /* count this game */
11409                 lastLoadGameStart = cm;
11410                 break;
11411               default:
11412                 /* impossible */
11413                 break;
11414             }
11415             break;
11416
11417           case PGNTag:
11418             switch (lastLoadGameStart) {
11419               case GNUChessGame:
11420               case PGNTag:
11421               case MoveNumberOne:
11422               case EndOfFile:
11423                 gn--;           /* count this game */
11424                 lastLoadGameStart = cm;
11425                 break;
11426               case XBoardGame:
11427                 lastLoadGameStart = cm; /* game counted already */
11428                 break;
11429               default:
11430                 /* impossible */
11431                 break;
11432             }
11433             if (gn > 0) {
11434                 do {
11435                     yyboardindex = forwardMostMove;
11436                     cm = (ChessMove) Myylex();
11437                 } while (cm == PGNTag || cm == Comment);
11438             }
11439             break;
11440
11441           case WhiteWins:
11442           case BlackWins:
11443           case GameIsDrawn:
11444             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11445                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11446                     != CMAIL_OLD_RESULT) {
11447                     nCmailResults ++ ;
11448                     cmailResult[  CMAIL_MAX_GAMES
11449                                 - gn - 1] = CMAIL_OLD_RESULT;
11450                 }
11451             }
11452             break;
11453
11454           case NormalMove:
11455             /* Only a NormalMove can be at the start of a game
11456              * without a position diagram. */
11457             if (lastLoadGameStart == EndOfFile ) {
11458               gn--;
11459               lastLoadGameStart = MoveNumberOne;
11460             }
11461             break;
11462
11463           default:
11464             break;
11465         }
11466     }
11467
11468     if (appData.debugMode)
11469       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11470
11471     if (cm == XBoardGame) {
11472         /* Skip any header junk before position diagram and/or move 1 */
11473         for (;;) {
11474             yyboardindex = forwardMostMove;
11475             cm = (ChessMove) Myylex();
11476
11477             if (cm == EndOfFile ||
11478                 cm == GNUChessGame || cm == XBoardGame) {
11479                 /* Empty game; pretend end-of-file and handle later */
11480                 cm = EndOfFile;
11481                 break;
11482             }
11483
11484             if (cm == MoveNumberOne || cm == PositionDiagram ||
11485                 cm == PGNTag || cm == Comment)
11486               break;
11487         }
11488     } else if (cm == GNUChessGame) {
11489         if (gameInfo.event != NULL) {
11490             free(gameInfo.event);
11491         }
11492         gameInfo.event = StrSave(yy_text);
11493     }
11494
11495     startedFromSetupPosition = FALSE;
11496     while (cm == PGNTag) {
11497         if (appData.debugMode)
11498           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11499         err = ParsePGNTag(yy_text, &gameInfo);
11500         if (!err) numPGNTags++;
11501
11502         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11503         if(gameInfo.variant != oldVariant) {
11504             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11505             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11506             InitPosition(TRUE);
11507             oldVariant = gameInfo.variant;
11508             if (appData.debugMode)
11509               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11510         }
11511
11512
11513         if (gameInfo.fen != NULL) {
11514           Board initial_position;
11515           startedFromSetupPosition = TRUE;
11516           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11517             Reset(TRUE, TRUE);
11518             DisplayError(_("Bad FEN position in file"), 0);
11519             return FALSE;
11520           }
11521           CopyBoard(boards[0], initial_position);
11522           if (blackPlaysFirst) {
11523             currentMove = forwardMostMove = backwardMostMove = 1;
11524             CopyBoard(boards[1], initial_position);
11525             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11526             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11527             timeRemaining[0][1] = whiteTimeRemaining;
11528             timeRemaining[1][1] = blackTimeRemaining;
11529             if (commentList[0] != NULL) {
11530               commentList[1] = commentList[0];
11531               commentList[0] = NULL;
11532             }
11533           } else {
11534             currentMove = forwardMostMove = backwardMostMove = 0;
11535           }
11536           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11537           {   int i;
11538               initialRulePlies = FENrulePlies;
11539               for( i=0; i< nrCastlingRights; i++ )
11540                   initialRights[i] = initial_position[CASTLING][i];
11541           }
11542           yyboardindex = forwardMostMove;
11543           free(gameInfo.fen);
11544           gameInfo.fen = NULL;
11545         }
11546
11547         yyboardindex = forwardMostMove;
11548         cm = (ChessMove) Myylex();
11549
11550         /* Handle comments interspersed among the tags */
11551         while (cm == Comment) {
11552             char *p;
11553             if (appData.debugMode)
11554               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11555             p = yy_text;
11556             AppendComment(currentMove, p, FALSE);
11557             yyboardindex = forwardMostMove;
11558             cm = (ChessMove) Myylex();
11559         }
11560     }
11561
11562     /* don't rely on existence of Event tag since if game was
11563      * pasted from clipboard the Event tag may not exist
11564      */
11565     if (numPGNTags > 0){
11566         char *tags;
11567         if (gameInfo.variant == VariantNormal) {
11568           VariantClass v = StringToVariant(gameInfo.event);
11569           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11570           if(v < VariantShogi) gameInfo.variant = v;
11571         }
11572         if (!matchMode) {
11573           if( appData.autoDisplayTags ) {
11574             tags = PGNTags(&gameInfo);
11575             TagsPopUp(tags, CmailMsg());
11576             free(tags);
11577           }
11578         }
11579     } else {
11580         /* Make something up, but don't display it now */
11581         SetGameInfo();
11582         TagsPopDown();
11583     }
11584
11585     if (cm == PositionDiagram) {
11586         int i, j;
11587         char *p;
11588         Board initial_position;
11589
11590         if (appData.debugMode)
11591           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11592
11593         if (!startedFromSetupPosition) {
11594             p = yy_text;
11595             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11596               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11597                 switch (*p) {
11598                   case '{':
11599                   case '[':
11600                   case '-':
11601                   case ' ':
11602                   case '\t':
11603                   case '\n':
11604                   case '\r':
11605                     break;
11606                   default:
11607                     initial_position[i][j++] = CharToPiece(*p);
11608                     break;
11609                 }
11610             while (*p == ' ' || *p == '\t' ||
11611                    *p == '\n' || *p == '\r') p++;
11612
11613             if (strncmp(p, "black", strlen("black"))==0)
11614               blackPlaysFirst = TRUE;
11615             else
11616               blackPlaysFirst = FALSE;
11617             startedFromSetupPosition = TRUE;
11618
11619             CopyBoard(boards[0], initial_position);
11620             if (blackPlaysFirst) {
11621                 currentMove = forwardMostMove = backwardMostMove = 1;
11622                 CopyBoard(boards[1], initial_position);
11623                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11624                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11625                 timeRemaining[0][1] = whiteTimeRemaining;
11626                 timeRemaining[1][1] = blackTimeRemaining;
11627                 if (commentList[0] != NULL) {
11628                     commentList[1] = commentList[0];
11629                     commentList[0] = NULL;
11630                 }
11631             } else {
11632                 currentMove = forwardMostMove = backwardMostMove = 0;
11633             }
11634         }
11635         yyboardindex = forwardMostMove;
11636         cm = (ChessMove) Myylex();
11637     }
11638
11639     if (first.pr == NoProc) {
11640         StartChessProgram(&first);
11641     }
11642     InitChessProgram(&first, FALSE);
11643     SendToProgram("force\n", &first);
11644     if (startedFromSetupPosition) {
11645         SendBoard(&first, forwardMostMove);
11646     if (appData.debugMode) {
11647         fprintf(debugFP, "Load Game\n");
11648     }
11649         DisplayBothClocks();
11650     }
11651
11652     /* [HGM] server: flag to write setup moves in broadcast file as one */
11653     loadFlag = appData.suppressLoadMoves;
11654
11655     while (cm == Comment) {
11656         char *p;
11657         if (appData.debugMode)
11658           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11659         p = yy_text;
11660         AppendComment(currentMove, p, FALSE);
11661         yyboardindex = forwardMostMove;
11662         cm = (ChessMove) Myylex();
11663     }
11664
11665     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11666         cm == WhiteWins || cm == BlackWins ||
11667         cm == GameIsDrawn || cm == GameUnfinished) {
11668         DisplayMessage("", _("No moves in game"));
11669         if (cmailMsgLoaded) {
11670             if (appData.debugMode)
11671               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11672             ClearHighlights();
11673             flipView = FALSE;
11674         }
11675         DrawPosition(FALSE, boards[currentMove]);
11676         DisplayBothClocks();
11677         gameMode = EditGame;
11678         ModeHighlight();
11679         gameFileFP = NULL;
11680         cmailOldMove = 0;
11681         return TRUE;
11682     }
11683
11684     // [HGM] PV info: routine tests if comment empty
11685     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11686         DisplayComment(currentMove - 1, commentList[currentMove]);
11687     }
11688     if (!matchMode && appData.timeDelay != 0)
11689       DrawPosition(FALSE, boards[currentMove]);
11690
11691     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11692       programStats.ok_to_send = 1;
11693     }
11694
11695     /* if the first token after the PGN tags is a move
11696      * and not move number 1, retrieve it from the parser
11697      */
11698     if (cm != MoveNumberOne)
11699         LoadGameOneMove(cm);
11700
11701     /* load the remaining moves from the file */
11702     while (LoadGameOneMove(EndOfFile)) {
11703       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11704       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11705     }
11706
11707     /* rewind to the start of the game */
11708     currentMove = backwardMostMove;
11709
11710     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11711
11712     if (oldGameMode == AnalyzeFile ||
11713         oldGameMode == AnalyzeMode) {
11714       AnalyzeFileEvent();
11715     }
11716
11717     if (!matchMode && pos >= 0) {
11718         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11719     } else
11720     if (matchMode || appData.timeDelay == 0) {
11721       ToEndEvent();
11722     } else if (appData.timeDelay > 0) {
11723       AutoPlayGameLoop();
11724     }
11725
11726     if (appData.debugMode)
11727         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11728
11729     loadFlag = 0; /* [HGM] true game starts */
11730     return TRUE;
11731 }
11732
11733 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11734 int
11735 ReloadPosition(offset)
11736      int offset;
11737 {
11738     int positionNumber = lastLoadPositionNumber + offset;
11739     if (lastLoadPositionFP == NULL) {
11740         DisplayError(_("No position has been loaded yet"), 0);
11741         return FALSE;
11742     }
11743     if (positionNumber <= 0) {
11744         DisplayError(_("Can't back up any further"), 0);
11745         return FALSE;
11746     }
11747     return LoadPosition(lastLoadPositionFP, positionNumber,
11748                         lastLoadPositionTitle);
11749 }
11750
11751 /* Load the nth position from the given file */
11752 int
11753 LoadPositionFromFile(filename, n, title)
11754      char *filename;
11755      int n;
11756      char *title;
11757 {
11758     FILE *f;
11759     char buf[MSG_SIZ];
11760
11761     if (strcmp(filename, "-") == 0) {
11762         return LoadPosition(stdin, n, "stdin");
11763     } else {
11764         f = fopen(filename, "rb");
11765         if (f == NULL) {
11766             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11767             DisplayError(buf, errno);
11768             return FALSE;
11769         } else {
11770             return LoadPosition(f, n, title);
11771         }
11772     }
11773 }
11774
11775 /* Load the nth position from the given open file, and close it */
11776 int
11777 LoadPosition(f, positionNumber, title)
11778      FILE *f;
11779      int positionNumber;
11780      char *title;
11781 {
11782     char *p, line[MSG_SIZ];
11783     Board initial_position;
11784     int i, j, fenMode, pn;
11785
11786     if (gameMode == Training )
11787         SetTrainingModeOff();
11788
11789     if (gameMode != BeginningOfGame) {
11790         Reset(FALSE, TRUE);
11791     }
11792     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11793         fclose(lastLoadPositionFP);
11794     }
11795     if (positionNumber == 0) positionNumber = 1;
11796     lastLoadPositionFP = f;
11797     lastLoadPositionNumber = positionNumber;
11798     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11799     if (first.pr == NoProc) {
11800       StartChessProgram(&first);
11801       InitChessProgram(&first, FALSE);
11802     }
11803     pn = positionNumber;
11804     if (positionNumber < 0) {
11805         /* Negative position number means to seek to that byte offset */
11806         if (fseek(f, -positionNumber, 0) == -1) {
11807             DisplayError(_("Can't seek on position file"), 0);
11808             return FALSE;
11809         };
11810         pn = 1;
11811     } else {
11812         if (fseek(f, 0, 0) == -1) {
11813             if (f == lastLoadPositionFP ?
11814                 positionNumber == lastLoadPositionNumber + 1 :
11815                 positionNumber == 1) {
11816                 pn = 1;
11817             } else {
11818                 DisplayError(_("Can't seek on position file"), 0);
11819                 return FALSE;
11820             }
11821         }
11822     }
11823     /* See if this file is FEN or old-style xboard */
11824     if (fgets(line, MSG_SIZ, f) == NULL) {
11825         DisplayError(_("Position not found in file"), 0);
11826         return FALSE;
11827     }
11828     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11829     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11830
11831     if (pn >= 2) {
11832         if (fenMode || line[0] == '#') pn--;
11833         while (pn > 0) {
11834             /* skip positions before number pn */
11835             if (fgets(line, MSG_SIZ, f) == NULL) {
11836                 Reset(TRUE, TRUE);
11837                 DisplayError(_("Position not found in file"), 0);
11838                 return FALSE;
11839             }
11840             if (fenMode || line[0] == '#') pn--;
11841         }
11842     }
11843
11844     if (fenMode) {
11845         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11846             DisplayError(_("Bad FEN position in file"), 0);
11847             return FALSE;
11848         }
11849     } else {
11850         (void) fgets(line, MSG_SIZ, f);
11851         (void) fgets(line, MSG_SIZ, f);
11852
11853         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11854             (void) fgets(line, MSG_SIZ, f);
11855             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11856                 if (*p == ' ')
11857                   continue;
11858                 initial_position[i][j++] = CharToPiece(*p);
11859             }
11860         }
11861
11862         blackPlaysFirst = FALSE;
11863         if (!feof(f)) {
11864             (void) fgets(line, MSG_SIZ, f);
11865             if (strncmp(line, "black", strlen("black"))==0)
11866               blackPlaysFirst = TRUE;
11867         }
11868     }
11869     startedFromSetupPosition = TRUE;
11870
11871     SendToProgram("force\n", &first);
11872     CopyBoard(boards[0], initial_position);
11873     if (blackPlaysFirst) {
11874         currentMove = forwardMostMove = backwardMostMove = 1;
11875         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11876         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11877         CopyBoard(boards[1], initial_position);
11878         DisplayMessage("", _("Black to play"));
11879     } else {
11880         currentMove = forwardMostMove = backwardMostMove = 0;
11881         DisplayMessage("", _("White to play"));
11882     }
11883     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11884     SendBoard(&first, forwardMostMove);
11885     if (appData.debugMode) {
11886 int i, j;
11887   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11888   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11889         fprintf(debugFP, "Load Position\n");
11890     }
11891
11892     if (positionNumber > 1) {
11893       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11894         DisplayTitle(line);
11895     } else {
11896         DisplayTitle(title);
11897     }
11898     gameMode = EditGame;
11899     ModeHighlight();
11900     ResetClocks();
11901     timeRemaining[0][1] = whiteTimeRemaining;
11902     timeRemaining[1][1] = blackTimeRemaining;
11903     DrawPosition(FALSE, boards[currentMove]);
11904
11905     return TRUE;
11906 }
11907
11908
11909 void
11910 CopyPlayerNameIntoFileName(dest, src)
11911      char **dest, *src;
11912 {
11913     while (*src != NULLCHAR && *src != ',') {
11914         if (*src == ' ') {
11915             *(*dest)++ = '_';
11916             src++;
11917         } else {
11918             *(*dest)++ = *src++;
11919         }
11920     }
11921 }
11922
11923 char *DefaultFileName(ext)
11924      char *ext;
11925 {
11926     static char def[MSG_SIZ];
11927     char *p;
11928
11929     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11930         p = def;
11931         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11932         *p++ = '-';
11933         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11934         *p++ = '.';
11935         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11936     } else {
11937         def[0] = NULLCHAR;
11938     }
11939     return def;
11940 }
11941
11942 /* Save the current game to the given file */
11943 int
11944 SaveGameToFile(filename, append)
11945      char *filename;
11946      int append;
11947 {
11948     FILE *f;
11949     char buf[MSG_SIZ];
11950     int result;
11951
11952     if (strcmp(filename, "-") == 0) {
11953         return SaveGame(stdout, 0, NULL);
11954     } else {
11955         f = fopen(filename, append ? "a" : "w");
11956         if (f == NULL) {
11957             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11958             DisplayError(buf, errno);
11959             return FALSE;
11960         } else {
11961             safeStrCpy(buf, lastMsg, MSG_SIZ);
11962             DisplayMessage(_("Waiting for access to save file"), "");
11963             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11964             DisplayMessage(_("Saving game"), "");
11965             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11966             result = SaveGame(f, 0, NULL);
11967             DisplayMessage(buf, "");
11968             return result;
11969         }
11970     }
11971 }
11972
11973 char *
11974 SavePart(str)
11975      char *str;
11976 {
11977     static char buf[MSG_SIZ];
11978     char *p;
11979
11980     p = strchr(str, ' ');
11981     if (p == NULL) return str;
11982     strncpy(buf, str, p - str);
11983     buf[p - str] = NULLCHAR;
11984     return buf;
11985 }
11986
11987 #define PGN_MAX_LINE 75
11988
11989 #define PGN_SIDE_WHITE  0
11990 #define PGN_SIDE_BLACK  1
11991
11992 /* [AS] */
11993 static int FindFirstMoveOutOfBook( int side )
11994 {
11995     int result = -1;
11996
11997     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11998         int index = backwardMostMove;
11999         int has_book_hit = 0;
12000
12001         if( (index % 2) != side ) {
12002             index++;
12003         }
12004
12005         while( index < forwardMostMove ) {
12006             /* Check to see if engine is in book */
12007             int depth = pvInfoList[index].depth;
12008             int score = pvInfoList[index].score;
12009             int in_book = 0;
12010
12011             if( depth <= 2 ) {
12012                 in_book = 1;
12013             }
12014             else if( score == 0 && depth == 63 ) {
12015                 in_book = 1; /* Zappa */
12016             }
12017             else if( score == 2 && depth == 99 ) {
12018                 in_book = 1; /* Abrok */
12019             }
12020
12021             has_book_hit += in_book;
12022
12023             if( ! in_book ) {
12024                 result = index;
12025
12026                 break;
12027             }
12028
12029             index += 2;
12030         }
12031     }
12032
12033     return result;
12034 }
12035
12036 /* [AS] */
12037 void GetOutOfBookInfo( char * buf )
12038 {
12039     int oob[2];
12040     int i;
12041     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12042
12043     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12044     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12045
12046     *buf = '\0';
12047
12048     if( oob[0] >= 0 || oob[1] >= 0 ) {
12049         for( i=0; i<2; i++ ) {
12050             int idx = oob[i];
12051
12052             if( idx >= 0 ) {
12053                 if( i > 0 && oob[0] >= 0 ) {
12054                     strcat( buf, "   " );
12055                 }
12056
12057                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12058                 sprintf( buf+strlen(buf), "%s%.2f",
12059                     pvInfoList[idx].score >= 0 ? "+" : "",
12060                     pvInfoList[idx].score / 100.0 );
12061             }
12062         }
12063     }
12064 }
12065
12066 /* Save game in PGN style and close the file */
12067 int
12068 SaveGamePGN(f)
12069      FILE *f;
12070 {
12071     int i, offset, linelen, newblock;
12072     time_t tm;
12073 //    char *movetext;
12074     char numtext[32];
12075     int movelen, numlen, blank;
12076     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12077
12078     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12079
12080     tm = time((time_t *) NULL);
12081
12082     PrintPGNTags(f, &gameInfo);
12083
12084     if (backwardMostMove > 0 || startedFromSetupPosition) {
12085         char *fen = PositionToFEN(backwardMostMove, NULL);
12086         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12087         fprintf(f, "\n{--------------\n");
12088         PrintPosition(f, backwardMostMove);
12089         fprintf(f, "--------------}\n");
12090         free(fen);
12091     }
12092     else {
12093         /* [AS] Out of book annotation */
12094         if( appData.saveOutOfBookInfo ) {
12095             char buf[64];
12096
12097             GetOutOfBookInfo( buf );
12098
12099             if( buf[0] != '\0' ) {
12100                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12101             }
12102         }
12103
12104         fprintf(f, "\n");
12105     }
12106
12107     i = backwardMostMove;
12108     linelen = 0;
12109     newblock = TRUE;
12110
12111     while (i < forwardMostMove) {
12112         /* Print comments preceding this move */
12113         if (commentList[i] != NULL) {
12114             if (linelen > 0) fprintf(f, "\n");
12115             fprintf(f, "%s", commentList[i]);
12116             linelen = 0;
12117             newblock = TRUE;
12118         }
12119
12120         /* Format move number */
12121         if ((i % 2) == 0)
12122           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12123         else
12124           if (newblock)
12125             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12126           else
12127             numtext[0] = NULLCHAR;
12128
12129         numlen = strlen(numtext);
12130         newblock = FALSE;
12131
12132         /* Print move number */
12133         blank = linelen > 0 && numlen > 0;
12134         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12135             fprintf(f, "\n");
12136             linelen = 0;
12137             blank = 0;
12138         }
12139         if (blank) {
12140             fprintf(f, " ");
12141             linelen++;
12142         }
12143         fprintf(f, "%s", numtext);
12144         linelen += numlen;
12145
12146         /* Get move */
12147         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12148         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12149
12150         /* Print move */
12151         blank = linelen > 0 && movelen > 0;
12152         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12153             fprintf(f, "\n");
12154             linelen = 0;
12155             blank = 0;
12156         }
12157         if (blank) {
12158             fprintf(f, " ");
12159             linelen++;
12160         }
12161         fprintf(f, "%s", move_buffer);
12162         linelen += movelen;
12163
12164         /* [AS] Add PV info if present */
12165         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12166             /* [HGM] add time */
12167             char buf[MSG_SIZ]; int seconds;
12168
12169             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12170
12171             if( seconds <= 0)
12172               buf[0] = 0;
12173             else
12174               if( seconds < 30 )
12175                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12176               else
12177                 {
12178                   seconds = (seconds + 4)/10; // round to full seconds
12179                   if( seconds < 60 )
12180                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12181                   else
12182                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12183                 }
12184
12185             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12186                       pvInfoList[i].score >= 0 ? "+" : "",
12187                       pvInfoList[i].score / 100.0,
12188                       pvInfoList[i].depth,
12189                       buf );
12190
12191             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12192
12193             /* Print score/depth */
12194             blank = linelen > 0 && movelen > 0;
12195             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12196                 fprintf(f, "\n");
12197                 linelen = 0;
12198                 blank = 0;
12199             }
12200             if (blank) {
12201                 fprintf(f, " ");
12202                 linelen++;
12203             }
12204             fprintf(f, "%s", move_buffer);
12205             linelen += movelen;
12206         }
12207
12208         i++;
12209     }
12210
12211     /* Start a new line */
12212     if (linelen > 0) fprintf(f, "\n");
12213
12214     /* Print comments after last move */
12215     if (commentList[i] != NULL) {
12216         fprintf(f, "%s\n", commentList[i]);
12217     }
12218
12219     /* Print result */
12220     if (gameInfo.resultDetails != NULL &&
12221         gameInfo.resultDetails[0] != NULLCHAR) {
12222         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12223                 PGNResult(gameInfo.result));
12224     } else {
12225         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12226     }
12227
12228     fclose(f);
12229     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12230     return TRUE;
12231 }
12232
12233 /* Save game in old style and close the file */
12234 int
12235 SaveGameOldStyle(f)
12236      FILE *f;
12237 {
12238     int i, offset;
12239     time_t tm;
12240
12241     tm = time((time_t *) NULL);
12242
12243     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12244     PrintOpponents(f);
12245
12246     if (backwardMostMove > 0 || startedFromSetupPosition) {
12247         fprintf(f, "\n[--------------\n");
12248         PrintPosition(f, backwardMostMove);
12249         fprintf(f, "--------------]\n");
12250     } else {
12251         fprintf(f, "\n");
12252     }
12253
12254     i = backwardMostMove;
12255     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12256
12257     while (i < forwardMostMove) {
12258         if (commentList[i] != NULL) {
12259             fprintf(f, "[%s]\n", commentList[i]);
12260         }
12261
12262         if ((i % 2) == 1) {
12263             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12264             i++;
12265         } else {
12266             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12267             i++;
12268             if (commentList[i] != NULL) {
12269                 fprintf(f, "\n");
12270                 continue;
12271             }
12272             if (i >= forwardMostMove) {
12273                 fprintf(f, "\n");
12274                 break;
12275             }
12276             fprintf(f, "%s\n", parseList[i]);
12277             i++;
12278         }
12279     }
12280
12281     if (commentList[i] != NULL) {
12282         fprintf(f, "[%s]\n", commentList[i]);
12283     }
12284
12285     /* This isn't really the old style, but it's close enough */
12286     if (gameInfo.resultDetails != NULL &&
12287         gameInfo.resultDetails[0] != NULLCHAR) {
12288         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12289                 gameInfo.resultDetails);
12290     } else {
12291         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12292     }
12293
12294     fclose(f);
12295     return TRUE;
12296 }
12297
12298 /* Save the current game to open file f and close the file */
12299 int
12300 SaveGame(f, dummy, dummy2)
12301      FILE *f;
12302      int dummy;
12303      char *dummy2;
12304 {
12305     if (gameMode == EditPosition) EditPositionDone(TRUE);
12306     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12307     if (appData.oldSaveStyle)
12308       return SaveGameOldStyle(f);
12309     else
12310       return SaveGamePGN(f);
12311 }
12312
12313 /* Save the current position to the given file */
12314 int
12315 SavePositionToFile(filename)
12316      char *filename;
12317 {
12318     FILE *f;
12319     char buf[MSG_SIZ];
12320
12321     if (strcmp(filename, "-") == 0) {
12322         return SavePosition(stdout, 0, NULL);
12323     } else {
12324         f = fopen(filename, "a");
12325         if (f == NULL) {
12326             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12327             DisplayError(buf, errno);
12328             return FALSE;
12329         } else {
12330             safeStrCpy(buf, lastMsg, MSG_SIZ);
12331             DisplayMessage(_("Waiting for access to save file"), "");
12332             flock(fileno(f), LOCK_EX); // [HGM] lock
12333             DisplayMessage(_("Saving position"), "");
12334             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12335             SavePosition(f, 0, NULL);
12336             DisplayMessage(buf, "");
12337             return TRUE;
12338         }
12339     }
12340 }
12341
12342 /* Save the current position to the given open file and close the file */
12343 int
12344 SavePosition(f, dummy, dummy2)
12345      FILE *f;
12346      int dummy;
12347      char *dummy2;
12348 {
12349     time_t tm;
12350     char *fen;
12351
12352     if (gameMode == EditPosition) EditPositionDone(TRUE);
12353     if (appData.oldSaveStyle) {
12354         tm = time((time_t *) NULL);
12355
12356         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12357         PrintOpponents(f);
12358         fprintf(f, "[--------------\n");
12359         PrintPosition(f, currentMove);
12360         fprintf(f, "--------------]\n");
12361     } else {
12362         fen = PositionToFEN(currentMove, NULL);
12363         fprintf(f, "%s\n", fen);
12364         free(fen);
12365     }
12366     fclose(f);
12367     return TRUE;
12368 }
12369
12370 void
12371 ReloadCmailMsgEvent(unregister)
12372      int unregister;
12373 {
12374 #if !WIN32
12375     static char *inFilename = NULL;
12376     static char *outFilename;
12377     int i;
12378     struct stat inbuf, outbuf;
12379     int status;
12380
12381     /* Any registered moves are unregistered if unregister is set, */
12382     /* i.e. invoked by the signal handler */
12383     if (unregister) {
12384         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12385             cmailMoveRegistered[i] = FALSE;
12386             if (cmailCommentList[i] != NULL) {
12387                 free(cmailCommentList[i]);
12388                 cmailCommentList[i] = NULL;
12389             }
12390         }
12391         nCmailMovesRegistered = 0;
12392     }
12393
12394     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12395         cmailResult[i] = CMAIL_NOT_RESULT;
12396     }
12397     nCmailResults = 0;
12398
12399     if (inFilename == NULL) {
12400         /* Because the filenames are static they only get malloced once  */
12401         /* and they never get freed                                      */
12402         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12403         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12404
12405         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12406         sprintf(outFilename, "%s.out", appData.cmailGameName);
12407     }
12408
12409     status = stat(outFilename, &outbuf);
12410     if (status < 0) {
12411         cmailMailedMove = FALSE;
12412     } else {
12413         status = stat(inFilename, &inbuf);
12414         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12415     }
12416
12417     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12418        counts the games, notes how each one terminated, etc.
12419
12420        It would be nice to remove this kludge and instead gather all
12421        the information while building the game list.  (And to keep it
12422        in the game list nodes instead of having a bunch of fixed-size
12423        parallel arrays.)  Note this will require getting each game's
12424        termination from the PGN tags, as the game list builder does
12425        not process the game moves.  --mann
12426        */
12427     cmailMsgLoaded = TRUE;
12428     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12429
12430     /* Load first game in the file or popup game menu */
12431     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12432
12433 #endif /* !WIN32 */
12434     return;
12435 }
12436
12437 int
12438 RegisterMove()
12439 {
12440     FILE *f;
12441     char string[MSG_SIZ];
12442
12443     if (   cmailMailedMove
12444         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12445         return TRUE;            /* Allow free viewing  */
12446     }
12447
12448     /* Unregister move to ensure that we don't leave RegisterMove        */
12449     /* with the move registered when the conditions for registering no   */
12450     /* longer hold                                                       */
12451     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12452         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12453         nCmailMovesRegistered --;
12454
12455         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12456           {
12457               free(cmailCommentList[lastLoadGameNumber - 1]);
12458               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12459           }
12460     }
12461
12462     if (cmailOldMove == -1) {
12463         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12464         return FALSE;
12465     }
12466
12467     if (currentMove > cmailOldMove + 1) {
12468         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12469         return FALSE;
12470     }
12471
12472     if (currentMove < cmailOldMove) {
12473         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12474         return FALSE;
12475     }
12476
12477     if (forwardMostMove > currentMove) {
12478         /* Silently truncate extra moves */
12479         TruncateGame();
12480     }
12481
12482     if (   (currentMove == cmailOldMove + 1)
12483         || (   (currentMove == cmailOldMove)
12484             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12485                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12486         if (gameInfo.result != GameUnfinished) {
12487             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12488         }
12489
12490         if (commentList[currentMove] != NULL) {
12491             cmailCommentList[lastLoadGameNumber - 1]
12492               = StrSave(commentList[currentMove]);
12493         }
12494         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12495
12496         if (appData.debugMode)
12497           fprintf(debugFP, "Saving %s for game %d\n",
12498                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12499
12500         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12501
12502         f = fopen(string, "w");
12503         if (appData.oldSaveStyle) {
12504             SaveGameOldStyle(f); /* also closes the file */
12505
12506             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12507             f = fopen(string, "w");
12508             SavePosition(f, 0, NULL); /* also closes the file */
12509         } else {
12510             fprintf(f, "{--------------\n");
12511             PrintPosition(f, currentMove);
12512             fprintf(f, "--------------}\n\n");
12513
12514             SaveGame(f, 0, NULL); /* also closes the file*/
12515         }
12516
12517         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12518         nCmailMovesRegistered ++;
12519     } else if (nCmailGames == 1) {
12520         DisplayError(_("You have not made a move yet"), 0);
12521         return FALSE;
12522     }
12523
12524     return TRUE;
12525 }
12526
12527 void
12528 MailMoveEvent()
12529 {
12530 #if !WIN32
12531     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12532     FILE *commandOutput;
12533     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12534     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12535     int nBuffers;
12536     int i;
12537     int archived;
12538     char *arcDir;
12539
12540     if (! cmailMsgLoaded) {
12541         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12542         return;
12543     }
12544
12545     if (nCmailGames == nCmailResults) {
12546         DisplayError(_("No unfinished games"), 0);
12547         return;
12548     }
12549
12550 #if CMAIL_PROHIBIT_REMAIL
12551     if (cmailMailedMove) {
12552       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);
12553         DisplayError(msg, 0);
12554         return;
12555     }
12556 #endif
12557
12558     if (! (cmailMailedMove || RegisterMove())) return;
12559
12560     if (   cmailMailedMove
12561         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12562       snprintf(string, MSG_SIZ, partCommandString,
12563                appData.debugMode ? " -v" : "", appData.cmailGameName);
12564         commandOutput = popen(string, "r");
12565
12566         if (commandOutput == NULL) {
12567             DisplayError(_("Failed to invoke cmail"), 0);
12568         } else {
12569             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12570                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12571             }
12572             if (nBuffers > 1) {
12573                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12574                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12575                 nBytes = MSG_SIZ - 1;
12576             } else {
12577                 (void) memcpy(msg, buffer, nBytes);
12578             }
12579             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12580
12581             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12582                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12583
12584                 archived = TRUE;
12585                 for (i = 0; i < nCmailGames; i ++) {
12586                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12587                         archived = FALSE;
12588                     }
12589                 }
12590                 if (   archived
12591                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12592                         != NULL)) {
12593                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12594                            arcDir,
12595                            appData.cmailGameName,
12596                            gameInfo.date);
12597                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12598                     cmailMsgLoaded = FALSE;
12599                 }
12600             }
12601
12602             DisplayInformation(msg);
12603             pclose(commandOutput);
12604         }
12605     } else {
12606         if ((*cmailMsg) != '\0') {
12607             DisplayInformation(cmailMsg);
12608         }
12609     }
12610
12611     return;
12612 #endif /* !WIN32 */
12613 }
12614
12615 char *
12616 CmailMsg()
12617 {
12618 #if WIN32
12619     return NULL;
12620 #else
12621     int  prependComma = 0;
12622     char number[5];
12623     char string[MSG_SIZ];       /* Space for game-list */
12624     int  i;
12625
12626     if (!cmailMsgLoaded) return "";
12627
12628     if (cmailMailedMove) {
12629       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12630     } else {
12631         /* Create a list of games left */
12632       snprintf(string, MSG_SIZ, "[");
12633         for (i = 0; i < nCmailGames; i ++) {
12634             if (! (   cmailMoveRegistered[i]
12635                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12636                 if (prependComma) {
12637                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12638                 } else {
12639                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12640                     prependComma = 1;
12641                 }
12642
12643                 strcat(string, number);
12644             }
12645         }
12646         strcat(string, "]");
12647
12648         if (nCmailMovesRegistered + nCmailResults == 0) {
12649             switch (nCmailGames) {
12650               case 1:
12651                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12652                 break;
12653
12654               case 2:
12655                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12656                 break;
12657
12658               default:
12659                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12660                          nCmailGames);
12661                 break;
12662             }
12663         } else {
12664             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12665               case 1:
12666                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12667                          string);
12668                 break;
12669
12670               case 0:
12671                 if (nCmailResults == nCmailGames) {
12672                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12673                 } else {
12674                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12675                 }
12676                 break;
12677
12678               default:
12679                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12680                          string);
12681             }
12682         }
12683     }
12684     return cmailMsg;
12685 #endif /* WIN32 */
12686 }
12687
12688 void
12689 ResetGameEvent()
12690 {
12691     if (gameMode == Training)
12692       SetTrainingModeOff();
12693
12694     Reset(TRUE, TRUE);
12695     cmailMsgLoaded = FALSE;
12696     if (appData.icsActive) {
12697       SendToICS(ics_prefix);
12698       SendToICS("refresh\n");
12699     }
12700 }
12701
12702 void
12703 ExitEvent(status)
12704      int status;
12705 {
12706     exiting++;
12707     if (exiting > 2) {
12708       /* Give up on clean exit */
12709       exit(status);
12710     }
12711     if (exiting > 1) {
12712       /* Keep trying for clean exit */
12713       return;
12714     }
12715
12716     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12717
12718     if (telnetISR != NULL) {
12719       RemoveInputSource(telnetISR);
12720     }
12721     if (icsPR != NoProc) {
12722       DestroyChildProcess(icsPR, TRUE);
12723     }
12724
12725     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12726     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12727
12728     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12729     /* make sure this other one finishes before killing it!                  */
12730     if(endingGame) { int count = 0;
12731         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12732         while(endingGame && count++ < 10) DoSleep(1);
12733         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12734     }
12735
12736     /* Kill off chess programs */
12737     if (first.pr != NoProc) {
12738         ExitAnalyzeMode();
12739
12740         DoSleep( appData.delayBeforeQuit );
12741         SendToProgram("quit\n", &first);
12742         DoSleep( appData.delayAfterQuit );
12743         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12744     }
12745     if (second.pr != NoProc) {
12746         DoSleep( appData.delayBeforeQuit );
12747         SendToProgram("quit\n", &second);
12748         DoSleep( appData.delayAfterQuit );
12749         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12750     }
12751     if (first.isr != NULL) {
12752         RemoveInputSource(first.isr);
12753     }
12754     if (second.isr != NULL) {
12755         RemoveInputSource(second.isr);
12756     }
12757
12758     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12759     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12760
12761     ShutDownFrontEnd();
12762     exit(status);
12763 }
12764
12765 void
12766 PauseEvent()
12767 {
12768     if (appData.debugMode)
12769         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12770     if (pausing) {
12771         pausing = FALSE;
12772         ModeHighlight();
12773         if (gameMode == MachinePlaysWhite ||
12774             gameMode == MachinePlaysBlack) {
12775             StartClocks();
12776         } else {
12777             DisplayBothClocks();
12778         }
12779         if (gameMode == PlayFromGameFile) {
12780             if (appData.timeDelay >= 0)
12781                 AutoPlayGameLoop();
12782         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12783             Reset(FALSE, TRUE);
12784             SendToICS(ics_prefix);
12785             SendToICS("refresh\n");
12786         } else if (currentMove < forwardMostMove) {
12787             ForwardInner(forwardMostMove);
12788         }
12789         pauseExamInvalid = FALSE;
12790     } else {
12791         switch (gameMode) {
12792           default:
12793             return;
12794           case IcsExamining:
12795             pauseExamForwardMostMove = forwardMostMove;
12796             pauseExamInvalid = FALSE;
12797             /* fall through */
12798           case IcsObserving:
12799           case IcsPlayingWhite:
12800           case IcsPlayingBlack:
12801             pausing = TRUE;
12802             ModeHighlight();
12803             return;
12804           case PlayFromGameFile:
12805             (void) StopLoadGameTimer();
12806             pausing = TRUE;
12807             ModeHighlight();
12808             break;
12809           case BeginningOfGame:
12810             if (appData.icsActive) return;
12811             /* else fall through */
12812           case MachinePlaysWhite:
12813           case MachinePlaysBlack:
12814           case TwoMachinesPlay:
12815             if (forwardMostMove == 0)
12816               return;           /* don't pause if no one has moved */
12817             if ((gameMode == MachinePlaysWhite &&
12818                  !WhiteOnMove(forwardMostMove)) ||
12819                 (gameMode == MachinePlaysBlack &&
12820                  WhiteOnMove(forwardMostMove))) {
12821                 StopClocks();
12822             }
12823             pausing = TRUE;
12824             ModeHighlight();
12825             break;
12826         }
12827     }
12828 }
12829
12830 void
12831 EditCommentEvent()
12832 {
12833     char title[MSG_SIZ];
12834
12835     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12836       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12837     } else {
12838       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12839                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12840                parseList[currentMove - 1]);
12841     }
12842
12843     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12844 }
12845
12846
12847 void
12848 EditTagsEvent()
12849 {
12850     char *tags = PGNTags(&gameInfo);
12851     bookUp = FALSE;
12852     EditTagsPopUp(tags, NULL);
12853     free(tags);
12854 }
12855
12856 void
12857 AnalyzeModeEvent()
12858 {
12859     if (appData.noChessProgram || gameMode == AnalyzeMode)
12860       return;
12861
12862     if (gameMode != AnalyzeFile) {
12863         if (!appData.icsEngineAnalyze) {
12864                EditGameEvent();
12865                if (gameMode != EditGame) return;
12866         }
12867         ResurrectChessProgram();
12868         SendToProgram("analyze\n", &first);
12869         first.analyzing = TRUE;
12870         /*first.maybeThinking = TRUE;*/
12871         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12872         EngineOutputPopUp();
12873     }
12874     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12875     pausing = FALSE;
12876     ModeHighlight();
12877     SetGameInfo();
12878
12879     StartAnalysisClock();
12880     GetTimeMark(&lastNodeCountTime);
12881     lastNodeCount = 0;
12882 }
12883
12884 void
12885 AnalyzeFileEvent()
12886 {
12887     if (appData.noChessProgram || gameMode == AnalyzeFile)
12888       return;
12889
12890     if (gameMode != AnalyzeMode) {
12891         EditGameEvent();
12892         if (gameMode != EditGame) return;
12893         ResurrectChessProgram();
12894         SendToProgram("analyze\n", &first);
12895         first.analyzing = TRUE;
12896         /*first.maybeThinking = TRUE;*/
12897         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12898         EngineOutputPopUp();
12899     }
12900     gameMode = AnalyzeFile;
12901     pausing = FALSE;
12902     ModeHighlight();
12903     SetGameInfo();
12904
12905     StartAnalysisClock();
12906     GetTimeMark(&lastNodeCountTime);
12907     lastNodeCount = 0;
12908     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
12909 }
12910
12911 void
12912 MachineWhiteEvent()
12913 {
12914     char buf[MSG_SIZ];
12915     char *bookHit = NULL;
12916
12917     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12918       return;
12919
12920
12921     if (gameMode == PlayFromGameFile ||
12922         gameMode == TwoMachinesPlay  ||
12923         gameMode == Training         ||
12924         gameMode == AnalyzeMode      ||
12925         gameMode == EndOfGame)
12926         EditGameEvent();
12927
12928     if (gameMode == EditPosition)
12929         EditPositionDone(TRUE);
12930
12931     if (!WhiteOnMove(currentMove)) {
12932         DisplayError(_("It is not White's turn"), 0);
12933         return;
12934     }
12935
12936     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12937       ExitAnalyzeMode();
12938
12939     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12940         gameMode == AnalyzeFile)
12941         TruncateGame();
12942
12943     ResurrectChessProgram();    /* in case it isn't running */
12944     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12945         gameMode = MachinePlaysWhite;
12946         ResetClocks();
12947     } else
12948     gameMode = MachinePlaysWhite;
12949     pausing = FALSE;
12950     ModeHighlight();
12951     SetGameInfo();
12952     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12953     DisplayTitle(buf);
12954     if (first.sendName) {
12955       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12956       SendToProgram(buf, &first);
12957     }
12958     if (first.sendTime) {
12959       if (first.useColors) {
12960         SendToProgram("black\n", &first); /*gnu kludge*/
12961       }
12962       SendTimeRemaining(&first, TRUE);
12963     }
12964     if (first.useColors) {
12965       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12966     }
12967     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12968     SetMachineThinkingEnables();
12969     first.maybeThinking = TRUE;
12970     StartClocks();
12971     firstMove = FALSE;
12972
12973     if (appData.autoFlipView && !flipView) {
12974       flipView = !flipView;
12975       DrawPosition(FALSE, NULL);
12976       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12977     }
12978
12979     if(bookHit) { // [HGM] book: simulate book reply
12980         static char bookMove[MSG_SIZ]; // a bit generous?
12981
12982         programStats.nodes = programStats.depth = programStats.time =
12983         programStats.score = programStats.got_only_move = 0;
12984         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12985
12986         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12987         strcat(bookMove, bookHit);
12988         HandleMachineMove(bookMove, &first);
12989     }
12990 }
12991
12992 void
12993 MachineBlackEvent()
12994 {
12995   char buf[MSG_SIZ];
12996   char *bookHit = NULL;
12997
12998     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12999         return;
13000
13001
13002     if (gameMode == PlayFromGameFile ||
13003         gameMode == TwoMachinesPlay  ||
13004         gameMode == Training         ||
13005         gameMode == AnalyzeMode      ||
13006         gameMode == EndOfGame)
13007         EditGameEvent();
13008
13009     if (gameMode == EditPosition)
13010         EditPositionDone(TRUE);
13011
13012     if (WhiteOnMove(currentMove)) {
13013         DisplayError(_("It is not Black's turn"), 0);
13014         return;
13015     }
13016
13017     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13018       ExitAnalyzeMode();
13019
13020     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13021         gameMode == AnalyzeFile)
13022         TruncateGame();
13023
13024     ResurrectChessProgram();    /* in case it isn't running */
13025     gameMode = MachinePlaysBlack;
13026     pausing = FALSE;
13027     ModeHighlight();
13028     SetGameInfo();
13029     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13030     DisplayTitle(buf);
13031     if (first.sendName) {
13032       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13033       SendToProgram(buf, &first);
13034     }
13035     if (first.sendTime) {
13036       if (first.useColors) {
13037         SendToProgram("white\n", &first); /*gnu kludge*/
13038       }
13039       SendTimeRemaining(&first, FALSE);
13040     }
13041     if (first.useColors) {
13042       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13043     }
13044     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13045     SetMachineThinkingEnables();
13046     first.maybeThinking = TRUE;
13047     StartClocks();
13048
13049     if (appData.autoFlipView && flipView) {
13050       flipView = !flipView;
13051       DrawPosition(FALSE, NULL);
13052       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13053     }
13054     if(bookHit) { // [HGM] book: simulate book reply
13055         static char bookMove[MSG_SIZ]; // a bit generous?
13056
13057         programStats.nodes = programStats.depth = programStats.time =
13058         programStats.score = programStats.got_only_move = 0;
13059         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13060
13061         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13062         strcat(bookMove, bookHit);
13063         HandleMachineMove(bookMove, &first);
13064     }
13065 }
13066
13067
13068 void
13069 DisplayTwoMachinesTitle()
13070 {
13071     char buf[MSG_SIZ];
13072     if (appData.matchGames > 0) {
13073         if(appData.tourneyFile[0]) {
13074           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13075                    gameInfo.white, gameInfo.black,
13076                    nextGame+1, appData.matchGames+1,
13077                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13078         } else 
13079         if (first.twoMachinesColor[0] == 'w') {
13080           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13081                    gameInfo.white, gameInfo.black,
13082                    first.matchWins, second.matchWins,
13083                    matchGame - 1 - (first.matchWins + second.matchWins));
13084         } else {
13085           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13086                    gameInfo.white, gameInfo.black,
13087                    second.matchWins, first.matchWins,
13088                    matchGame - 1 - (first.matchWins + second.matchWins));
13089         }
13090     } else {
13091       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13092     }
13093     DisplayTitle(buf);
13094 }
13095
13096 void
13097 SettingsMenuIfReady()
13098 {
13099   if (second.lastPing != second.lastPong) {
13100     DisplayMessage("", _("Waiting for second chess program"));
13101     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13102     return;
13103   }
13104   ThawUI();
13105   DisplayMessage("", "");
13106   SettingsPopUp(&second);
13107 }
13108
13109 int
13110 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13111 {
13112     char buf[MSG_SIZ];
13113     if (cps->pr == NULL) {
13114         StartChessProgram(cps);
13115         if (cps->protocolVersion == 1) {
13116           retry();
13117         } else {
13118           /* kludge: allow timeout for initial "feature" command */
13119           FreezeUI();
13120           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13121           DisplayMessage("", buf);
13122           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13123         }
13124         return 1;
13125     }
13126     return 0;
13127 }
13128
13129 void
13130 TwoMachinesEvent P((void))
13131 {
13132     int i;
13133     char buf[MSG_SIZ];
13134     ChessProgramState *onmove;
13135     char *bookHit = NULL;
13136     static int stalling = 0;
13137     TimeMark now;
13138     long wait;
13139
13140     if (appData.noChessProgram) return;
13141
13142     switch (gameMode) {
13143       case TwoMachinesPlay:
13144         return;
13145       case MachinePlaysWhite:
13146       case MachinePlaysBlack:
13147         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13148             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13149             return;
13150         }
13151         /* fall through */
13152       case BeginningOfGame:
13153       case PlayFromGameFile:
13154       case EndOfGame:
13155         EditGameEvent();
13156         if (gameMode != EditGame) return;
13157         break;
13158       case EditPosition:
13159         EditPositionDone(TRUE);
13160         break;
13161       case AnalyzeMode:
13162       case AnalyzeFile:
13163         ExitAnalyzeMode();
13164         break;
13165       case EditGame:
13166       default:
13167         break;
13168     }
13169
13170 //    forwardMostMove = currentMove;
13171     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13172
13173     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13174
13175     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13176     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13177       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13178       return;
13179     }
13180     if(!stalling) {
13181       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13182       SendToProgram("force\n", &second);
13183       stalling = 1;
13184       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13185       return;
13186     }
13187     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13188     if(appData.matchPause>10000 || appData.matchPause<10)
13189                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13190     wait = SubtractTimeMarks(&now, &pauseStart);
13191     if(wait < appData.matchPause) {
13192         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13193         return;
13194     }
13195     stalling = 0;
13196     DisplayMessage("", "");
13197     if (startedFromSetupPosition) {
13198         SendBoard(&second, backwardMostMove);
13199     if (appData.debugMode) {
13200         fprintf(debugFP, "Two Machines\n");
13201     }
13202     }
13203     for (i = backwardMostMove; i < forwardMostMove; i++) {
13204         SendMoveToProgram(i, &second);
13205     }
13206
13207     gameMode = TwoMachinesPlay;
13208     pausing = FALSE;
13209     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13210     SetGameInfo();
13211     DisplayTwoMachinesTitle();
13212     firstMove = TRUE;
13213     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13214         onmove = &first;
13215     } else {
13216         onmove = &second;
13217     }
13218     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13219     SendToProgram(first.computerString, &first);
13220     if (first.sendName) {
13221       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13222       SendToProgram(buf, &first);
13223     }
13224     SendToProgram(second.computerString, &second);
13225     if (second.sendName) {
13226       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13227       SendToProgram(buf, &second);
13228     }
13229
13230     ResetClocks();
13231     if (!first.sendTime || !second.sendTime) {
13232         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13233         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13234     }
13235     if (onmove->sendTime) {
13236       if (onmove->useColors) {
13237         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13238       }
13239       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13240     }
13241     if (onmove->useColors) {
13242       SendToProgram(onmove->twoMachinesColor, onmove);
13243     }
13244     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13245 //    SendToProgram("go\n", onmove);
13246     onmove->maybeThinking = TRUE;
13247     SetMachineThinkingEnables();
13248
13249     StartClocks();
13250
13251     if(bookHit) { // [HGM] book: simulate book reply
13252         static char bookMove[MSG_SIZ]; // a bit generous?
13253
13254         programStats.nodes = programStats.depth = programStats.time =
13255         programStats.score = programStats.got_only_move = 0;
13256         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13257
13258         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13259         strcat(bookMove, bookHit);
13260         savedMessage = bookMove; // args for deferred call
13261         savedState = onmove;
13262         ScheduleDelayedEvent(DeferredBookMove, 1);
13263     }
13264 }
13265
13266 void
13267 TrainingEvent()
13268 {
13269     if (gameMode == Training) {
13270       SetTrainingModeOff();
13271       gameMode = PlayFromGameFile;
13272       DisplayMessage("", _("Training mode off"));
13273     } else {
13274       gameMode = Training;
13275       animateTraining = appData.animate;
13276
13277       /* make sure we are not already at the end of the game */
13278       if (currentMove < forwardMostMove) {
13279         SetTrainingModeOn();
13280         DisplayMessage("", _("Training mode on"));
13281       } else {
13282         gameMode = PlayFromGameFile;
13283         DisplayError(_("Already at end of game"), 0);
13284       }
13285     }
13286     ModeHighlight();
13287 }
13288
13289 void
13290 IcsClientEvent()
13291 {
13292     if (!appData.icsActive) return;
13293     switch (gameMode) {
13294       case IcsPlayingWhite:
13295       case IcsPlayingBlack:
13296       case IcsObserving:
13297       case IcsIdle:
13298       case BeginningOfGame:
13299       case IcsExamining:
13300         return;
13301
13302       case EditGame:
13303         break;
13304
13305       case EditPosition:
13306         EditPositionDone(TRUE);
13307         break;
13308
13309       case AnalyzeMode:
13310       case AnalyzeFile:
13311         ExitAnalyzeMode();
13312         break;
13313
13314       default:
13315         EditGameEvent();
13316         break;
13317     }
13318
13319     gameMode = IcsIdle;
13320     ModeHighlight();
13321     return;
13322 }
13323
13324
13325 void
13326 EditGameEvent()
13327 {
13328     int i;
13329
13330     switch (gameMode) {
13331       case Training:
13332         SetTrainingModeOff();
13333         break;
13334       case MachinePlaysWhite:
13335       case MachinePlaysBlack:
13336       case BeginningOfGame:
13337         SendToProgram("force\n", &first);
13338         SetUserThinkingEnables();
13339         break;
13340       case PlayFromGameFile:
13341         (void) StopLoadGameTimer();
13342         if (gameFileFP != NULL) {
13343             gameFileFP = NULL;
13344         }
13345         break;
13346       case EditPosition:
13347         EditPositionDone(TRUE);
13348         break;
13349       case AnalyzeMode:
13350       case AnalyzeFile:
13351         ExitAnalyzeMode();
13352         SendToProgram("force\n", &first);
13353         break;
13354       case TwoMachinesPlay:
13355         GameEnds(EndOfFile, NULL, GE_PLAYER);
13356         ResurrectChessProgram();
13357         SetUserThinkingEnables();
13358         break;
13359       case EndOfGame:
13360         ResurrectChessProgram();
13361         break;
13362       case IcsPlayingBlack:
13363       case IcsPlayingWhite:
13364         DisplayError(_("Warning: You are still playing a game"), 0);
13365         break;
13366       case IcsObserving:
13367         DisplayError(_("Warning: You are still observing a game"), 0);
13368         break;
13369       case IcsExamining:
13370         DisplayError(_("Warning: You are still examining a game"), 0);
13371         break;
13372       case IcsIdle:
13373         break;
13374       case EditGame:
13375       default:
13376         return;
13377     }
13378
13379     pausing = FALSE;
13380     StopClocks();
13381     first.offeredDraw = second.offeredDraw = 0;
13382
13383     if (gameMode == PlayFromGameFile) {
13384         whiteTimeRemaining = timeRemaining[0][currentMove];
13385         blackTimeRemaining = timeRemaining[1][currentMove];
13386         DisplayTitle("");
13387     }
13388
13389     if (gameMode == MachinePlaysWhite ||
13390         gameMode == MachinePlaysBlack ||
13391         gameMode == TwoMachinesPlay ||
13392         gameMode == EndOfGame) {
13393         i = forwardMostMove;
13394         while (i > currentMove) {
13395             SendToProgram("undo\n", &first);
13396             i--;
13397         }
13398         whiteTimeRemaining = timeRemaining[0][currentMove];
13399         blackTimeRemaining = timeRemaining[1][currentMove];
13400         DisplayBothClocks();
13401         if (whiteFlag || blackFlag) {
13402             whiteFlag = blackFlag = 0;
13403         }
13404         DisplayTitle("");
13405     }
13406
13407     gameMode = EditGame;
13408     ModeHighlight();
13409     SetGameInfo();
13410 }
13411
13412
13413 void
13414 EditPositionEvent()
13415 {
13416     if (gameMode == EditPosition) {
13417         EditGameEvent();
13418         return;
13419     }
13420
13421     EditGameEvent();
13422     if (gameMode != EditGame) return;
13423
13424     gameMode = EditPosition;
13425     ModeHighlight();
13426     SetGameInfo();
13427     if (currentMove > 0)
13428       CopyBoard(boards[0], boards[currentMove]);
13429
13430     blackPlaysFirst = !WhiteOnMove(currentMove);
13431     ResetClocks();
13432     currentMove = forwardMostMove = backwardMostMove = 0;
13433     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13434     DisplayMove(-1);
13435 }
13436
13437 void
13438 ExitAnalyzeMode()
13439 {
13440     /* [DM] icsEngineAnalyze - possible call from other functions */
13441     if (appData.icsEngineAnalyze) {
13442         appData.icsEngineAnalyze = FALSE;
13443
13444         DisplayMessage("",_("Close ICS engine analyze..."));
13445     }
13446     if (first.analysisSupport && first.analyzing) {
13447       SendToProgram("exit\n", &first);
13448       first.analyzing = FALSE;
13449     }
13450     thinkOutput[0] = NULLCHAR;
13451 }
13452
13453 void
13454 EditPositionDone(Boolean fakeRights)
13455 {
13456     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13457
13458     startedFromSetupPosition = TRUE;
13459     InitChessProgram(&first, FALSE);
13460     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13461       boards[0][EP_STATUS] = EP_NONE;
13462       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13463     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13464         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13465         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13466       } else boards[0][CASTLING][2] = NoRights;
13467     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13468         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13469         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13470       } else boards[0][CASTLING][5] = NoRights;
13471     }
13472     SendToProgram("force\n", &first);
13473     if (blackPlaysFirst) {
13474         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13475         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13476         currentMove = forwardMostMove = backwardMostMove = 1;
13477         CopyBoard(boards[1], boards[0]);
13478     } else {
13479         currentMove = forwardMostMove = backwardMostMove = 0;
13480     }
13481     SendBoard(&first, forwardMostMove);
13482     if (appData.debugMode) {
13483         fprintf(debugFP, "EditPosDone\n");
13484     }
13485     DisplayTitle("");
13486     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13487     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13488     gameMode = EditGame;
13489     ModeHighlight();
13490     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13491     ClearHighlights(); /* [AS] */
13492 }
13493
13494 /* Pause for `ms' milliseconds */
13495 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13496 void
13497 TimeDelay(ms)
13498      long ms;
13499 {
13500     TimeMark m1, m2;
13501
13502     GetTimeMark(&m1);
13503     do {
13504         GetTimeMark(&m2);
13505     } while (SubtractTimeMarks(&m2, &m1) < ms);
13506 }
13507
13508 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13509 void
13510 SendMultiLineToICS(buf)
13511      char *buf;
13512 {
13513     char temp[MSG_SIZ+1], *p;
13514     int len;
13515
13516     len = strlen(buf);
13517     if (len > MSG_SIZ)
13518       len = MSG_SIZ;
13519
13520     strncpy(temp, buf, len);
13521     temp[len] = 0;
13522
13523     p = temp;
13524     while (*p) {
13525         if (*p == '\n' || *p == '\r')
13526           *p = ' ';
13527         ++p;
13528     }
13529
13530     strcat(temp, "\n");
13531     SendToICS(temp);
13532     SendToPlayer(temp, strlen(temp));
13533 }
13534
13535 void
13536 SetWhiteToPlayEvent()
13537 {
13538     if (gameMode == EditPosition) {
13539         blackPlaysFirst = FALSE;
13540         DisplayBothClocks();    /* works because currentMove is 0 */
13541     } else if (gameMode == IcsExamining) {
13542         SendToICS(ics_prefix);
13543         SendToICS("tomove white\n");
13544     }
13545 }
13546
13547 void
13548 SetBlackToPlayEvent()
13549 {
13550     if (gameMode == EditPosition) {
13551         blackPlaysFirst = TRUE;
13552         currentMove = 1;        /* kludge */
13553         DisplayBothClocks();
13554         currentMove = 0;
13555     } else if (gameMode == IcsExamining) {
13556         SendToICS(ics_prefix);
13557         SendToICS("tomove black\n");
13558     }
13559 }
13560
13561 void
13562 EditPositionMenuEvent(selection, x, y)
13563      ChessSquare selection;
13564      int x, y;
13565 {
13566     char buf[MSG_SIZ];
13567     ChessSquare piece = boards[0][y][x];
13568
13569     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13570
13571     switch (selection) {
13572       case ClearBoard:
13573         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13574             SendToICS(ics_prefix);
13575             SendToICS("bsetup clear\n");
13576         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13577             SendToICS(ics_prefix);
13578             SendToICS("clearboard\n");
13579         } else {
13580             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13581                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13582                 for (y = 0; y < BOARD_HEIGHT; y++) {
13583                     if (gameMode == IcsExamining) {
13584                         if (boards[currentMove][y][x] != EmptySquare) {
13585                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13586                                     AAA + x, ONE + y);
13587                             SendToICS(buf);
13588                         }
13589                     } else {
13590                         boards[0][y][x] = p;
13591                     }
13592                 }
13593             }
13594         }
13595         if (gameMode == EditPosition) {
13596             DrawPosition(FALSE, boards[0]);
13597         }
13598         break;
13599
13600       case WhitePlay:
13601         SetWhiteToPlayEvent();
13602         break;
13603
13604       case BlackPlay:
13605         SetBlackToPlayEvent();
13606         break;
13607
13608       case EmptySquare:
13609         if (gameMode == IcsExamining) {
13610             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13611             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13612             SendToICS(buf);
13613         } else {
13614             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13615                 if(x == BOARD_LEFT-2) {
13616                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13617                     boards[0][y][1] = 0;
13618                 } else
13619                 if(x == BOARD_RGHT+1) {
13620                     if(y >= gameInfo.holdingsSize) break;
13621                     boards[0][y][BOARD_WIDTH-2] = 0;
13622                 } else break;
13623             }
13624             boards[0][y][x] = EmptySquare;
13625             DrawPosition(FALSE, boards[0]);
13626         }
13627         break;
13628
13629       case PromotePiece:
13630         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13631            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13632             selection = (ChessSquare) (PROMOTED piece);
13633         } else if(piece == EmptySquare) selection = WhiteSilver;
13634         else selection = (ChessSquare)((int)piece - 1);
13635         goto defaultlabel;
13636
13637       case DemotePiece:
13638         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13639            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13640             selection = (ChessSquare) (DEMOTED piece);
13641         } else if(piece == EmptySquare) selection = BlackSilver;
13642         else selection = (ChessSquare)((int)piece + 1);
13643         goto defaultlabel;
13644
13645       case WhiteQueen:
13646       case BlackQueen:
13647         if(gameInfo.variant == VariantShatranj ||
13648            gameInfo.variant == VariantXiangqi  ||
13649            gameInfo.variant == VariantCourier  ||
13650            gameInfo.variant == VariantMakruk     )
13651             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13652         goto defaultlabel;
13653
13654       case WhiteKing:
13655       case BlackKing:
13656         if(gameInfo.variant == VariantXiangqi)
13657             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13658         if(gameInfo.variant == VariantKnightmate)
13659             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13660       default:
13661         defaultlabel:
13662         if (gameMode == IcsExamining) {
13663             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13664             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13665                      PieceToChar(selection), AAA + x, ONE + y);
13666             SendToICS(buf);
13667         } else {
13668             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13669                 int n;
13670                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13671                     n = PieceToNumber(selection - BlackPawn);
13672                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13673                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13674                     boards[0][BOARD_HEIGHT-1-n][1]++;
13675                 } else
13676                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13677                     n = PieceToNumber(selection);
13678                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13679                     boards[0][n][BOARD_WIDTH-1] = selection;
13680                     boards[0][n][BOARD_WIDTH-2]++;
13681                 }
13682             } else
13683             boards[0][y][x] = selection;
13684             DrawPosition(TRUE, boards[0]);
13685         }
13686         break;
13687     }
13688 }
13689
13690
13691 void
13692 DropMenuEvent(selection, x, y)
13693      ChessSquare selection;
13694      int x, y;
13695 {
13696     ChessMove moveType;
13697
13698     switch (gameMode) {
13699       case IcsPlayingWhite:
13700       case MachinePlaysBlack:
13701         if (!WhiteOnMove(currentMove)) {
13702             DisplayMoveError(_("It is Black's turn"));
13703             return;
13704         }
13705         moveType = WhiteDrop;
13706         break;
13707       case IcsPlayingBlack:
13708       case MachinePlaysWhite:
13709         if (WhiteOnMove(currentMove)) {
13710             DisplayMoveError(_("It is White's turn"));
13711             return;
13712         }
13713         moveType = BlackDrop;
13714         break;
13715       case EditGame:
13716         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13717         break;
13718       default:
13719         return;
13720     }
13721
13722     if (moveType == BlackDrop && selection < BlackPawn) {
13723       selection = (ChessSquare) ((int) selection
13724                                  + (int) BlackPawn - (int) WhitePawn);
13725     }
13726     if (boards[currentMove][y][x] != EmptySquare) {
13727         DisplayMoveError(_("That square is occupied"));
13728         return;
13729     }
13730
13731     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13732 }
13733
13734 void
13735 AcceptEvent()
13736 {
13737     /* Accept a pending offer of any kind from opponent */
13738
13739     if (appData.icsActive) {
13740         SendToICS(ics_prefix);
13741         SendToICS("accept\n");
13742     } else if (cmailMsgLoaded) {
13743         if (currentMove == cmailOldMove &&
13744             commentList[cmailOldMove] != NULL &&
13745             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13746                    "Black offers a draw" : "White offers a draw")) {
13747             TruncateGame();
13748             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13749             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13750         } else {
13751             DisplayError(_("There is no pending offer on this move"), 0);
13752             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13753         }
13754     } else {
13755         /* Not used for offers from chess program */
13756     }
13757 }
13758
13759 void
13760 DeclineEvent()
13761 {
13762     /* Decline a pending offer of any kind from opponent */
13763
13764     if (appData.icsActive) {
13765         SendToICS(ics_prefix);
13766         SendToICS("decline\n");
13767     } else if (cmailMsgLoaded) {
13768         if (currentMove == cmailOldMove &&
13769             commentList[cmailOldMove] != NULL &&
13770             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13771                    "Black offers a draw" : "White offers a draw")) {
13772 #ifdef NOTDEF
13773             AppendComment(cmailOldMove, "Draw declined", TRUE);
13774             DisplayComment(cmailOldMove - 1, "Draw declined");
13775 #endif /*NOTDEF*/
13776         } else {
13777             DisplayError(_("There is no pending offer on this move"), 0);
13778         }
13779     } else {
13780         /* Not used for offers from chess program */
13781     }
13782 }
13783
13784 void
13785 RematchEvent()
13786 {
13787     /* Issue ICS rematch command */
13788     if (appData.icsActive) {
13789         SendToICS(ics_prefix);
13790         SendToICS("rematch\n");
13791     }
13792 }
13793
13794 void
13795 CallFlagEvent()
13796 {
13797     /* Call your opponent's flag (claim a win on time) */
13798     if (appData.icsActive) {
13799         SendToICS(ics_prefix);
13800         SendToICS("flag\n");
13801     } else {
13802         switch (gameMode) {
13803           default:
13804             return;
13805           case MachinePlaysWhite:
13806             if (whiteFlag) {
13807                 if (blackFlag)
13808                   GameEnds(GameIsDrawn, "Both players ran out of time",
13809                            GE_PLAYER);
13810                 else
13811                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13812             } else {
13813                 DisplayError(_("Your opponent is not out of time"), 0);
13814             }
13815             break;
13816           case MachinePlaysBlack:
13817             if (blackFlag) {
13818                 if (whiteFlag)
13819                   GameEnds(GameIsDrawn, "Both players ran out of time",
13820                            GE_PLAYER);
13821                 else
13822                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13823             } else {
13824                 DisplayError(_("Your opponent is not out of time"), 0);
13825             }
13826             break;
13827         }
13828     }
13829 }
13830
13831 void
13832 ClockClick(int which)
13833 {       // [HGM] code moved to back-end from winboard.c
13834         if(which) { // black clock
13835           if (gameMode == EditPosition || gameMode == IcsExamining) {
13836             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13837             SetBlackToPlayEvent();
13838           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13839           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13840           } else if (shiftKey) {
13841             AdjustClock(which, -1);
13842           } else if (gameMode == IcsPlayingWhite ||
13843                      gameMode == MachinePlaysBlack) {
13844             CallFlagEvent();
13845           }
13846         } else { // white clock
13847           if (gameMode == EditPosition || gameMode == IcsExamining) {
13848             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13849             SetWhiteToPlayEvent();
13850           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13851           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13852           } else if (shiftKey) {
13853             AdjustClock(which, -1);
13854           } else if (gameMode == IcsPlayingBlack ||
13855                    gameMode == MachinePlaysWhite) {
13856             CallFlagEvent();
13857           }
13858         }
13859 }
13860
13861 void
13862 DrawEvent()
13863 {
13864     /* Offer draw or accept pending draw offer from opponent */
13865
13866     if (appData.icsActive) {
13867         /* Note: tournament rules require draw offers to be
13868            made after you make your move but before you punch
13869            your clock.  Currently ICS doesn't let you do that;
13870            instead, you immediately punch your clock after making
13871            a move, but you can offer a draw at any time. */
13872
13873         SendToICS(ics_prefix);
13874         SendToICS("draw\n");
13875         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13876     } else if (cmailMsgLoaded) {
13877         if (currentMove == cmailOldMove &&
13878             commentList[cmailOldMove] != NULL &&
13879             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13880                    "Black offers a draw" : "White offers a draw")) {
13881             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13882             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13883         } else if (currentMove == cmailOldMove + 1) {
13884             char *offer = WhiteOnMove(cmailOldMove) ?
13885               "White offers a draw" : "Black offers a draw";
13886             AppendComment(currentMove, offer, TRUE);
13887             DisplayComment(currentMove - 1, offer);
13888             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13889         } else {
13890             DisplayError(_("You must make your move before offering a draw"), 0);
13891             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13892         }
13893     } else if (first.offeredDraw) {
13894         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13895     } else {
13896         if (first.sendDrawOffers) {
13897             SendToProgram("draw\n", &first);
13898             userOfferedDraw = TRUE;
13899         }
13900     }
13901 }
13902
13903 void
13904 AdjournEvent()
13905 {
13906     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13907
13908     if (appData.icsActive) {
13909         SendToICS(ics_prefix);
13910         SendToICS("adjourn\n");
13911     } else {
13912         /* Currently GNU Chess doesn't offer or accept Adjourns */
13913     }
13914 }
13915
13916
13917 void
13918 AbortEvent()
13919 {
13920     /* Offer Abort or accept pending Abort offer from opponent */
13921
13922     if (appData.icsActive) {
13923         SendToICS(ics_prefix);
13924         SendToICS("abort\n");
13925     } else {
13926         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13927     }
13928 }
13929
13930 void
13931 ResignEvent()
13932 {
13933     /* Resign.  You can do this even if it's not your turn. */
13934
13935     if (appData.icsActive) {
13936         SendToICS(ics_prefix);
13937         SendToICS("resign\n");
13938     } else {
13939         switch (gameMode) {
13940           case MachinePlaysWhite:
13941             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13942             break;
13943           case MachinePlaysBlack:
13944             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13945             break;
13946           case EditGame:
13947             if (cmailMsgLoaded) {
13948                 TruncateGame();
13949                 if (WhiteOnMove(cmailOldMove)) {
13950                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13951                 } else {
13952                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13953                 }
13954                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13955             }
13956             break;
13957           default:
13958             break;
13959         }
13960     }
13961 }
13962
13963
13964 void
13965 StopObservingEvent()
13966 {
13967     /* Stop observing current games */
13968     SendToICS(ics_prefix);
13969     SendToICS("unobserve\n");
13970 }
13971
13972 void
13973 StopExaminingEvent()
13974 {
13975     /* Stop observing current game */
13976     SendToICS(ics_prefix);
13977     SendToICS("unexamine\n");
13978 }
13979
13980 void
13981 ForwardInner(target)
13982      int target;
13983 {
13984     int limit;
13985
13986     if (appData.debugMode)
13987         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13988                 target, currentMove, forwardMostMove);
13989
13990     if (gameMode == EditPosition)
13991       return;
13992
13993     if (gameMode == PlayFromGameFile && !pausing)
13994       PauseEvent();
13995
13996     if (gameMode == IcsExamining && pausing)
13997       limit = pauseExamForwardMostMove;
13998     else
13999       limit = forwardMostMove;
14000
14001     if (target > limit) target = limit;
14002
14003     if (target > 0 && moveList[target - 1][0]) {
14004         int fromX, fromY, toX, toY;
14005         toX = moveList[target - 1][2] - AAA;
14006         toY = moveList[target - 1][3] - ONE;
14007         if (moveList[target - 1][1] == '@') {
14008             if (appData.highlightLastMove) {
14009                 SetHighlights(-1, -1, toX, toY);
14010             }
14011         } else {
14012             fromX = moveList[target - 1][0] - AAA;
14013             fromY = moveList[target - 1][1] - ONE;
14014             if (target == currentMove + 1) {
14015                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14016             }
14017             if (appData.highlightLastMove) {
14018                 SetHighlights(fromX, fromY, toX, toY);
14019             }
14020         }
14021     }
14022     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14023         gameMode == Training || gameMode == PlayFromGameFile ||
14024         gameMode == AnalyzeFile) {
14025         while (currentMove < target) {
14026             SendMoveToProgram(currentMove++, &first);
14027         }
14028     } else {
14029         currentMove = target;
14030     }
14031
14032     if (gameMode == EditGame || gameMode == EndOfGame) {
14033         whiteTimeRemaining = timeRemaining[0][currentMove];
14034         blackTimeRemaining = timeRemaining[1][currentMove];
14035     }
14036     DisplayBothClocks();
14037     DisplayMove(currentMove - 1);
14038     DrawPosition(FALSE, boards[currentMove]);
14039     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14040     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14041         DisplayComment(currentMove - 1, commentList[currentMove]);
14042     }
14043     DisplayBook(currentMove);
14044 }
14045
14046
14047 void
14048 ForwardEvent()
14049 {
14050     if (gameMode == IcsExamining && !pausing) {
14051         SendToICS(ics_prefix);
14052         SendToICS("forward\n");
14053     } else {
14054         ForwardInner(currentMove + 1);
14055     }
14056 }
14057
14058 void
14059 ToEndEvent()
14060 {
14061     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14062         /* to optimze, we temporarily turn off analysis mode while we feed
14063          * the remaining moves to the engine. Otherwise we get analysis output
14064          * after each move.
14065          */
14066         if (first.analysisSupport) {
14067           SendToProgram("exit\nforce\n", &first);
14068           first.analyzing = FALSE;
14069         }
14070     }
14071
14072     if (gameMode == IcsExamining && !pausing) {
14073         SendToICS(ics_prefix);
14074         SendToICS("forward 999999\n");
14075     } else {
14076         ForwardInner(forwardMostMove);
14077     }
14078
14079     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14080         /* we have fed all the moves, so reactivate analysis mode */
14081         SendToProgram("analyze\n", &first);
14082         first.analyzing = TRUE;
14083         /*first.maybeThinking = TRUE;*/
14084         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14085     }
14086 }
14087
14088 void
14089 BackwardInner(target)
14090      int target;
14091 {
14092     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14093
14094     if (appData.debugMode)
14095         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14096                 target, currentMove, forwardMostMove);
14097
14098     if (gameMode == EditPosition) return;
14099     if (currentMove <= backwardMostMove) {
14100         ClearHighlights();
14101         DrawPosition(full_redraw, boards[currentMove]);
14102         return;
14103     }
14104     if (gameMode == PlayFromGameFile && !pausing)
14105       PauseEvent();
14106
14107     if (moveList[target][0]) {
14108         int fromX, fromY, toX, toY;
14109         toX = moveList[target][2] - AAA;
14110         toY = moveList[target][3] - ONE;
14111         if (moveList[target][1] == '@') {
14112             if (appData.highlightLastMove) {
14113                 SetHighlights(-1, -1, toX, toY);
14114             }
14115         } else {
14116             fromX = moveList[target][0] - AAA;
14117             fromY = moveList[target][1] - ONE;
14118             if (target == currentMove - 1) {
14119                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14120             }
14121             if (appData.highlightLastMove) {
14122                 SetHighlights(fromX, fromY, toX, toY);
14123             }
14124         }
14125     }
14126     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14127         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14128         while (currentMove > target) {
14129             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14130                 // null move cannot be undone. Reload program with move history before it.
14131                 int i;
14132                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14133                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14134                 }
14135                 SendBoard(&first, i); 
14136                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14137                 break;
14138             }
14139             SendToProgram("undo\n", &first);
14140             currentMove--;
14141         }
14142     } else {
14143         currentMove = target;
14144     }
14145
14146     if (gameMode == EditGame || gameMode == EndOfGame) {
14147         whiteTimeRemaining = timeRemaining[0][currentMove];
14148         blackTimeRemaining = timeRemaining[1][currentMove];
14149     }
14150     DisplayBothClocks();
14151     DisplayMove(currentMove - 1);
14152     DrawPosition(full_redraw, boards[currentMove]);
14153     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14154     // [HGM] PV info: routine tests if comment empty
14155     DisplayComment(currentMove - 1, commentList[currentMove]);
14156     DisplayBook(currentMove);
14157 }
14158
14159 void
14160 BackwardEvent()
14161 {
14162     if (gameMode == IcsExamining && !pausing) {
14163         SendToICS(ics_prefix);
14164         SendToICS("backward\n");
14165     } else {
14166         BackwardInner(currentMove - 1);
14167     }
14168 }
14169
14170 void
14171 ToStartEvent()
14172 {
14173     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14174         /* to optimize, we temporarily turn off analysis mode while we undo
14175          * all the moves. Otherwise we get analysis output after each undo.
14176          */
14177         if (first.analysisSupport) {
14178           SendToProgram("exit\nforce\n", &first);
14179           first.analyzing = FALSE;
14180         }
14181     }
14182
14183     if (gameMode == IcsExamining && !pausing) {
14184         SendToICS(ics_prefix);
14185         SendToICS("backward 999999\n");
14186     } else {
14187         BackwardInner(backwardMostMove);
14188     }
14189
14190     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14191         /* we have fed all the moves, so reactivate analysis mode */
14192         SendToProgram("analyze\n", &first);
14193         first.analyzing = TRUE;
14194         /*first.maybeThinking = TRUE;*/
14195         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14196     }
14197 }
14198
14199 void
14200 ToNrEvent(int to)
14201 {
14202   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14203   if (to >= forwardMostMove) to = forwardMostMove;
14204   if (to <= backwardMostMove) to = backwardMostMove;
14205   if (to < currentMove) {
14206     BackwardInner(to);
14207   } else {
14208     ForwardInner(to);
14209   }
14210 }
14211
14212 void
14213 RevertEvent(Boolean annotate)
14214 {
14215     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14216         return;
14217     }
14218     if (gameMode != IcsExamining) {
14219         DisplayError(_("You are not examining a game"), 0);
14220         return;
14221     }
14222     if (pausing) {
14223         DisplayError(_("You can't revert while pausing"), 0);
14224         return;
14225     }
14226     SendToICS(ics_prefix);
14227     SendToICS("revert\n");
14228 }
14229
14230 void
14231 RetractMoveEvent()
14232 {
14233     switch (gameMode) {
14234       case MachinePlaysWhite:
14235       case MachinePlaysBlack:
14236         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14237             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14238             return;
14239         }
14240         if (forwardMostMove < 2) return;
14241         currentMove = forwardMostMove = forwardMostMove - 2;
14242         whiteTimeRemaining = timeRemaining[0][currentMove];
14243         blackTimeRemaining = timeRemaining[1][currentMove];
14244         DisplayBothClocks();
14245         DisplayMove(currentMove - 1);
14246         ClearHighlights();/*!! could figure this out*/
14247         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14248         SendToProgram("remove\n", &first);
14249         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14250         break;
14251
14252       case BeginningOfGame:
14253       default:
14254         break;
14255
14256       case IcsPlayingWhite:
14257       case IcsPlayingBlack:
14258         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14259             SendToICS(ics_prefix);
14260             SendToICS("takeback 2\n");
14261         } else {
14262             SendToICS(ics_prefix);
14263             SendToICS("takeback 1\n");
14264         }
14265         break;
14266     }
14267 }
14268
14269 void
14270 MoveNowEvent()
14271 {
14272     ChessProgramState *cps;
14273
14274     switch (gameMode) {
14275       case MachinePlaysWhite:
14276         if (!WhiteOnMove(forwardMostMove)) {
14277             DisplayError(_("It is your turn"), 0);
14278             return;
14279         }
14280         cps = &first;
14281         break;
14282       case MachinePlaysBlack:
14283         if (WhiteOnMove(forwardMostMove)) {
14284             DisplayError(_("It is your turn"), 0);
14285             return;
14286         }
14287         cps = &first;
14288         break;
14289       case TwoMachinesPlay:
14290         if (WhiteOnMove(forwardMostMove) ==
14291             (first.twoMachinesColor[0] == 'w')) {
14292             cps = &first;
14293         } else {
14294             cps = &second;
14295         }
14296         break;
14297       case BeginningOfGame:
14298       default:
14299         return;
14300     }
14301     SendToProgram("?\n", cps);
14302 }
14303
14304 void
14305 TruncateGameEvent()
14306 {
14307     EditGameEvent();
14308     if (gameMode != EditGame) return;
14309     TruncateGame();
14310 }
14311
14312 void
14313 TruncateGame()
14314 {
14315     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14316     if (forwardMostMove > currentMove) {
14317         if (gameInfo.resultDetails != NULL) {
14318             free(gameInfo.resultDetails);
14319             gameInfo.resultDetails = NULL;
14320             gameInfo.result = GameUnfinished;
14321         }
14322         forwardMostMove = currentMove;
14323         HistorySet(parseList, backwardMostMove, forwardMostMove,
14324                    currentMove-1);
14325     }
14326 }
14327
14328 void
14329 HintEvent()
14330 {
14331     if (appData.noChessProgram) return;
14332     switch (gameMode) {
14333       case MachinePlaysWhite:
14334         if (WhiteOnMove(forwardMostMove)) {
14335             DisplayError(_("Wait until your turn"), 0);
14336             return;
14337         }
14338         break;
14339       case BeginningOfGame:
14340       case MachinePlaysBlack:
14341         if (!WhiteOnMove(forwardMostMove)) {
14342             DisplayError(_("Wait until your turn"), 0);
14343             return;
14344         }
14345         break;
14346       default:
14347         DisplayError(_("No hint available"), 0);
14348         return;
14349     }
14350     SendToProgram("hint\n", &first);
14351     hintRequested = TRUE;
14352 }
14353
14354 void
14355 BookEvent()
14356 {
14357     if (appData.noChessProgram) return;
14358     switch (gameMode) {
14359       case MachinePlaysWhite:
14360         if (WhiteOnMove(forwardMostMove)) {
14361             DisplayError(_("Wait until your turn"), 0);
14362             return;
14363         }
14364         break;
14365       case BeginningOfGame:
14366       case MachinePlaysBlack:
14367         if (!WhiteOnMove(forwardMostMove)) {
14368             DisplayError(_("Wait until your turn"), 0);
14369             return;
14370         }
14371         break;
14372       case EditPosition:
14373         EditPositionDone(TRUE);
14374         break;
14375       case TwoMachinesPlay:
14376         return;
14377       default:
14378         break;
14379     }
14380     SendToProgram("bk\n", &first);
14381     bookOutput[0] = NULLCHAR;
14382     bookRequested = TRUE;
14383 }
14384
14385 void
14386 AboutGameEvent()
14387 {
14388     char *tags = PGNTags(&gameInfo);
14389     TagsPopUp(tags, CmailMsg());
14390     free(tags);
14391 }
14392
14393 /* end button procedures */
14394
14395 void
14396 PrintPosition(fp, move)
14397      FILE *fp;
14398      int move;
14399 {
14400     int i, j;
14401
14402     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14403         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14404             char c = PieceToChar(boards[move][i][j]);
14405             fputc(c == 'x' ? '.' : c, fp);
14406             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14407         }
14408     }
14409     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14410       fprintf(fp, "white to play\n");
14411     else
14412       fprintf(fp, "black to play\n");
14413 }
14414
14415 void
14416 PrintOpponents(fp)
14417      FILE *fp;
14418 {
14419     if (gameInfo.white != NULL) {
14420         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14421     } else {
14422         fprintf(fp, "\n");
14423     }
14424 }
14425
14426 /* Find last component of program's own name, using some heuristics */
14427 void
14428 TidyProgramName(prog, host, buf)
14429      char *prog, *host, buf[MSG_SIZ];
14430 {
14431     char *p, *q;
14432     int local = (strcmp(host, "localhost") == 0);
14433     while (!local && (p = strchr(prog, ';')) != NULL) {
14434         p++;
14435         while (*p == ' ') p++;
14436         prog = p;
14437     }
14438     if (*prog == '"' || *prog == '\'') {
14439         q = strchr(prog + 1, *prog);
14440     } else {
14441         q = strchr(prog, ' ');
14442     }
14443     if (q == NULL) q = prog + strlen(prog);
14444     p = q;
14445     while (p >= prog && *p != '/' && *p != '\\') p--;
14446     p++;
14447     if(p == prog && *p == '"') p++;
14448     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14449     memcpy(buf, p, q - p);
14450     buf[q - p] = NULLCHAR;
14451     if (!local) {
14452         strcat(buf, "@");
14453         strcat(buf, host);
14454     }
14455 }
14456
14457 char *
14458 TimeControlTagValue()
14459 {
14460     char buf[MSG_SIZ];
14461     if (!appData.clockMode) {
14462       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14463     } else if (movesPerSession > 0) {
14464       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14465     } else if (timeIncrement == 0) {
14466       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14467     } else {
14468       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14469     }
14470     return StrSave(buf);
14471 }
14472
14473 void
14474 SetGameInfo()
14475 {
14476     /* This routine is used only for certain modes */
14477     VariantClass v = gameInfo.variant;
14478     ChessMove r = GameUnfinished;
14479     char *p = NULL;
14480
14481     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14482         r = gameInfo.result;
14483         p = gameInfo.resultDetails;
14484         gameInfo.resultDetails = NULL;
14485     }
14486     ClearGameInfo(&gameInfo);
14487     gameInfo.variant = v;
14488
14489     switch (gameMode) {
14490       case MachinePlaysWhite:
14491         gameInfo.event = StrSave( appData.pgnEventHeader );
14492         gameInfo.site = StrSave(HostName());
14493         gameInfo.date = PGNDate();
14494         gameInfo.round = StrSave("-");
14495         gameInfo.white = StrSave(first.tidy);
14496         gameInfo.black = StrSave(UserName());
14497         gameInfo.timeControl = TimeControlTagValue();
14498         break;
14499
14500       case MachinePlaysBlack:
14501         gameInfo.event = StrSave( appData.pgnEventHeader );
14502         gameInfo.site = StrSave(HostName());
14503         gameInfo.date = PGNDate();
14504         gameInfo.round = StrSave("-");
14505         gameInfo.white = StrSave(UserName());
14506         gameInfo.black = StrSave(first.tidy);
14507         gameInfo.timeControl = TimeControlTagValue();
14508         break;
14509
14510       case TwoMachinesPlay:
14511         gameInfo.event = StrSave( appData.pgnEventHeader );
14512         gameInfo.site = StrSave(HostName());
14513         gameInfo.date = PGNDate();
14514         if (roundNr > 0) {
14515             char buf[MSG_SIZ];
14516             snprintf(buf, MSG_SIZ, "%d", roundNr);
14517             gameInfo.round = StrSave(buf);
14518         } else {
14519             gameInfo.round = StrSave("-");
14520         }
14521         if (first.twoMachinesColor[0] == 'w') {
14522             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14523             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14524         } else {
14525             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14526             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14527         }
14528         gameInfo.timeControl = TimeControlTagValue();
14529         break;
14530
14531       case EditGame:
14532         gameInfo.event = StrSave("Edited game");
14533         gameInfo.site = StrSave(HostName());
14534         gameInfo.date = PGNDate();
14535         gameInfo.round = StrSave("-");
14536         gameInfo.white = StrSave("-");
14537         gameInfo.black = StrSave("-");
14538         gameInfo.result = r;
14539         gameInfo.resultDetails = p;
14540         break;
14541
14542       case EditPosition:
14543         gameInfo.event = StrSave("Edited position");
14544         gameInfo.site = StrSave(HostName());
14545         gameInfo.date = PGNDate();
14546         gameInfo.round = StrSave("-");
14547         gameInfo.white = StrSave("-");
14548         gameInfo.black = StrSave("-");
14549         break;
14550
14551       case IcsPlayingWhite:
14552       case IcsPlayingBlack:
14553       case IcsObserving:
14554       case IcsExamining:
14555         break;
14556
14557       case PlayFromGameFile:
14558         gameInfo.event = StrSave("Game from non-PGN file");
14559         gameInfo.site = StrSave(HostName());
14560         gameInfo.date = PGNDate();
14561         gameInfo.round = StrSave("-");
14562         gameInfo.white = StrSave("?");
14563         gameInfo.black = StrSave("?");
14564         break;
14565
14566       default:
14567         break;
14568     }
14569 }
14570
14571 void
14572 ReplaceComment(index, text)
14573      int index;
14574      char *text;
14575 {
14576     int len;
14577     char *p;
14578     float score;
14579
14580     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14581        pvInfoList[index-1].depth == len &&
14582        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14583        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14584     while (*text == '\n') text++;
14585     len = strlen(text);
14586     while (len > 0 && text[len - 1] == '\n') len--;
14587
14588     if (commentList[index] != NULL)
14589       free(commentList[index]);
14590
14591     if (len == 0) {
14592         commentList[index] = NULL;
14593         return;
14594     }
14595   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14596       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14597       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14598     commentList[index] = (char *) malloc(len + 2);
14599     strncpy(commentList[index], text, len);
14600     commentList[index][len] = '\n';
14601     commentList[index][len + 1] = NULLCHAR;
14602   } else {
14603     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14604     char *p;
14605     commentList[index] = (char *) malloc(len + 7);
14606     safeStrCpy(commentList[index], "{\n", 3);
14607     safeStrCpy(commentList[index]+2, text, len+1);
14608     commentList[index][len+2] = NULLCHAR;
14609     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14610     strcat(commentList[index], "\n}\n");
14611   }
14612 }
14613
14614 void
14615 CrushCRs(text)
14616      char *text;
14617 {
14618   char *p = text;
14619   char *q = text;
14620   char ch;
14621
14622   do {
14623     ch = *p++;
14624     if (ch == '\r') continue;
14625     *q++ = ch;
14626   } while (ch != '\0');
14627 }
14628
14629 void
14630 AppendComment(index, text, addBraces)
14631      int index;
14632      char *text;
14633      Boolean addBraces; // [HGM] braces: tells if we should add {}
14634 {
14635     int oldlen, len;
14636     char *old;
14637
14638 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14639     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14640
14641     CrushCRs(text);
14642     while (*text == '\n') text++;
14643     len = strlen(text);
14644     while (len > 0 && text[len - 1] == '\n') len--;
14645
14646     if (len == 0) return;
14647
14648     if (commentList[index] != NULL) {
14649       Boolean addClosingBrace = addBraces;
14650         old = commentList[index];
14651         oldlen = strlen(old);
14652         while(commentList[index][oldlen-1] ==  '\n')
14653           commentList[index][--oldlen] = NULLCHAR;
14654         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14655         safeStrCpy(commentList[index], old, oldlen + len + 6);
14656         free(old);
14657         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14658         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14659           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14660           while (*text == '\n') { text++; len--; }
14661           commentList[index][--oldlen] = NULLCHAR;
14662       }
14663         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14664         else          strcat(commentList[index], "\n");
14665         strcat(commentList[index], text);
14666         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14667         else          strcat(commentList[index], "\n");
14668     } else {
14669         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14670         if(addBraces)
14671           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14672         else commentList[index][0] = NULLCHAR;
14673         strcat(commentList[index], text);
14674         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14675         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14676     }
14677 }
14678
14679 static char * FindStr( char * text, char * sub_text )
14680 {
14681     char * result = strstr( text, sub_text );
14682
14683     if( result != NULL ) {
14684         result += strlen( sub_text );
14685     }
14686
14687     return result;
14688 }
14689
14690 /* [AS] Try to extract PV info from PGN comment */
14691 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14692 char *GetInfoFromComment( int index, char * text )
14693 {
14694     char * sep = text, *p;
14695
14696     if( text != NULL && index > 0 ) {
14697         int score = 0;
14698         int depth = 0;
14699         int time = -1, sec = 0, deci;
14700         char * s_eval = FindStr( text, "[%eval " );
14701         char * s_emt = FindStr( text, "[%emt " );
14702
14703         if( s_eval != NULL || s_emt != NULL ) {
14704             /* New style */
14705             char delim;
14706
14707             if( s_eval != NULL ) {
14708                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14709                     return text;
14710                 }
14711
14712                 if( delim != ']' ) {
14713                     return text;
14714                 }
14715             }
14716
14717             if( s_emt != NULL ) {
14718             }
14719                 return text;
14720         }
14721         else {
14722             /* We expect something like: [+|-]nnn.nn/dd */
14723             int score_lo = 0;
14724
14725             if(*text != '{') return text; // [HGM] braces: must be normal comment
14726
14727             sep = strchr( text, '/' );
14728             if( sep == NULL || sep < (text+4) ) {
14729                 return text;
14730             }
14731
14732             p = text;
14733             if(p[1] == '(') { // comment starts with PV
14734                p = strchr(p, ')'); // locate end of PV
14735                if(p == NULL || sep < p+5) return text;
14736                // at this point we have something like "{(.*) +0.23/6 ..."
14737                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14738                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14739                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14740             }
14741             time = -1; sec = -1; deci = -1;
14742             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14743                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14744                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14745                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14746                 return text;
14747             }
14748
14749             if( score_lo < 0 || score_lo >= 100 ) {
14750                 return text;
14751             }
14752
14753             if(sec >= 0) time = 600*time + 10*sec; else
14754             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14755
14756             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14757
14758             /* [HGM] PV time: now locate end of PV info */
14759             while( *++sep >= '0' && *sep <= '9'); // strip depth
14760             if(time >= 0)
14761             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14762             if(sec >= 0)
14763             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14764             if(deci >= 0)
14765             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14766             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14767         }
14768
14769         if( depth <= 0 ) {
14770             return text;
14771         }
14772
14773         if( time < 0 ) {
14774             time = -1;
14775         }
14776
14777         pvInfoList[index-1].depth = depth;
14778         pvInfoList[index-1].score = score;
14779         pvInfoList[index-1].time  = 10*time; // centi-sec
14780         if(*sep == '}') *sep = 0; else *--sep = '{';
14781         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14782     }
14783     return sep;
14784 }
14785
14786 void
14787 SendToProgram(message, cps)
14788      char *message;
14789      ChessProgramState *cps;
14790 {
14791     int count, outCount, error;
14792     char buf[MSG_SIZ];
14793
14794     if (cps->pr == NULL) return;
14795     Attention(cps);
14796
14797     if (appData.debugMode) {
14798         TimeMark now;
14799         GetTimeMark(&now);
14800         fprintf(debugFP, "%ld >%-6s: %s",
14801                 SubtractTimeMarks(&now, &programStartTime),
14802                 cps->which, message);
14803     }
14804
14805     count = strlen(message);
14806     outCount = OutputToProcess(cps->pr, message, count, &error);
14807     if (outCount < count && !exiting
14808                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14809       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14810       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14811         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14812             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14813                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14814                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14815                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14816             } else {
14817                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14818                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14819                 gameInfo.result = res;
14820             }
14821             gameInfo.resultDetails = StrSave(buf);
14822         }
14823         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14824         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14825     }
14826 }
14827
14828 void
14829 ReceiveFromProgram(isr, closure, message, count, error)
14830      InputSourceRef isr;
14831      VOIDSTAR closure;
14832      char *message;
14833      int count;
14834      int error;
14835 {
14836     char *end_str;
14837     char buf[MSG_SIZ];
14838     ChessProgramState *cps = (ChessProgramState *)closure;
14839
14840     if (isr != cps->isr) return; /* Killed intentionally */
14841     if (count <= 0) {
14842         if (count == 0) {
14843             RemoveInputSource(cps->isr);
14844             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14845             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14846                     _(cps->which), cps->program);
14847         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14848                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14849                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14850                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14851                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14852                 } else {
14853                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14854                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14855                     gameInfo.result = res;
14856                 }
14857                 gameInfo.resultDetails = StrSave(buf);
14858             }
14859             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14860             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14861         } else {
14862             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14863                     _(cps->which), cps->program);
14864             RemoveInputSource(cps->isr);
14865
14866             /* [AS] Program is misbehaving badly... kill it */
14867             if( count == -2 ) {
14868                 DestroyChildProcess( cps->pr, 9 );
14869                 cps->pr = NoProc;
14870             }
14871
14872             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14873         }
14874         return;
14875     }
14876
14877     if ((end_str = strchr(message, '\r')) != NULL)
14878       *end_str = NULLCHAR;
14879     if ((end_str = strchr(message, '\n')) != NULL)
14880       *end_str = NULLCHAR;
14881
14882     if (appData.debugMode) {
14883         TimeMark now; int print = 1;
14884         char *quote = ""; char c; int i;
14885
14886         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14887                 char start = message[0];
14888                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14889                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14890                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14891                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14892                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14893                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14894                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14895                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14896                    sscanf(message, "hint: %c", &c)!=1 && 
14897                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14898                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14899                     print = (appData.engineComments >= 2);
14900                 }
14901                 message[0] = start; // restore original message
14902         }
14903         if(print) {
14904                 GetTimeMark(&now);
14905                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14906                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14907                         quote,
14908                         message);
14909         }
14910     }
14911
14912     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14913     if (appData.icsEngineAnalyze) {
14914         if (strstr(message, "whisper") != NULL ||
14915              strstr(message, "kibitz") != NULL ||
14916             strstr(message, "tellics") != NULL) return;
14917     }
14918
14919     HandleMachineMove(message, cps);
14920 }
14921
14922
14923 void
14924 SendTimeControl(cps, mps, tc, inc, sd, st)
14925      ChessProgramState *cps;
14926      int mps, inc, sd, st;
14927      long tc;
14928 {
14929     char buf[MSG_SIZ];
14930     int seconds;
14931
14932     if( timeControl_2 > 0 ) {
14933         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14934             tc = timeControl_2;
14935         }
14936     }
14937     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14938     inc /= cps->timeOdds;
14939     st  /= cps->timeOdds;
14940
14941     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14942
14943     if (st > 0) {
14944       /* Set exact time per move, normally using st command */
14945       if (cps->stKludge) {
14946         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14947         seconds = st % 60;
14948         if (seconds == 0) {
14949           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14950         } else {
14951           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14952         }
14953       } else {
14954         snprintf(buf, MSG_SIZ, "st %d\n", st);
14955       }
14956     } else {
14957       /* Set conventional or incremental time control, using level command */
14958       if (seconds == 0) {
14959         /* Note old gnuchess bug -- minutes:seconds used to not work.
14960            Fixed in later versions, but still avoid :seconds
14961            when seconds is 0. */
14962         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14963       } else {
14964         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14965                  seconds, inc/1000.);
14966       }
14967     }
14968     SendToProgram(buf, cps);
14969
14970     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14971     /* Orthogonally, limit search to given depth */
14972     if (sd > 0) {
14973       if (cps->sdKludge) {
14974         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14975       } else {
14976         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14977       }
14978       SendToProgram(buf, cps);
14979     }
14980
14981     if(cps->nps >= 0) { /* [HGM] nps */
14982         if(cps->supportsNPS == FALSE)
14983           cps->nps = -1; // don't use if engine explicitly says not supported!
14984         else {
14985           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14986           SendToProgram(buf, cps);
14987         }
14988     }
14989 }
14990
14991 ChessProgramState *WhitePlayer()
14992 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14993 {
14994     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14995        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14996         return &second;
14997     return &first;
14998 }
14999
15000 void
15001 SendTimeRemaining(cps, machineWhite)
15002      ChessProgramState *cps;
15003      int /*boolean*/ machineWhite;
15004 {
15005     char message[MSG_SIZ];
15006     long time, otime;
15007
15008     /* Note: this routine must be called when the clocks are stopped
15009        or when they have *just* been set or switched; otherwise
15010        it will be off by the time since the current tick started.
15011     */
15012     if (machineWhite) {
15013         time = whiteTimeRemaining / 10;
15014         otime = blackTimeRemaining / 10;
15015     } else {
15016         time = blackTimeRemaining / 10;
15017         otime = whiteTimeRemaining / 10;
15018     }
15019     /* [HGM] translate opponent's time by time-odds factor */
15020     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15021     if (appData.debugMode) {
15022         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15023     }
15024
15025     if (time <= 0) time = 1;
15026     if (otime <= 0) otime = 1;
15027
15028     snprintf(message, MSG_SIZ, "time %ld\n", time);
15029     SendToProgram(message, cps);
15030
15031     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15032     SendToProgram(message, cps);
15033 }
15034
15035 int
15036 BoolFeature(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   int val;
15045
15046   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15047     (*p) += len + 1;
15048     sscanf(*p, "%d", &val);
15049     *loc = (val != 0);
15050     while (**p && **p != ' ')
15051       (*p)++;
15052     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15053     SendToProgram(buf, cps);
15054     return TRUE;
15055   }
15056   return FALSE;
15057 }
15058
15059 int
15060 IntFeature(p, name, loc, cps)
15061      char **p;
15062      char *name;
15063      int *loc;
15064      ChessProgramState *cps;
15065 {
15066   char buf[MSG_SIZ];
15067   int len = strlen(name);
15068   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15069     (*p) += len + 1;
15070     sscanf(*p, "%d", loc);
15071     while (**p && **p != ' ') (*p)++;
15072     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15073     SendToProgram(buf, cps);
15074     return TRUE;
15075   }
15076   return FALSE;
15077 }
15078
15079 int
15080 StringFeature(p, name, loc, cps)
15081      char **p;
15082      char *name;
15083      char loc[];
15084      ChessProgramState *cps;
15085 {
15086   char buf[MSG_SIZ];
15087   int len = strlen(name);
15088   if (strncmp((*p), name, len) == 0
15089       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15090     (*p) += len + 2;
15091     sscanf(*p, "%[^\"]", loc);
15092     while (**p && **p != '\"') (*p)++;
15093     if (**p == '\"') (*p)++;
15094     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15095     SendToProgram(buf, cps);
15096     return TRUE;
15097   }
15098   return FALSE;
15099 }
15100
15101 int
15102 ParseOption(Option *opt, ChessProgramState *cps)
15103 // [HGM] options: process the string that defines an engine option, and determine
15104 // name, type, default value, and allowed value range
15105 {
15106         char *p, *q, buf[MSG_SIZ];
15107         int n, min = (-1)<<31, max = 1<<31, def;
15108
15109         if(p = strstr(opt->name, " -spin ")) {
15110             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15111             if(max < min) max = min; // enforce consistency
15112             if(def < min) def = min;
15113             if(def > max) def = max;
15114             opt->value = def;
15115             opt->min = min;
15116             opt->max = max;
15117             opt->type = Spin;
15118         } else if((p = strstr(opt->name, " -slider "))) {
15119             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15120             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15121             if(max < min) max = min; // enforce consistency
15122             if(def < min) def = min;
15123             if(def > max) def = max;
15124             opt->value = def;
15125             opt->min = min;
15126             opt->max = max;
15127             opt->type = Spin; // Slider;
15128         } else if((p = strstr(opt->name, " -string "))) {
15129             opt->textValue = p+9;
15130             opt->type = TextBox;
15131         } else if((p = strstr(opt->name, " -file "))) {
15132             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15133             opt->textValue = p+7;
15134             opt->type = FileName; // FileName;
15135         } else if((p = strstr(opt->name, " -path "))) {
15136             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15137             opt->textValue = p+7;
15138             opt->type = PathName; // PathName;
15139         } else if(p = strstr(opt->name, " -check ")) {
15140             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15141             opt->value = (def != 0);
15142             opt->type = CheckBox;
15143         } else if(p = strstr(opt->name, " -combo ")) {
15144             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15145             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15146             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15147             opt->value = n = 0;
15148             while(q = StrStr(q, " /// ")) {
15149                 n++; *q = 0;    // count choices, and null-terminate each of them
15150                 q += 5;
15151                 if(*q == '*') { // remember default, which is marked with * prefix
15152                     q++;
15153                     opt->value = n;
15154                 }
15155                 cps->comboList[cps->comboCnt++] = q;
15156             }
15157             cps->comboList[cps->comboCnt++] = NULL;
15158             opt->max = n + 1;
15159             opt->type = ComboBox;
15160         } else if(p = strstr(opt->name, " -button")) {
15161             opt->type = Button;
15162         } else if(p = strstr(opt->name, " -save")) {
15163             opt->type = SaveButton;
15164         } else return FALSE;
15165         *p = 0; // terminate option name
15166         // now look if the command-line options define a setting for this engine option.
15167         if(cps->optionSettings && cps->optionSettings[0])
15168             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15169         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15170           snprintf(buf, MSG_SIZ, "option %s", p);
15171                 if(p = strstr(buf, ",")) *p = 0;
15172                 if(q = strchr(buf, '=')) switch(opt->type) {
15173                     case ComboBox:
15174                         for(n=0; n<opt->max; n++)
15175                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15176                         break;
15177                     case TextBox:
15178                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15179                         break;
15180                     case Spin:
15181                     case CheckBox:
15182                         opt->value = atoi(q+1);
15183                     default:
15184                         break;
15185                 }
15186                 strcat(buf, "\n");
15187                 SendToProgram(buf, cps);
15188         }
15189         return TRUE;
15190 }
15191
15192 void
15193 FeatureDone(cps, val)
15194      ChessProgramState* cps;
15195      int val;
15196 {
15197   DelayedEventCallback cb = GetDelayedEvent();
15198   if ((cb == InitBackEnd3 && cps == &first) ||
15199       (cb == SettingsMenuIfReady && cps == &second) ||
15200       (cb == LoadEngine) ||
15201       (cb == TwoMachinesEventIfReady)) {
15202     CancelDelayedEvent();
15203     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15204   }
15205   cps->initDone = val;
15206 }
15207
15208 /* Parse feature command from engine */
15209 void
15210 ParseFeatures(args, cps)
15211      char* args;
15212      ChessProgramState *cps;
15213 {
15214   char *p = args;
15215   char *q;
15216   int val;
15217   char buf[MSG_SIZ];
15218
15219   for (;;) {
15220     while (*p == ' ') p++;
15221     if (*p == NULLCHAR) return;
15222
15223     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15224     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15225     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15226     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15227     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15228     if (BoolFeature(&p, "reuse", &val, cps)) {
15229       /* Engine can disable reuse, but can't enable it if user said no */
15230       if (!val) cps->reuse = FALSE;
15231       continue;
15232     }
15233     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15234     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15235       if (gameMode == TwoMachinesPlay) {
15236         DisplayTwoMachinesTitle();
15237       } else {
15238         DisplayTitle("");
15239       }
15240       continue;
15241     }
15242     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15243     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15244     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15245     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15246     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15247     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15248     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15249     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15250     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15251     if (IntFeature(&p, "done", &val, cps)) {
15252       FeatureDone(cps, val);
15253       continue;
15254     }
15255     /* Added by Tord: */
15256     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15257     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15258     /* End of additions by Tord */
15259
15260     /* [HGM] added features: */
15261     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15262     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15263     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15264     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15265     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15266     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15267     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15268         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15269           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15270             SendToProgram(buf, cps);
15271             continue;
15272         }
15273         if(cps->nrOptions >= MAX_OPTIONS) {
15274             cps->nrOptions--;
15275             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15276             DisplayError(buf, 0);
15277         }
15278         continue;
15279     }
15280     /* End of additions by HGM */
15281
15282     /* unknown feature: complain and skip */
15283     q = p;
15284     while (*q && *q != '=') q++;
15285     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15286     SendToProgram(buf, cps);
15287     p = q;
15288     if (*p == '=') {
15289       p++;
15290       if (*p == '\"') {
15291         p++;
15292         while (*p && *p != '\"') p++;
15293         if (*p == '\"') p++;
15294       } else {
15295         while (*p && *p != ' ') p++;
15296       }
15297     }
15298   }
15299
15300 }
15301
15302 void
15303 PeriodicUpdatesEvent(newState)
15304      int newState;
15305 {
15306     if (newState == appData.periodicUpdates)
15307       return;
15308
15309     appData.periodicUpdates=newState;
15310
15311     /* Display type changes, so update it now */
15312 //    DisplayAnalysis();
15313
15314     /* Get the ball rolling again... */
15315     if (newState) {
15316         AnalysisPeriodicEvent(1);
15317         StartAnalysisClock();
15318     }
15319 }
15320
15321 void
15322 PonderNextMoveEvent(newState)
15323      int newState;
15324 {
15325     if (newState == appData.ponderNextMove) return;
15326     if (gameMode == EditPosition) EditPositionDone(TRUE);
15327     if (newState) {
15328         SendToProgram("hard\n", &first);
15329         if (gameMode == TwoMachinesPlay) {
15330             SendToProgram("hard\n", &second);
15331         }
15332     } else {
15333         SendToProgram("easy\n", &first);
15334         thinkOutput[0] = NULLCHAR;
15335         if (gameMode == TwoMachinesPlay) {
15336             SendToProgram("easy\n", &second);
15337         }
15338     }
15339     appData.ponderNextMove = newState;
15340 }
15341
15342 void
15343 NewSettingEvent(option, feature, command, value)
15344      char *command;
15345      int option, value, *feature;
15346 {
15347     char buf[MSG_SIZ];
15348
15349     if (gameMode == EditPosition) EditPositionDone(TRUE);
15350     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15351     if(feature == NULL || *feature) SendToProgram(buf, &first);
15352     if (gameMode == TwoMachinesPlay) {
15353         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15354     }
15355 }
15356
15357 void
15358 ShowThinkingEvent()
15359 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15360 {
15361     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15362     int newState = appData.showThinking
15363         // [HGM] thinking: other features now need thinking output as well
15364         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15365
15366     if (oldState == newState) return;
15367     oldState = newState;
15368     if (gameMode == EditPosition) EditPositionDone(TRUE);
15369     if (oldState) {
15370         SendToProgram("post\n", &first);
15371         if (gameMode == TwoMachinesPlay) {
15372             SendToProgram("post\n", &second);
15373         }
15374     } else {
15375         SendToProgram("nopost\n", &first);
15376         thinkOutput[0] = NULLCHAR;
15377         if (gameMode == TwoMachinesPlay) {
15378             SendToProgram("nopost\n", &second);
15379         }
15380     }
15381 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15382 }
15383
15384 void
15385 AskQuestionEvent(title, question, replyPrefix, which)
15386      char *title; char *question; char *replyPrefix; char *which;
15387 {
15388   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15389   if (pr == NoProc) return;
15390   AskQuestion(title, question, replyPrefix, pr);
15391 }
15392
15393 void
15394 TypeInEvent(char firstChar)
15395 {
15396     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15397         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15398         gameMode == AnalyzeMode || gameMode == EditGame || 
15399         gameMode == EditPosition || gameMode == IcsExamining ||
15400         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15401         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15402                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15403                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15404         gameMode == Training) PopUpMoveDialog(firstChar);
15405 }
15406
15407 void
15408 TypeInDoneEvent(char *move)
15409 {
15410         Board board;
15411         int n, fromX, fromY, toX, toY;
15412         char promoChar;
15413         ChessMove moveType;
15414
15415         // [HGM] FENedit
15416         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15417                 EditPositionPasteFEN(move);
15418                 return;
15419         }
15420         // [HGM] movenum: allow move number to be typed in any mode
15421         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15422           ToNrEvent(2*n-1);
15423           return;
15424         }
15425
15426       if (gameMode != EditGame && currentMove != forwardMostMove && 
15427         gameMode != Training) {
15428         DisplayMoveError(_("Displayed move is not current"));
15429       } else {
15430         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15431           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15432         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15433         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15434           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15435           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15436         } else {
15437           DisplayMoveError(_("Could not parse move"));
15438         }
15439       }
15440 }
15441
15442 void
15443 DisplayMove(moveNumber)
15444      int moveNumber;
15445 {
15446     char message[MSG_SIZ];
15447     char res[MSG_SIZ];
15448     char cpThinkOutput[MSG_SIZ];
15449
15450     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15451
15452     if (moveNumber == forwardMostMove - 1 ||
15453         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15454
15455         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15456
15457         if (strchr(cpThinkOutput, '\n')) {
15458             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15459         }
15460     } else {
15461         *cpThinkOutput = NULLCHAR;
15462     }
15463
15464     /* [AS] Hide thinking from human user */
15465     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15466         *cpThinkOutput = NULLCHAR;
15467         if( thinkOutput[0] != NULLCHAR ) {
15468             int i;
15469
15470             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15471                 cpThinkOutput[i] = '.';
15472             }
15473             cpThinkOutput[i] = NULLCHAR;
15474             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15475         }
15476     }
15477
15478     if (moveNumber == forwardMostMove - 1 &&
15479         gameInfo.resultDetails != NULL) {
15480         if (gameInfo.resultDetails[0] == NULLCHAR) {
15481           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15482         } else {
15483           snprintf(res, MSG_SIZ, " {%s} %s",
15484                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15485         }
15486     } else {
15487         res[0] = NULLCHAR;
15488     }
15489
15490     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15491         DisplayMessage(res, cpThinkOutput);
15492     } else {
15493       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15494                 WhiteOnMove(moveNumber) ? " " : ".. ",
15495                 parseList[moveNumber], res);
15496         DisplayMessage(message, cpThinkOutput);
15497     }
15498 }
15499
15500 void
15501 DisplayComment(moveNumber, text)
15502      int moveNumber;
15503      char *text;
15504 {
15505     char title[MSG_SIZ];
15506
15507     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15508       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15509     } else {
15510       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15511               WhiteOnMove(moveNumber) ? " " : ".. ",
15512               parseList[moveNumber]);
15513     }
15514     if (text != NULL && (appData.autoDisplayComment || commentUp))
15515         CommentPopUp(title, text);
15516 }
15517
15518 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15519  * might be busy thinking or pondering.  It can be omitted if your
15520  * gnuchess is configured to stop thinking immediately on any user
15521  * input.  However, that gnuchess feature depends on the FIONREAD
15522  * ioctl, which does not work properly on some flavors of Unix.
15523  */
15524 void
15525 Attention(cps)
15526      ChessProgramState *cps;
15527 {
15528 #if ATTENTION
15529     if (!cps->useSigint) return;
15530     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15531     switch (gameMode) {
15532       case MachinePlaysWhite:
15533       case MachinePlaysBlack:
15534       case TwoMachinesPlay:
15535       case IcsPlayingWhite:
15536       case IcsPlayingBlack:
15537       case AnalyzeMode:
15538       case AnalyzeFile:
15539         /* Skip if we know it isn't thinking */
15540         if (!cps->maybeThinking) return;
15541         if (appData.debugMode)
15542           fprintf(debugFP, "Interrupting %s\n", cps->which);
15543         InterruptChildProcess(cps->pr);
15544         cps->maybeThinking = FALSE;
15545         break;
15546       default:
15547         break;
15548     }
15549 #endif /*ATTENTION*/
15550 }
15551
15552 int
15553 CheckFlags()
15554 {
15555     if (whiteTimeRemaining <= 0) {
15556         if (!whiteFlag) {
15557             whiteFlag = TRUE;
15558             if (appData.icsActive) {
15559                 if (appData.autoCallFlag &&
15560                     gameMode == IcsPlayingBlack && !blackFlag) {
15561                   SendToICS(ics_prefix);
15562                   SendToICS("flag\n");
15563                 }
15564             } else {
15565                 if (blackFlag) {
15566                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15567                 } else {
15568                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15569                     if (appData.autoCallFlag) {
15570                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15571                         return TRUE;
15572                     }
15573                 }
15574             }
15575         }
15576     }
15577     if (blackTimeRemaining <= 0) {
15578         if (!blackFlag) {
15579             blackFlag = TRUE;
15580             if (appData.icsActive) {
15581                 if (appData.autoCallFlag &&
15582                     gameMode == IcsPlayingWhite && !whiteFlag) {
15583                   SendToICS(ics_prefix);
15584                   SendToICS("flag\n");
15585                 }
15586             } else {
15587                 if (whiteFlag) {
15588                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15589                 } else {
15590                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15591                     if (appData.autoCallFlag) {
15592                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15593                         return TRUE;
15594                     }
15595                 }
15596             }
15597         }
15598     }
15599     return FALSE;
15600 }
15601
15602 void
15603 CheckTimeControl()
15604 {
15605     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15606         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15607
15608     /*
15609      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15610      */
15611     if ( !WhiteOnMove(forwardMostMove) ) {
15612         /* White made time control */
15613         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15614         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15615         /* [HGM] time odds: correct new time quota for time odds! */
15616                                             / WhitePlayer()->timeOdds;
15617         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15618     } else {
15619         lastBlack -= blackTimeRemaining;
15620         /* Black made time control */
15621         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15622                                             / WhitePlayer()->other->timeOdds;
15623         lastWhite = whiteTimeRemaining;
15624     }
15625 }
15626
15627 void
15628 DisplayBothClocks()
15629 {
15630     int wom = gameMode == EditPosition ?
15631       !blackPlaysFirst : WhiteOnMove(currentMove);
15632     DisplayWhiteClock(whiteTimeRemaining, wom);
15633     DisplayBlackClock(blackTimeRemaining, !wom);
15634 }
15635
15636
15637 /* Timekeeping seems to be a portability nightmare.  I think everyone
15638    has ftime(), but I'm really not sure, so I'm including some ifdefs
15639    to use other calls if you don't.  Clocks will be less accurate if
15640    you have neither ftime nor gettimeofday.
15641 */
15642
15643 /* VS 2008 requires the #include outside of the function */
15644 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15645 #include <sys/timeb.h>
15646 #endif
15647
15648 /* Get the current time as a TimeMark */
15649 void
15650 GetTimeMark(tm)
15651      TimeMark *tm;
15652 {
15653 #if HAVE_GETTIMEOFDAY
15654
15655     struct timeval timeVal;
15656     struct timezone timeZone;
15657
15658     gettimeofday(&timeVal, &timeZone);
15659     tm->sec = (long) timeVal.tv_sec;
15660     tm->ms = (int) (timeVal.tv_usec / 1000L);
15661
15662 #else /*!HAVE_GETTIMEOFDAY*/
15663 #if HAVE_FTIME
15664
15665 // include <sys/timeb.h> / moved to just above start of function
15666     struct timeb timeB;
15667
15668     ftime(&timeB);
15669     tm->sec = (long) timeB.time;
15670     tm->ms = (int) timeB.millitm;
15671
15672 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15673     tm->sec = (long) time(NULL);
15674     tm->ms = 0;
15675 #endif
15676 #endif
15677 }
15678
15679 /* Return the difference in milliseconds between two
15680    time marks.  We assume the difference will fit in a long!
15681 */
15682 long
15683 SubtractTimeMarks(tm2, tm1)
15684      TimeMark *tm2, *tm1;
15685 {
15686     return 1000L*(tm2->sec - tm1->sec) +
15687            (long) (tm2->ms - tm1->ms);
15688 }
15689
15690
15691 /*
15692  * Code to manage the game clocks.
15693  *
15694  * In tournament play, black starts the clock and then white makes a move.
15695  * We give the human user a slight advantage if he is playing white---the
15696  * clocks don't run until he makes his first move, so it takes zero time.
15697  * Also, we don't account for network lag, so we could get out of sync
15698  * with GNU Chess's clock -- but then, referees are always right.
15699  */
15700
15701 static TimeMark tickStartTM;
15702 static long intendedTickLength;
15703
15704 long
15705 NextTickLength(timeRemaining)
15706      long timeRemaining;
15707 {
15708     long nominalTickLength, nextTickLength;
15709
15710     if (timeRemaining > 0L && timeRemaining <= 10000L)
15711       nominalTickLength = 100L;
15712     else
15713       nominalTickLength = 1000L;
15714     nextTickLength = timeRemaining % nominalTickLength;
15715     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15716
15717     return nextTickLength;
15718 }
15719
15720 /* Adjust clock one minute up or down */
15721 void
15722 AdjustClock(Boolean which, int dir)
15723 {
15724     if(which) blackTimeRemaining += 60000*dir;
15725     else      whiteTimeRemaining += 60000*dir;
15726     DisplayBothClocks();
15727 }
15728
15729 /* Stop clocks and reset to a fresh time control */
15730 void
15731 ResetClocks()
15732 {
15733     (void) StopClockTimer();
15734     if (appData.icsActive) {
15735         whiteTimeRemaining = blackTimeRemaining = 0;
15736     } else if (searchTime) {
15737         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15738         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15739     } else { /* [HGM] correct new time quote for time odds */
15740         whiteTC = blackTC = fullTimeControlString;
15741         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15742         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15743     }
15744     if (whiteFlag || blackFlag) {
15745         DisplayTitle("");
15746         whiteFlag = blackFlag = FALSE;
15747     }
15748     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15749     DisplayBothClocks();
15750 }
15751
15752 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15753
15754 /* Decrement running clock by amount of time that has passed */
15755 void
15756 DecrementClocks()
15757 {
15758     long timeRemaining;
15759     long lastTickLength, fudge;
15760     TimeMark now;
15761
15762     if (!appData.clockMode) return;
15763     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15764
15765     GetTimeMark(&now);
15766
15767     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15768
15769     /* Fudge if we woke up a little too soon */
15770     fudge = intendedTickLength - lastTickLength;
15771     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15772
15773     if (WhiteOnMove(forwardMostMove)) {
15774         if(whiteNPS >= 0) lastTickLength = 0;
15775         timeRemaining = whiteTimeRemaining -= lastTickLength;
15776         if(timeRemaining < 0 && !appData.icsActive) {
15777             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15778             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15779                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15780                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15781             }
15782         }
15783         DisplayWhiteClock(whiteTimeRemaining - fudge,
15784                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15785     } else {
15786         if(blackNPS >= 0) lastTickLength = 0;
15787         timeRemaining = blackTimeRemaining -= lastTickLength;
15788         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15789             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15790             if(suddenDeath) {
15791                 blackStartMove = forwardMostMove;
15792                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15793             }
15794         }
15795         DisplayBlackClock(blackTimeRemaining - fudge,
15796                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15797     }
15798     if (CheckFlags()) return;
15799
15800     tickStartTM = now;
15801     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15802     StartClockTimer(intendedTickLength);
15803
15804     /* if the time remaining has fallen below the alarm threshold, sound the
15805      * alarm. if the alarm has sounded and (due to a takeback or time control
15806      * with increment) the time remaining has increased to a level above the
15807      * threshold, reset the alarm so it can sound again.
15808      */
15809
15810     if (appData.icsActive && appData.icsAlarm) {
15811
15812         /* make sure we are dealing with the user's clock */
15813         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15814                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15815            )) return;
15816
15817         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15818             alarmSounded = FALSE;
15819         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15820             PlayAlarmSound();
15821             alarmSounded = TRUE;
15822         }
15823     }
15824 }
15825
15826
15827 /* A player has just moved, so stop the previously running
15828    clock and (if in clock mode) start the other one.
15829    We redisplay both clocks in case we're in ICS mode, because
15830    ICS gives us an update to both clocks after every move.
15831    Note that this routine is called *after* forwardMostMove
15832    is updated, so the last fractional tick must be subtracted
15833    from the color that is *not* on move now.
15834 */
15835 void
15836 SwitchClocks(int newMoveNr)
15837 {
15838     long lastTickLength;
15839     TimeMark now;
15840     int flagged = FALSE;
15841
15842     GetTimeMark(&now);
15843
15844     if (StopClockTimer() && appData.clockMode) {
15845         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15846         if (!WhiteOnMove(forwardMostMove)) {
15847             if(blackNPS >= 0) lastTickLength = 0;
15848             blackTimeRemaining -= lastTickLength;
15849            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15850 //         if(pvInfoList[forwardMostMove].time == -1)
15851                  pvInfoList[forwardMostMove].time =               // use GUI time
15852                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15853         } else {
15854            if(whiteNPS >= 0) lastTickLength = 0;
15855            whiteTimeRemaining -= lastTickLength;
15856            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15857 //         if(pvInfoList[forwardMostMove].time == -1)
15858                  pvInfoList[forwardMostMove].time =
15859                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15860         }
15861         flagged = CheckFlags();
15862     }
15863     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15864     CheckTimeControl();
15865
15866     if (flagged || !appData.clockMode) return;
15867
15868     switch (gameMode) {
15869       case MachinePlaysBlack:
15870       case MachinePlaysWhite:
15871       case BeginningOfGame:
15872         if (pausing) return;
15873         break;
15874
15875       case EditGame:
15876       case PlayFromGameFile:
15877       case IcsExamining:
15878         return;
15879
15880       default:
15881         break;
15882     }
15883
15884     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15885         if(WhiteOnMove(forwardMostMove))
15886              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15887         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15888     }
15889
15890     tickStartTM = now;
15891     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15892       whiteTimeRemaining : blackTimeRemaining);
15893     StartClockTimer(intendedTickLength);
15894 }
15895
15896
15897 /* Stop both clocks */
15898 void
15899 StopClocks()
15900 {
15901     long lastTickLength;
15902     TimeMark now;
15903
15904     if (!StopClockTimer()) return;
15905     if (!appData.clockMode) return;
15906
15907     GetTimeMark(&now);
15908
15909     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15910     if (WhiteOnMove(forwardMostMove)) {
15911         if(whiteNPS >= 0) lastTickLength = 0;
15912         whiteTimeRemaining -= lastTickLength;
15913         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15914     } else {
15915         if(blackNPS >= 0) lastTickLength = 0;
15916         blackTimeRemaining -= lastTickLength;
15917         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15918     }
15919     CheckFlags();
15920 }
15921
15922 /* Start clock of player on move.  Time may have been reset, so
15923    if clock is already running, stop and restart it. */
15924 void
15925 StartClocks()
15926 {
15927     (void) StopClockTimer(); /* in case it was running already */
15928     DisplayBothClocks();
15929     if (CheckFlags()) return;
15930
15931     if (!appData.clockMode) return;
15932     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15933
15934     GetTimeMark(&tickStartTM);
15935     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15936       whiteTimeRemaining : blackTimeRemaining);
15937
15938    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15939     whiteNPS = blackNPS = -1;
15940     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15941        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15942         whiteNPS = first.nps;
15943     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15944        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15945         blackNPS = first.nps;
15946     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15947         whiteNPS = second.nps;
15948     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15949         blackNPS = second.nps;
15950     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15951
15952     StartClockTimer(intendedTickLength);
15953 }
15954
15955 char *
15956 TimeString(ms)
15957      long ms;
15958 {
15959     long second, minute, hour, day;
15960     char *sign = "";
15961     static char buf[32];
15962
15963     if (ms > 0 && ms <= 9900) {
15964       /* convert milliseconds to tenths, rounding up */
15965       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15966
15967       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15968       return buf;
15969     }
15970
15971     /* convert milliseconds to seconds, rounding up */
15972     /* use floating point to avoid strangeness of integer division
15973        with negative dividends on many machines */
15974     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15975
15976     if (second < 0) {
15977         sign = "-";
15978         second = -second;
15979     }
15980
15981     day = second / (60 * 60 * 24);
15982     second = second % (60 * 60 * 24);
15983     hour = second / (60 * 60);
15984     second = second % (60 * 60);
15985     minute = second / 60;
15986     second = second % 60;
15987
15988     if (day > 0)
15989       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15990               sign, day, hour, minute, second);
15991     else if (hour > 0)
15992       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15993     else
15994       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15995
15996     return buf;
15997 }
15998
15999
16000 /*
16001  * This is necessary because some C libraries aren't ANSI C compliant yet.
16002  */
16003 char *
16004 StrStr(string, match)
16005      char *string, *match;
16006 {
16007     int i, length;
16008
16009     length = strlen(match);
16010
16011     for (i = strlen(string) - length; i >= 0; i--, string++)
16012       if (!strncmp(match, string, length))
16013         return string;
16014
16015     return NULL;
16016 }
16017
16018 char *
16019 StrCaseStr(string, match)
16020      char *string, *match;
16021 {
16022     int i, j, length;
16023
16024     length = strlen(match);
16025
16026     for (i = strlen(string) - length; i >= 0; i--, string++) {
16027         for (j = 0; j < length; j++) {
16028             if (ToLower(match[j]) != ToLower(string[j]))
16029               break;
16030         }
16031         if (j == length) return string;
16032     }
16033
16034     return NULL;
16035 }
16036
16037 #ifndef _amigados
16038 int
16039 StrCaseCmp(s1, s2)
16040      char *s1, *s2;
16041 {
16042     char c1, c2;
16043
16044     for (;;) {
16045         c1 = ToLower(*s1++);
16046         c2 = ToLower(*s2++);
16047         if (c1 > c2) return 1;
16048         if (c1 < c2) return -1;
16049         if (c1 == NULLCHAR) return 0;
16050     }
16051 }
16052
16053
16054 int
16055 ToLower(c)
16056      int c;
16057 {
16058     return isupper(c) ? tolower(c) : c;
16059 }
16060
16061
16062 int
16063 ToUpper(c)
16064      int c;
16065 {
16066     return islower(c) ? toupper(c) : c;
16067 }
16068 #endif /* !_amigados    */
16069
16070 char *
16071 StrSave(s)
16072      char *s;
16073 {
16074   char *ret;
16075
16076   if ((ret = (char *) malloc(strlen(s) + 1)))
16077     {
16078       safeStrCpy(ret, s, strlen(s)+1);
16079     }
16080   return ret;
16081 }
16082
16083 char *
16084 StrSavePtr(s, savePtr)
16085      char *s, **savePtr;
16086 {
16087     if (*savePtr) {
16088         free(*savePtr);
16089     }
16090     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16091       safeStrCpy(*savePtr, s, strlen(s)+1);
16092     }
16093     return(*savePtr);
16094 }
16095
16096 char *
16097 PGNDate()
16098 {
16099     time_t clock;
16100     struct tm *tm;
16101     char buf[MSG_SIZ];
16102
16103     clock = time((time_t *)NULL);
16104     tm = localtime(&clock);
16105     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16106             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16107     return StrSave(buf);
16108 }
16109
16110
16111 char *
16112 PositionToFEN(move, overrideCastling)
16113      int move;
16114      char *overrideCastling;
16115 {
16116     int i, j, fromX, fromY, toX, toY;
16117     int whiteToPlay;
16118     char buf[MSG_SIZ];
16119     char *p, *q;
16120     int emptycount;
16121     ChessSquare piece;
16122
16123     whiteToPlay = (gameMode == EditPosition) ?
16124       !blackPlaysFirst : (move % 2 == 0);
16125     p = buf;
16126
16127     /* Piece placement data */
16128     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16129         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16130         emptycount = 0;
16131         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16132             if (boards[move][i][j] == EmptySquare) {
16133                 emptycount++;
16134             } else { ChessSquare piece = boards[move][i][j];
16135                 if (emptycount > 0) {
16136                     if(emptycount<10) /* [HGM] can be >= 10 */
16137                         *p++ = '0' + emptycount;
16138                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16139                     emptycount = 0;
16140                 }
16141                 if(PieceToChar(piece) == '+') {
16142                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16143                     *p++ = '+';
16144                     piece = (ChessSquare)(DEMOTED piece);
16145                 }
16146                 *p++ = PieceToChar(piece);
16147                 if(p[-1] == '~') {
16148                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16149                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16150                     *p++ = '~';
16151                 }
16152             }
16153         }
16154         if (emptycount > 0) {
16155             if(emptycount<10) /* [HGM] can be >= 10 */
16156                 *p++ = '0' + emptycount;
16157             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16158             emptycount = 0;
16159         }
16160         *p++ = '/';
16161     }
16162     *(p - 1) = ' ';
16163
16164     /* [HGM] print Crazyhouse or Shogi holdings */
16165     if( gameInfo.holdingsWidth ) {
16166         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16167         q = p;
16168         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16169             piece = boards[move][i][BOARD_WIDTH-1];
16170             if( piece != EmptySquare )
16171               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16172                   *p++ = PieceToChar(piece);
16173         }
16174         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16175             piece = boards[move][BOARD_HEIGHT-i-1][0];
16176             if( piece != EmptySquare )
16177               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16178                   *p++ = PieceToChar(piece);
16179         }
16180
16181         if( q == p ) *p++ = '-';
16182         *p++ = ']';
16183         *p++ = ' ';
16184     }
16185
16186     /* Active color */
16187     *p++ = whiteToPlay ? 'w' : 'b';
16188     *p++ = ' ';
16189
16190   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16191     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16192   } else {
16193   if(nrCastlingRights) {
16194      q = p;
16195      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16196        /* [HGM] write directly from rights */
16197            if(boards[move][CASTLING][2] != NoRights &&
16198               boards[move][CASTLING][0] != NoRights   )
16199                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16200            if(boards[move][CASTLING][2] != NoRights &&
16201               boards[move][CASTLING][1] != NoRights   )
16202                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16203            if(boards[move][CASTLING][5] != NoRights &&
16204               boards[move][CASTLING][3] != NoRights   )
16205                 *p++ = boards[move][CASTLING][3] + AAA;
16206            if(boards[move][CASTLING][5] != NoRights &&
16207               boards[move][CASTLING][4] != NoRights   )
16208                 *p++ = boards[move][CASTLING][4] + AAA;
16209      } else {
16210
16211         /* [HGM] write true castling rights */
16212         if( nrCastlingRights == 6 ) {
16213             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16214                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16215             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16216                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16217             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16218                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16219             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16220                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16221         }
16222      }
16223      if (q == p) *p++ = '-'; /* No castling rights */
16224      *p++ = ' ';
16225   }
16226
16227   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16228      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16229     /* En passant target square */
16230     if (move > backwardMostMove) {
16231         fromX = moveList[move - 1][0] - AAA;
16232         fromY = moveList[move - 1][1] - ONE;
16233         toX = moveList[move - 1][2] - AAA;
16234         toY = moveList[move - 1][3] - ONE;
16235         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16236             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16237             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16238             fromX == toX) {
16239             /* 2-square pawn move just happened */
16240             *p++ = toX + AAA;
16241             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16242         } else {
16243             *p++ = '-';
16244         }
16245     } else if(move == backwardMostMove) {
16246         // [HGM] perhaps we should always do it like this, and forget the above?
16247         if((signed char)boards[move][EP_STATUS] >= 0) {
16248             *p++ = boards[move][EP_STATUS] + AAA;
16249             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16250         } else {
16251             *p++ = '-';
16252         }
16253     } else {
16254         *p++ = '-';
16255     }
16256     *p++ = ' ';
16257   }
16258   }
16259
16260     /* [HGM] find reversible plies */
16261     {   int i = 0, j=move;
16262
16263         if (appData.debugMode) { int k;
16264             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16265             for(k=backwardMostMove; k<=forwardMostMove; k++)
16266                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16267
16268         }
16269
16270         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16271         if( j == backwardMostMove ) i += initialRulePlies;
16272         sprintf(p, "%d ", i);
16273         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16274     }
16275     /* Fullmove number */
16276     sprintf(p, "%d", (move / 2) + 1);
16277
16278     return StrSave(buf);
16279 }
16280
16281 Boolean
16282 ParseFEN(board, blackPlaysFirst, fen)
16283     Board board;
16284      int *blackPlaysFirst;
16285      char *fen;
16286 {
16287     int i, j;
16288     char *p, c;
16289     int emptycount;
16290     ChessSquare piece;
16291
16292     p = fen;
16293
16294     /* [HGM] by default clear Crazyhouse holdings, if present */
16295     if(gameInfo.holdingsWidth) {
16296        for(i=0; i<BOARD_HEIGHT; i++) {
16297            board[i][0]             = EmptySquare; /* black holdings */
16298            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16299            board[i][1]             = (ChessSquare) 0; /* black counts */
16300            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16301        }
16302     }
16303
16304     /* Piece placement data */
16305     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16306         j = 0;
16307         for (;;) {
16308             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16309                 if (*p == '/') p++;
16310                 emptycount = gameInfo.boardWidth - j;
16311                 while (emptycount--)
16312                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16313                 break;
16314 #if(BOARD_FILES >= 10)
16315             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16316                 p++; emptycount=10;
16317                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16318                 while (emptycount--)
16319                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16320 #endif
16321             } else if (isdigit(*p)) {
16322                 emptycount = *p++ - '0';
16323                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16324                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16325                 while (emptycount--)
16326                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16327             } else if (*p == '+' || isalpha(*p)) {
16328                 if (j >= gameInfo.boardWidth) return FALSE;
16329                 if(*p=='+') {
16330                     piece = CharToPiece(*++p);
16331                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16332                     piece = (ChessSquare) (PROMOTED piece ); p++;
16333                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16334                 } else piece = CharToPiece(*p++);
16335
16336                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16337                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16338                     piece = (ChessSquare) (PROMOTED piece);
16339                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16340                     p++;
16341                 }
16342                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16343             } else {
16344                 return FALSE;
16345             }
16346         }
16347     }
16348     while (*p == '/' || *p == ' ') p++;
16349
16350     /* [HGM] look for Crazyhouse holdings here */
16351     while(*p==' ') p++;
16352     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16353         if(*p == '[') p++;
16354         if(*p == '-' ) p++; /* empty holdings */ else {
16355             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16356             /* if we would allow FEN reading to set board size, we would   */
16357             /* have to add holdings and shift the board read so far here   */
16358             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16359                 p++;
16360                 if((int) piece >= (int) BlackPawn ) {
16361                     i = (int)piece - (int)BlackPawn;
16362                     i = PieceToNumber((ChessSquare)i);
16363                     if( i >= gameInfo.holdingsSize ) return FALSE;
16364                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16365                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16366                 } else {
16367                     i = (int)piece - (int)WhitePawn;
16368                     i = PieceToNumber((ChessSquare)i);
16369                     if( i >= gameInfo.holdingsSize ) return FALSE;
16370                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16371                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16372                 }
16373             }
16374         }
16375         if(*p == ']') p++;
16376     }
16377
16378     while(*p == ' ') p++;
16379
16380     /* Active color */
16381     c = *p++;
16382     if(appData.colorNickNames) {
16383       if( c == appData.colorNickNames[0] ) c = 'w'; else
16384       if( c == appData.colorNickNames[1] ) c = 'b';
16385     }
16386     switch (c) {
16387       case 'w':
16388         *blackPlaysFirst = FALSE;
16389         break;
16390       case 'b':
16391         *blackPlaysFirst = TRUE;
16392         break;
16393       default:
16394         return FALSE;
16395     }
16396
16397     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16398     /* return the extra info in global variiables             */
16399
16400     /* set defaults in case FEN is incomplete */
16401     board[EP_STATUS] = EP_UNKNOWN;
16402     for(i=0; i<nrCastlingRights; i++ ) {
16403         board[CASTLING][i] =
16404             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16405     }   /* assume possible unless obviously impossible */
16406     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16407     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16408     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16409                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16410     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16411     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16412     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16413                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16414     FENrulePlies = 0;
16415
16416     while(*p==' ') p++;
16417     if(nrCastlingRights) {
16418       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16419           /* castling indicator present, so default becomes no castlings */
16420           for(i=0; i<nrCastlingRights; i++ ) {
16421                  board[CASTLING][i] = NoRights;
16422           }
16423       }
16424       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16425              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16426              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16427              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16428         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16429
16430         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16431             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16432             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16433         }
16434         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16435             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16436         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16437                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16438         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16439                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16440         switch(c) {
16441           case'K':
16442               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16443               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16444               board[CASTLING][2] = whiteKingFile;
16445               break;
16446           case'Q':
16447               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16448               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16449               board[CASTLING][2] = whiteKingFile;
16450               break;
16451           case'k':
16452               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16453               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16454               board[CASTLING][5] = blackKingFile;
16455               break;
16456           case'q':
16457               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16458               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16459               board[CASTLING][5] = blackKingFile;
16460           case '-':
16461               break;
16462           default: /* FRC castlings */
16463               if(c >= 'a') { /* black rights */
16464                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16465                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16466                   if(i == BOARD_RGHT) break;
16467                   board[CASTLING][5] = i;
16468                   c -= AAA;
16469                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16470                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16471                   if(c > i)
16472                       board[CASTLING][3] = c;
16473                   else
16474                       board[CASTLING][4] = c;
16475               } else { /* white rights */
16476                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16477                     if(board[0][i] == WhiteKing) break;
16478                   if(i == BOARD_RGHT) break;
16479                   board[CASTLING][2] = i;
16480                   c -= AAA - 'a' + 'A';
16481                   if(board[0][c] >= WhiteKing) break;
16482                   if(c > i)
16483                       board[CASTLING][0] = c;
16484                   else
16485                       board[CASTLING][1] = c;
16486               }
16487         }
16488       }
16489       for(i=0; i<nrCastlingRights; i++)
16490         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16491     if (appData.debugMode) {
16492         fprintf(debugFP, "FEN castling rights:");
16493         for(i=0; i<nrCastlingRights; i++)
16494         fprintf(debugFP, " %d", board[CASTLING][i]);
16495         fprintf(debugFP, "\n");
16496     }
16497
16498       while(*p==' ') p++;
16499     }
16500
16501     /* read e.p. field in games that know e.p. capture */
16502     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16503        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16504       if(*p=='-') {
16505         p++; board[EP_STATUS] = EP_NONE;
16506       } else {
16507          char c = *p++ - AAA;
16508
16509          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16510          if(*p >= '0' && *p <='9') p++;
16511          board[EP_STATUS] = c;
16512       }
16513     }
16514
16515
16516     if(sscanf(p, "%d", &i) == 1) {
16517         FENrulePlies = i; /* 50-move ply counter */
16518         /* (The move number is still ignored)    */
16519     }
16520
16521     return TRUE;
16522 }
16523
16524 void
16525 EditPositionPasteFEN(char *fen)
16526 {
16527   if (fen != NULL) {
16528     Board initial_position;
16529
16530     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16531       DisplayError(_("Bad FEN position in clipboard"), 0);
16532       return ;
16533     } else {
16534       int savedBlackPlaysFirst = blackPlaysFirst;
16535       EditPositionEvent();
16536       blackPlaysFirst = savedBlackPlaysFirst;
16537       CopyBoard(boards[0], initial_position);
16538       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16539       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16540       DisplayBothClocks();
16541       DrawPosition(FALSE, boards[currentMove]);
16542     }
16543   }
16544 }
16545
16546 static char cseq[12] = "\\   ";
16547
16548 Boolean set_cont_sequence(char *new_seq)
16549 {
16550     int len;
16551     Boolean ret;
16552
16553     // handle bad attempts to set the sequence
16554         if (!new_seq)
16555                 return 0; // acceptable error - no debug
16556
16557     len = strlen(new_seq);
16558     ret = (len > 0) && (len < sizeof(cseq));
16559     if (ret)
16560       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16561     else if (appData.debugMode)
16562       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16563     return ret;
16564 }
16565
16566 /*
16567     reformat a source message so words don't cross the width boundary.  internal
16568     newlines are not removed.  returns the wrapped size (no null character unless
16569     included in source message).  If dest is NULL, only calculate the size required
16570     for the dest buffer.  lp argument indicats line position upon entry, and it's
16571     passed back upon exit.
16572 */
16573 int wrap(char *dest, char *src, int count, int width, int *lp)
16574 {
16575     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16576
16577     cseq_len = strlen(cseq);
16578     old_line = line = *lp;
16579     ansi = len = clen = 0;
16580
16581     for (i=0; i < count; i++)
16582     {
16583         if (src[i] == '\033')
16584             ansi = 1;
16585
16586         // if we hit the width, back up
16587         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16588         {
16589             // store i & len in case the word is too long
16590             old_i = i, old_len = len;
16591
16592             // find the end of the last word
16593             while (i && src[i] != ' ' && src[i] != '\n')
16594             {
16595                 i--;
16596                 len--;
16597             }
16598
16599             // word too long?  restore i & len before splitting it
16600             if ((old_i-i+clen) >= width)
16601             {
16602                 i = old_i;
16603                 len = old_len;
16604             }
16605
16606             // extra space?
16607             if (i && src[i-1] == ' ')
16608                 len--;
16609
16610             if (src[i] != ' ' && src[i] != '\n')
16611             {
16612                 i--;
16613                 if (len)
16614                     len--;
16615             }
16616
16617             // now append the newline and continuation sequence
16618             if (dest)
16619                 dest[len] = '\n';
16620             len++;
16621             if (dest)
16622                 strncpy(dest+len, cseq, cseq_len);
16623             len += cseq_len;
16624             line = cseq_len;
16625             clen = cseq_len;
16626             continue;
16627         }
16628
16629         if (dest)
16630             dest[len] = src[i];
16631         len++;
16632         if (!ansi)
16633             line++;
16634         if (src[i] == '\n')
16635             line = 0;
16636         if (src[i] == 'm')
16637             ansi = 0;
16638     }
16639     if (dest && appData.debugMode)
16640     {
16641         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16642             count, width, line, len, *lp);
16643         show_bytes(debugFP, src, count);
16644         fprintf(debugFP, "\ndest: ");
16645         show_bytes(debugFP, dest, len);
16646         fprintf(debugFP, "\n");
16647     }
16648     *lp = dest ? line : old_line;
16649
16650     return len;
16651 }
16652
16653 // [HGM] vari: routines for shelving variations
16654 Boolean modeRestore = FALSE;
16655
16656 void
16657 PushInner(int firstMove, int lastMove)
16658 {
16659         int i, j, nrMoves = lastMove - firstMove;
16660
16661         // push current tail of game on stack
16662         savedResult[storedGames] = gameInfo.result;
16663         savedDetails[storedGames] = gameInfo.resultDetails;
16664         gameInfo.resultDetails = NULL;
16665         savedFirst[storedGames] = firstMove;
16666         savedLast [storedGames] = lastMove;
16667         savedFramePtr[storedGames] = framePtr;
16668         framePtr -= nrMoves; // reserve space for the boards
16669         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16670             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16671             for(j=0; j<MOVE_LEN; j++)
16672                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16673             for(j=0; j<2*MOVE_LEN; j++)
16674                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16675             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16676             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16677             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16678             pvInfoList[firstMove+i-1].depth = 0;
16679             commentList[framePtr+i] = commentList[firstMove+i];
16680             commentList[firstMove+i] = NULL;
16681         }
16682
16683         storedGames++;
16684         forwardMostMove = firstMove; // truncate game so we can start variation
16685 }
16686
16687 void
16688 PushTail(int firstMove, int lastMove)
16689 {
16690         if(appData.icsActive) { // only in local mode
16691                 forwardMostMove = currentMove; // mimic old ICS behavior
16692                 return;
16693         }
16694         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16695
16696         PushInner(firstMove, lastMove);
16697         if(storedGames == 1) GreyRevert(FALSE);
16698         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16699 }
16700
16701 void
16702 PopInner(Boolean annotate)
16703 {
16704         int i, j, nrMoves;
16705         char buf[8000], moveBuf[20];
16706
16707         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16708         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16709         nrMoves = savedLast[storedGames] - currentMove;
16710         if(annotate) {
16711                 int cnt = 10;
16712                 if(!WhiteOnMove(currentMove))
16713                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16714                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16715                 for(i=currentMove; i<forwardMostMove; i++) {
16716                         if(WhiteOnMove(i))
16717                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16718                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16719                         strcat(buf, moveBuf);
16720                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16721                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16722                 }
16723                 strcat(buf, ")");
16724         }
16725         for(i=1; i<=nrMoves; i++) { // copy last variation back
16726             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16727             for(j=0; j<MOVE_LEN; j++)
16728                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16729             for(j=0; j<2*MOVE_LEN; j++)
16730                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16731             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16732             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16733             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16734             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16735             commentList[currentMove+i] = commentList[framePtr+i];
16736             commentList[framePtr+i] = NULL;
16737         }
16738         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16739         framePtr = savedFramePtr[storedGames];
16740         gameInfo.result = savedResult[storedGames];
16741         if(gameInfo.resultDetails != NULL) {
16742             free(gameInfo.resultDetails);
16743       }
16744         gameInfo.resultDetails = savedDetails[storedGames];
16745         forwardMostMove = currentMove + nrMoves;
16746 }
16747
16748 Boolean
16749 PopTail(Boolean annotate)
16750 {
16751         if(appData.icsActive) return FALSE; // only in local mode
16752         if(!storedGames) return FALSE; // sanity
16753         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16754
16755         PopInner(annotate);
16756         if(currentMove < forwardMostMove) ForwardEvent(); else
16757         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16758
16759         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16760         return TRUE;
16761 }
16762
16763 void
16764 CleanupTail()
16765 {       // remove all shelved variations
16766         int i;
16767         for(i=0; i<storedGames; i++) {
16768             if(savedDetails[i])
16769                 free(savedDetails[i]);
16770             savedDetails[i] = NULL;
16771         }
16772         for(i=framePtr; i<MAX_MOVES; i++) {
16773                 if(commentList[i]) free(commentList[i]);
16774                 commentList[i] = NULL;
16775         }
16776         framePtr = MAX_MOVES-1;
16777         storedGames = 0;
16778 }
16779
16780 void
16781 LoadVariation(int index, char *text)
16782 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16783         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16784         int level = 0, move;
16785
16786         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16787         // first find outermost bracketing variation
16788         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16789             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16790                 if(*p == '{') wait = '}'; else
16791                 if(*p == '[') wait = ']'; else
16792                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16793                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16794             }
16795             if(*p == wait) wait = NULLCHAR; // closing ]} found
16796             p++;
16797         }
16798         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16799         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16800         end[1] = NULLCHAR; // clip off comment beyond variation
16801         ToNrEvent(currentMove-1);
16802         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16803         // kludge: use ParsePV() to append variation to game
16804         move = currentMove;
16805         ParsePV(start, TRUE, TRUE);
16806         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16807         ClearPremoveHighlights();
16808         CommentPopDown();
16809         ToNrEvent(currentMove+1);
16810 }
16811