Add -afterGame option
[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             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10441             ReserveGame(nextGame, resChar); // sets nextGame
10442             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10443             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10444         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10445
10446         if (nextGame <= appData.matchGames && !abortMatch) {
10447             gameMode = nextGameMode;
10448             matchGame = nextGame; // this will be overruled in tourney mode!
10449             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10450             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10451             endingGame = 0; /* [HGM] crash */
10452             return;
10453         } else {
10454             gameMode = nextGameMode;
10455             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10456                      first.tidy, second.tidy,
10457                      first.matchWins, second.matchWins,
10458                      appData.matchGames - (first.matchWins + second.matchWins));
10459             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10460             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10461             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10462                 first.twoMachinesColor = "black\n";
10463                 second.twoMachinesColor = "white\n";
10464             } else {
10465                 first.twoMachinesColor = "white\n";
10466                 second.twoMachinesColor = "black\n";
10467             }
10468         }
10469     }
10470     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10471         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10472       ExitAnalyzeMode();
10473     gameMode = nextGameMode;
10474     ModeHighlight();
10475     endingGame = 0;  /* [HGM] crash */
10476     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10477         if(matchMode == TRUE) { // match through command line: exit with or without popup
10478             if(ranking) {
10479                 ToNrEvent(forwardMostMove);
10480                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10481                 else ExitEvent(0);
10482             } else DisplayFatalError(buf, 0, 0);
10483         } else { // match through menu; just stop, with or without popup
10484             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10485             ModeHighlight();
10486             if(ranking){
10487                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10488             } else DisplayNote(buf);
10489       }
10490       if(ranking) free(ranking);
10491     }
10492 }
10493
10494 /* Assumes program was just initialized (initString sent).
10495    Leaves program in force mode. */
10496 void
10497 FeedMovesToProgram(cps, upto)
10498      ChessProgramState *cps;
10499      int upto;
10500 {
10501     int i;
10502
10503     if (appData.debugMode)
10504       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10505               startedFromSetupPosition ? "position and " : "",
10506               backwardMostMove, upto, cps->which);
10507     if(currentlyInitializedVariant != gameInfo.variant) {
10508       char buf[MSG_SIZ];
10509         // [HGM] variantswitch: make engine aware of new variant
10510         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10511                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10512         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10513         SendToProgram(buf, cps);
10514         currentlyInitializedVariant = gameInfo.variant;
10515     }
10516     SendToProgram("force\n", cps);
10517     if (startedFromSetupPosition) {
10518         SendBoard(cps, backwardMostMove);
10519     if (appData.debugMode) {
10520         fprintf(debugFP, "feedMoves\n");
10521     }
10522     }
10523     for (i = backwardMostMove; i < upto; i++) {
10524         SendMoveToProgram(i, cps);
10525     }
10526 }
10527
10528
10529 int
10530 ResurrectChessProgram()
10531 {
10532      /* The chess program may have exited.
10533         If so, restart it and feed it all the moves made so far. */
10534     static int doInit = 0;
10535
10536     if (appData.noChessProgram) return 1;
10537
10538     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10539         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10540         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10541         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10542     } else {
10543         if (first.pr != NoProc) return 1;
10544         StartChessProgram(&first);
10545     }
10546     InitChessProgram(&first, FALSE);
10547     FeedMovesToProgram(&first, currentMove);
10548
10549     if (!first.sendTime) {
10550         /* can't tell gnuchess what its clock should read,
10551            so we bow to its notion. */
10552         ResetClocks();
10553         timeRemaining[0][currentMove] = whiteTimeRemaining;
10554         timeRemaining[1][currentMove] = blackTimeRemaining;
10555     }
10556
10557     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10558                 appData.icsEngineAnalyze) && first.analysisSupport) {
10559       SendToProgram("analyze\n", &first);
10560       first.analyzing = TRUE;
10561     }
10562     return 1;
10563 }
10564
10565 /*
10566  * Button procedures
10567  */
10568 void
10569 Reset(redraw, init)
10570      int redraw, init;
10571 {
10572     int i;
10573
10574     if (appData.debugMode) {
10575         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10576                 redraw, init, gameMode);
10577     }
10578     CleanupTail(); // [HGM] vari: delete any stored variations
10579     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10580     pausing = pauseExamInvalid = FALSE;
10581     startedFromSetupPosition = blackPlaysFirst = FALSE;
10582     firstMove = TRUE;
10583     whiteFlag = blackFlag = FALSE;
10584     userOfferedDraw = FALSE;
10585     hintRequested = bookRequested = FALSE;
10586     first.maybeThinking = FALSE;
10587     second.maybeThinking = FALSE;
10588     first.bookSuspend = FALSE; // [HGM] book
10589     second.bookSuspend = FALSE;
10590     thinkOutput[0] = NULLCHAR;
10591     lastHint[0] = NULLCHAR;
10592     ClearGameInfo(&gameInfo);
10593     gameInfo.variant = StringToVariant(appData.variant);
10594     ics_user_moved = ics_clock_paused = FALSE;
10595     ics_getting_history = H_FALSE;
10596     ics_gamenum = -1;
10597     white_holding[0] = black_holding[0] = NULLCHAR;
10598     ClearProgramStats();
10599     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10600
10601     ResetFrontEnd();
10602     ClearHighlights();
10603     flipView = appData.flipView;
10604     ClearPremoveHighlights();
10605     gotPremove = FALSE;
10606     alarmSounded = FALSE;
10607
10608     GameEnds(EndOfFile, NULL, GE_PLAYER);
10609     if(appData.serverMovesName != NULL) {
10610         /* [HGM] prepare to make moves file for broadcasting */
10611         clock_t t = clock();
10612         if(serverMoves != NULL) fclose(serverMoves);
10613         serverMoves = fopen(appData.serverMovesName, "r");
10614         if(serverMoves != NULL) {
10615             fclose(serverMoves);
10616             /* delay 15 sec before overwriting, so all clients can see end */
10617             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10618         }
10619         serverMoves = fopen(appData.serverMovesName, "w");
10620     }
10621
10622     ExitAnalyzeMode();
10623     gameMode = BeginningOfGame;
10624     ModeHighlight();
10625     if(appData.icsActive) gameInfo.variant = VariantNormal;
10626     currentMove = forwardMostMove = backwardMostMove = 0;
10627     InitPosition(redraw);
10628     for (i = 0; i < MAX_MOVES; i++) {
10629         if (commentList[i] != NULL) {
10630             free(commentList[i]);
10631             commentList[i] = NULL;
10632         }
10633     }
10634     ResetClocks();
10635     timeRemaining[0][0] = whiteTimeRemaining;
10636     timeRemaining[1][0] = blackTimeRemaining;
10637
10638     if (first.pr == NULL) {
10639         StartChessProgram(&first);
10640     }
10641     if (init) {
10642             InitChessProgram(&first, startedFromSetupPosition);
10643     }
10644     DisplayTitle("");
10645     DisplayMessage("", "");
10646     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10647     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10648 }
10649
10650 void
10651 AutoPlayGameLoop()
10652 {
10653     for (;;) {
10654         if (!AutoPlayOneMove())
10655           return;
10656         if (matchMode || appData.timeDelay == 0)
10657           continue;
10658         if (appData.timeDelay < 0)
10659           return;
10660         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10661         break;
10662     }
10663 }
10664
10665
10666 int
10667 AutoPlayOneMove()
10668 {
10669     int fromX, fromY, toX, toY;
10670
10671     if (appData.debugMode) {
10672       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10673     }
10674
10675     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10676       return FALSE;
10677
10678     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10679       pvInfoList[currentMove].depth = programStats.depth;
10680       pvInfoList[currentMove].score = programStats.score;
10681       pvInfoList[currentMove].time  = 0;
10682       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10683     }
10684
10685     if (currentMove >= forwardMostMove) {
10686       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10687 //      gameMode = EndOfGame;
10688 //      ModeHighlight();
10689
10690       /* [AS] Clear current move marker at the end of a game */
10691       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10692
10693       return FALSE;
10694     }
10695
10696     toX = moveList[currentMove][2] - AAA;
10697     toY = moveList[currentMove][3] - ONE;
10698
10699     if (moveList[currentMove][1] == '@') {
10700         if (appData.highlightLastMove) {
10701             SetHighlights(-1, -1, toX, toY);
10702         }
10703     } else {
10704         fromX = moveList[currentMove][0] - AAA;
10705         fromY = moveList[currentMove][1] - ONE;
10706
10707         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10708
10709         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10710
10711         if (appData.highlightLastMove) {
10712             SetHighlights(fromX, fromY, toX, toY);
10713         }
10714     }
10715     DisplayMove(currentMove);
10716     SendMoveToProgram(currentMove++, &first);
10717     DisplayBothClocks();
10718     DrawPosition(FALSE, boards[currentMove]);
10719     // [HGM] PV info: always display, routine tests if empty
10720     DisplayComment(currentMove - 1, commentList[currentMove]);
10721     return TRUE;
10722 }
10723
10724
10725 int
10726 LoadGameOneMove(readAhead)
10727      ChessMove readAhead;
10728 {
10729     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10730     char promoChar = NULLCHAR;
10731     ChessMove moveType;
10732     char move[MSG_SIZ];
10733     char *p, *q;
10734
10735     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10736         gameMode != AnalyzeMode && gameMode != Training) {
10737         gameFileFP = NULL;
10738         return FALSE;
10739     }
10740
10741     yyboardindex = forwardMostMove;
10742     if (readAhead != EndOfFile) {
10743       moveType = readAhead;
10744     } else {
10745       if (gameFileFP == NULL)
10746           return FALSE;
10747       moveType = (ChessMove) Myylex();
10748     }
10749
10750     done = FALSE;
10751     switch (moveType) {
10752       case Comment:
10753         if (appData.debugMode)
10754           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10755         p = yy_text;
10756
10757         /* append the comment but don't display it */
10758         AppendComment(currentMove, p, FALSE);
10759         return TRUE;
10760
10761       case WhiteCapturesEnPassant:
10762       case BlackCapturesEnPassant:
10763       case WhitePromotion:
10764       case BlackPromotion:
10765       case WhiteNonPromotion:
10766       case BlackNonPromotion:
10767       case NormalMove:
10768       case WhiteKingSideCastle:
10769       case WhiteQueenSideCastle:
10770       case BlackKingSideCastle:
10771       case BlackQueenSideCastle:
10772       case WhiteKingSideCastleWild:
10773       case WhiteQueenSideCastleWild:
10774       case BlackKingSideCastleWild:
10775       case BlackQueenSideCastleWild:
10776       /* PUSH Fabien */
10777       case WhiteHSideCastleFR:
10778       case WhiteASideCastleFR:
10779       case BlackHSideCastleFR:
10780       case BlackASideCastleFR:
10781       /* POP Fabien */
10782         if (appData.debugMode)
10783           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10784         fromX = currentMoveString[0] - AAA;
10785         fromY = currentMoveString[1] - ONE;
10786         toX = currentMoveString[2] - AAA;
10787         toY = currentMoveString[3] - ONE;
10788         promoChar = currentMoveString[4];
10789         break;
10790
10791       case WhiteDrop:
10792       case BlackDrop:
10793         if (appData.debugMode)
10794           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10795         fromX = moveType == WhiteDrop ?
10796           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10797         (int) CharToPiece(ToLower(currentMoveString[0]));
10798         fromY = DROP_RANK;
10799         toX = currentMoveString[2] - AAA;
10800         toY = currentMoveString[3] - ONE;
10801         break;
10802
10803       case WhiteWins:
10804       case BlackWins:
10805       case GameIsDrawn:
10806       case GameUnfinished:
10807         if (appData.debugMode)
10808           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10809         p = strchr(yy_text, '{');
10810         if (p == NULL) p = strchr(yy_text, '(');
10811         if (p == NULL) {
10812             p = yy_text;
10813             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10814         } else {
10815             q = strchr(p, *p == '{' ? '}' : ')');
10816             if (q != NULL) *q = NULLCHAR;
10817             p++;
10818         }
10819         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10820         GameEnds(moveType, p, GE_FILE);
10821         done = TRUE;
10822         if (cmailMsgLoaded) {
10823             ClearHighlights();
10824             flipView = WhiteOnMove(currentMove);
10825             if (moveType == GameUnfinished) flipView = !flipView;
10826             if (appData.debugMode)
10827               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10828         }
10829         break;
10830
10831       case EndOfFile:
10832         if (appData.debugMode)
10833           fprintf(debugFP, "Parser hit end of file\n");
10834         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10835           case MT_NONE:
10836           case MT_CHECK:
10837             break;
10838           case MT_CHECKMATE:
10839           case MT_STAINMATE:
10840             if (WhiteOnMove(currentMove)) {
10841                 GameEnds(BlackWins, "Black mates", GE_FILE);
10842             } else {
10843                 GameEnds(WhiteWins, "White mates", GE_FILE);
10844             }
10845             break;
10846           case MT_STALEMATE:
10847             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10848             break;
10849         }
10850         done = TRUE;
10851         break;
10852
10853       case MoveNumberOne:
10854         if (lastLoadGameStart == GNUChessGame) {
10855             /* GNUChessGames have numbers, but they aren't move numbers */
10856             if (appData.debugMode)
10857               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10858                       yy_text, (int) moveType);
10859             return LoadGameOneMove(EndOfFile); /* tail recursion */
10860         }
10861         /* else fall thru */
10862
10863       case XBoardGame:
10864       case GNUChessGame:
10865       case PGNTag:
10866         /* Reached start of next game in file */
10867         if (appData.debugMode)
10868           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10869         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10870           case MT_NONE:
10871           case MT_CHECK:
10872             break;
10873           case MT_CHECKMATE:
10874           case MT_STAINMATE:
10875             if (WhiteOnMove(currentMove)) {
10876                 GameEnds(BlackWins, "Black mates", GE_FILE);
10877             } else {
10878                 GameEnds(WhiteWins, "White mates", GE_FILE);
10879             }
10880             break;
10881           case MT_STALEMATE:
10882             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10883             break;
10884         }
10885         done = TRUE;
10886         break;
10887
10888       case PositionDiagram:     /* should not happen; ignore */
10889       case ElapsedTime:         /* ignore */
10890       case NAG:                 /* ignore */
10891         if (appData.debugMode)
10892           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10893                   yy_text, (int) moveType);
10894         return LoadGameOneMove(EndOfFile); /* tail recursion */
10895
10896       case IllegalMove:
10897         if (appData.testLegality) {
10898             if (appData.debugMode)
10899               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10900             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10901                     (forwardMostMove / 2) + 1,
10902                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10903             DisplayError(move, 0);
10904             done = TRUE;
10905         } else {
10906             if (appData.debugMode)
10907               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10908                       yy_text, currentMoveString);
10909             fromX = currentMoveString[0] - AAA;
10910             fromY = currentMoveString[1] - ONE;
10911             toX = currentMoveString[2] - AAA;
10912             toY = currentMoveString[3] - ONE;
10913             promoChar = currentMoveString[4];
10914         }
10915         break;
10916
10917       case AmbiguousMove:
10918         if (appData.debugMode)
10919           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10920         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10921                 (forwardMostMove / 2) + 1,
10922                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10923         DisplayError(move, 0);
10924         done = TRUE;
10925         break;
10926
10927       default:
10928       case ImpossibleMove:
10929         if (appData.debugMode)
10930           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10931         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10932                 (forwardMostMove / 2) + 1,
10933                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10934         DisplayError(move, 0);
10935         done = TRUE;
10936         break;
10937     }
10938
10939     if (done) {
10940         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10941             DrawPosition(FALSE, boards[currentMove]);
10942             DisplayBothClocks();
10943             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10944               DisplayComment(currentMove - 1, commentList[currentMove]);
10945         }
10946         (void) StopLoadGameTimer();
10947         gameFileFP = NULL;
10948         cmailOldMove = forwardMostMove;
10949         return FALSE;
10950     } else {
10951         /* currentMoveString is set as a side-effect of yylex */
10952
10953         thinkOutput[0] = NULLCHAR;
10954         MakeMove(fromX, fromY, toX, toY, promoChar);
10955         currentMove = forwardMostMove;
10956         return TRUE;
10957     }
10958 }
10959
10960 /* Load the nth game from the given file */
10961 int
10962 LoadGameFromFile(filename, n, title, useList)
10963      char *filename;
10964      int n;
10965      char *title;
10966      /*Boolean*/ int useList;
10967 {
10968     FILE *f;
10969     char buf[MSG_SIZ];
10970
10971     if (strcmp(filename, "-") == 0) {
10972         f = stdin;
10973         title = "stdin";
10974     } else {
10975         f = fopen(filename, "rb");
10976         if (f == NULL) {
10977           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10978             DisplayError(buf, errno);
10979             return FALSE;
10980         }
10981     }
10982     if (fseek(f, 0, 0) == -1) {
10983         /* f is not seekable; probably a pipe */
10984         useList = FALSE;
10985     }
10986     if (useList && n == 0) {
10987         int error = GameListBuild(f);
10988         if (error) {
10989             DisplayError(_("Cannot build game list"), error);
10990         } else if (!ListEmpty(&gameList) &&
10991                    ((ListGame *) gameList.tailPred)->number > 1) {
10992             GameListPopUp(f, title);
10993             return TRUE;
10994         }
10995         GameListDestroy();
10996         n = 1;
10997     }
10998     if (n == 0) n = 1;
10999     return LoadGame(f, n, title, FALSE);
11000 }
11001
11002
11003 void
11004 MakeRegisteredMove()
11005 {
11006     int fromX, fromY, toX, toY;
11007     char promoChar;
11008     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11009         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11010           case CMAIL_MOVE:
11011           case CMAIL_DRAW:
11012             if (appData.debugMode)
11013               fprintf(debugFP, "Restoring %s for game %d\n",
11014                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11015
11016             thinkOutput[0] = NULLCHAR;
11017             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11018             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11019             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11020             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11021             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11022             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11023             MakeMove(fromX, fromY, toX, toY, promoChar);
11024             ShowMove(fromX, fromY, toX, toY);
11025
11026             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11027               case MT_NONE:
11028               case MT_CHECK:
11029                 break;
11030
11031               case MT_CHECKMATE:
11032               case MT_STAINMATE:
11033                 if (WhiteOnMove(currentMove)) {
11034                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11035                 } else {
11036                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11037                 }
11038                 break;
11039
11040               case MT_STALEMATE:
11041                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11042                 break;
11043             }
11044
11045             break;
11046
11047           case CMAIL_RESIGN:
11048             if (WhiteOnMove(currentMove)) {
11049                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11050             } else {
11051                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11052             }
11053             break;
11054
11055           case CMAIL_ACCEPT:
11056             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11057             break;
11058
11059           default:
11060             break;
11061         }
11062     }
11063
11064     return;
11065 }
11066
11067 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11068 int
11069 CmailLoadGame(f, gameNumber, title, useList)
11070      FILE *f;
11071      int gameNumber;
11072      char *title;
11073      int useList;
11074 {
11075     int retVal;
11076
11077     if (gameNumber > nCmailGames) {
11078         DisplayError(_("No more games in this message"), 0);
11079         return FALSE;
11080     }
11081     if (f == lastLoadGameFP) {
11082         int offset = gameNumber - lastLoadGameNumber;
11083         if (offset == 0) {
11084             cmailMsg[0] = NULLCHAR;
11085             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11086                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11087                 nCmailMovesRegistered--;
11088             }
11089             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11090             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11091                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11092             }
11093         } else {
11094             if (! RegisterMove()) return FALSE;
11095         }
11096     }
11097
11098     retVal = LoadGame(f, gameNumber, title, useList);
11099
11100     /* Make move registered during previous look at this game, if any */
11101     MakeRegisteredMove();
11102
11103     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11104         commentList[currentMove]
11105           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11106         DisplayComment(currentMove - 1, commentList[currentMove]);
11107     }
11108
11109     return retVal;
11110 }
11111
11112 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11113 int
11114 ReloadGame(offset)
11115      int offset;
11116 {
11117     int gameNumber = lastLoadGameNumber + offset;
11118     if (lastLoadGameFP == NULL) {
11119         DisplayError(_("No game has been loaded yet"), 0);
11120         return FALSE;
11121     }
11122     if (gameNumber <= 0) {
11123         DisplayError(_("Can't back up any further"), 0);
11124         return FALSE;
11125     }
11126     if (cmailMsgLoaded) {
11127         return CmailLoadGame(lastLoadGameFP, gameNumber,
11128                              lastLoadGameTitle, lastLoadGameUseList);
11129     } else {
11130         return LoadGame(lastLoadGameFP, gameNumber,
11131                         lastLoadGameTitle, lastLoadGameUseList);
11132     }
11133 }
11134
11135 int keys[EmptySquare+1];
11136
11137 int
11138 PositionMatches(Board b1, Board b2)
11139 {
11140     int r, f, sum=0;
11141     switch(appData.searchMode) {
11142         case 1: return CompareWithRights(b1, b2);
11143         case 2:
11144             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11145                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11146             }
11147             return TRUE;
11148         case 3:
11149             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11150               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11151                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11152             }
11153             return sum==0;
11154         case 4:
11155             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11156                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11157             }
11158             return sum==0;
11159     }
11160     return TRUE;
11161 }
11162
11163 GameInfo dummyInfo;
11164
11165 int GameContainsPosition(FILE *f, ListGame *lg)
11166 {
11167     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11168     int fromX, fromY, toX, toY;
11169     char promoChar;
11170     static int initDone=FALSE;
11171
11172     if(!initDone) {
11173         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11174         initDone = TRUE;
11175     }
11176     dummyInfo.variant = VariantNormal;
11177     FREE(dummyInfo.fen); dummyInfo.fen = NULL;
11178     dummyInfo.whiteRating = 0;
11179     dummyInfo.blackRating = 0;
11180     FREE(dummyInfo.date); dummyInfo.date = NULL;
11181     fseek(f, lg->offset, 0);
11182     yynewfile(f);
11183     CopyBoard(boards[scratch], initialPosition); // default start position
11184     while(1) {
11185         yyboardindex = scratch + (plyNr&1);
11186       quickFlag = 1;
11187         next = Myylex();
11188       quickFlag = 0;
11189         switch(next) {
11190             case PGNTag:
11191                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11192 #if 0
11193                 ParsePGNTag(yy_text, &dummyInfo); // this has a bad memory leak...
11194                 if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL;
11195 #else
11196                 // do it ourselves avoiding malloc
11197                 { char *p = yy_text+1, *q;
11198                   while(!isdigit(*p) && !isalpha(*p)) p++;
11199                   q  = p; while(*p != ' ' && *p != '\t' && *p != '\n') p++;
11200                   *p = NULLCHAR;
11201                   if(!StrCaseCmp(q, "Date") && (p = strchr(p+1, '"'))) { if(atoi(p+1) < appData.dateThreshold) return -1; } else
11202                   if(!StrCaseCmp(q, "Variant")  &&  (p = strchr(p+1, '"'))) dummyInfo.variant = StringToVariant(p+1); else
11203                   if(!StrCaseCmp(q, "WhiteElo")  && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11204                   if(!StrCaseCmp(q, "BlackElo")  && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11205                   if(!StrCaseCmp(q, "WhiteUSCF") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11206                   if(!StrCaseCmp(q, "BlackUSCF") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11207                   if(!StrCaseCmp(q, "FEN")  && (p = strchr(p+1, '"'))) ParseFEN(boards[scratch], &btm, p+1);
11208                 }
11209 #endif
11210             default:
11211                 continue;
11212
11213             case XBoardGame:
11214             case GNUChessGame:
11215                 if(plyNr) return -1; // after we have seen moves, this is for new game
11216               continue;
11217
11218             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11219             case ImpossibleMove:
11220             case WhiteWins: // game ends here with these four
11221             case BlackWins:
11222             case GameIsDrawn:
11223             case GameUnfinished:
11224                 return -1;
11225
11226             case IllegalMove:
11227                 if(appData.testLegality) return -1;
11228             case WhiteCapturesEnPassant:
11229             case BlackCapturesEnPassant:
11230             case WhitePromotion:
11231             case BlackPromotion:
11232             case WhiteNonPromotion:
11233             case BlackNonPromotion:
11234             case NormalMove:
11235             case WhiteKingSideCastle:
11236             case WhiteQueenSideCastle:
11237             case BlackKingSideCastle:
11238             case BlackQueenSideCastle:
11239             case WhiteKingSideCastleWild:
11240             case WhiteQueenSideCastleWild:
11241             case BlackKingSideCastleWild:
11242             case BlackQueenSideCastleWild:
11243             case WhiteHSideCastleFR:
11244             case WhiteASideCastleFR:
11245             case BlackHSideCastleFR:
11246             case BlackASideCastleFR:
11247                 fromX = currentMoveString[0] - AAA;
11248                 fromY = currentMoveString[1] - ONE;
11249                 toX = currentMoveString[2] - AAA;
11250                 toY = currentMoveString[3] - ONE;
11251                 promoChar = currentMoveString[4];
11252                 break;
11253             case WhiteDrop:
11254             case BlackDrop:
11255                 fromX = next == WhiteDrop ?
11256                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11257                   (int) CharToPiece(ToLower(currentMoveString[0]));
11258                 fromY = DROP_RANK;
11259                 toX = currentMoveString[2] - AAA;
11260                 toY = currentMoveString[3] - ONE;
11261                 break;
11262         }
11263         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11264         if(plyNr == 0) { // but first figure out variant and initial position
11265             if(dummyInfo.variant != gameInfo.variant) return -1; // wrong variant
11266             if(appData.eloThreshold1 && (dummyInfo.whiteRating < appData.eloThreshold1 && dummyInfo.blackRating < appData.eloThreshold1)) return -1;
11267             if(appData.eloThreshold2 && (dummyInfo.whiteRating < appData.eloThreshold2 || dummyInfo.blackRating < appData.eloThreshold2)) return -1;
11268             if(appData.dateThreshold && (!dummyInfo.date || atoi(dummyInfo.date) < appData.dateThreshold)) return -1;
11269             if(btm) CopyBoard(boards[scratch+1], boards[scratch]), plyNr++;
11270             if(PositionMatches(boards[scratch + plyNr], boards[currentMove])) return plyNr;
11271         }
11272         CopyBoard(boards[scratch + (plyNr+1&1)], boards[scratch + (plyNr&1)]);
11273         plyNr++;
11274         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch + (plyNr&1)]);
11275         if(PositionMatches(boards[scratch + (plyNr&1)], boards[currentMove])) return plyNr;
11276     }
11277 }
11278
11279 /* Load the nth game from open file f */
11280 int
11281 LoadGame(f, gameNumber, title, useList)
11282      FILE *f;
11283      int gameNumber;
11284      char *title;
11285      int useList;
11286 {
11287     ChessMove cm;
11288     char buf[MSG_SIZ];
11289     int gn = gameNumber;
11290     ListGame *lg = NULL;
11291     int numPGNTags = 0;
11292     int err, pos = -1;
11293     GameMode oldGameMode;
11294     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11295
11296     if (appData.debugMode)
11297         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11298
11299     if (gameMode == Training )
11300         SetTrainingModeOff();
11301
11302     oldGameMode = gameMode;
11303     if (gameMode != BeginningOfGame) {
11304       Reset(FALSE, TRUE);
11305     }
11306
11307     gameFileFP = f;
11308     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11309         fclose(lastLoadGameFP);
11310     }
11311
11312     if (useList) {
11313         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11314
11315         if (lg) {
11316             fseek(f, lg->offset, 0);
11317             GameListHighlight(gameNumber);
11318             pos = lg->position;
11319             gn = 1;
11320         }
11321         else {
11322             DisplayError(_("Game number out of range"), 0);
11323             return FALSE;
11324         }
11325     } else {
11326         GameListDestroy();
11327         if (fseek(f, 0, 0) == -1) {
11328             if (f == lastLoadGameFP ?
11329                 gameNumber == lastLoadGameNumber + 1 :
11330                 gameNumber == 1) {
11331                 gn = 1;
11332             } else {
11333                 DisplayError(_("Can't seek on game file"), 0);
11334                 return FALSE;
11335             }
11336         }
11337     }
11338     lastLoadGameFP = f;
11339     lastLoadGameNumber = gameNumber;
11340     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11341     lastLoadGameUseList = useList;
11342
11343     yynewfile(f);
11344
11345     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11346       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11347                 lg->gameInfo.black);
11348             DisplayTitle(buf);
11349     } else if (*title != NULLCHAR) {
11350         if (gameNumber > 1) {
11351           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11352             DisplayTitle(buf);
11353         } else {
11354             DisplayTitle(title);
11355         }
11356     }
11357
11358     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11359         gameMode = PlayFromGameFile;
11360         ModeHighlight();
11361     }
11362
11363     currentMove = forwardMostMove = backwardMostMove = 0;
11364     CopyBoard(boards[0], initialPosition);
11365     StopClocks();
11366
11367     /*
11368      * Skip the first gn-1 games in the file.
11369      * Also skip over anything that precedes an identifiable
11370      * start of game marker, to avoid being confused by
11371      * garbage at the start of the file.  Currently
11372      * recognized start of game markers are the move number "1",
11373      * the pattern "gnuchess .* game", the pattern
11374      * "^[#;%] [^ ]* game file", and a PGN tag block.
11375      * A game that starts with one of the latter two patterns
11376      * will also have a move number 1, possibly
11377      * following a position diagram.
11378      * 5-4-02: Let's try being more lenient and allowing a game to
11379      * start with an unnumbered move.  Does that break anything?
11380      */
11381     cm = lastLoadGameStart = EndOfFile;
11382     while (gn > 0) {
11383         yyboardindex = forwardMostMove;
11384         cm = (ChessMove) Myylex();
11385         switch (cm) {
11386           case EndOfFile:
11387             if (cmailMsgLoaded) {
11388                 nCmailGames = CMAIL_MAX_GAMES - gn;
11389             } else {
11390                 Reset(TRUE, TRUE);
11391                 DisplayError(_("Game not found in file"), 0);
11392             }
11393             return FALSE;
11394
11395           case GNUChessGame:
11396           case XBoardGame:
11397             gn--;
11398             lastLoadGameStart = cm;
11399             break;
11400
11401           case MoveNumberOne:
11402             switch (lastLoadGameStart) {
11403               case GNUChessGame:
11404               case XBoardGame:
11405               case PGNTag:
11406                 break;
11407               case MoveNumberOne:
11408               case EndOfFile:
11409                 gn--;           /* count this game */
11410                 lastLoadGameStart = cm;
11411                 break;
11412               default:
11413                 /* impossible */
11414                 break;
11415             }
11416             break;
11417
11418           case PGNTag:
11419             switch (lastLoadGameStart) {
11420               case GNUChessGame:
11421               case PGNTag:
11422               case MoveNumberOne:
11423               case EndOfFile:
11424                 gn--;           /* count this game */
11425                 lastLoadGameStart = cm;
11426                 break;
11427               case XBoardGame:
11428                 lastLoadGameStart = cm; /* game counted already */
11429                 break;
11430               default:
11431                 /* impossible */
11432                 break;
11433             }
11434             if (gn > 0) {
11435                 do {
11436                     yyboardindex = forwardMostMove;
11437                     cm = (ChessMove) Myylex();
11438                 } while (cm == PGNTag || cm == Comment);
11439             }
11440             break;
11441
11442           case WhiteWins:
11443           case BlackWins:
11444           case GameIsDrawn:
11445             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11446                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11447                     != CMAIL_OLD_RESULT) {
11448                     nCmailResults ++ ;
11449                     cmailResult[  CMAIL_MAX_GAMES
11450                                 - gn - 1] = CMAIL_OLD_RESULT;
11451                 }
11452             }
11453             break;
11454
11455           case NormalMove:
11456             /* Only a NormalMove can be at the start of a game
11457              * without a position diagram. */
11458             if (lastLoadGameStart == EndOfFile ) {
11459               gn--;
11460               lastLoadGameStart = MoveNumberOne;
11461             }
11462             break;
11463
11464           default:
11465             break;
11466         }
11467     }
11468
11469     if (appData.debugMode)
11470       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11471
11472     if (cm == XBoardGame) {
11473         /* Skip any header junk before position diagram and/or move 1 */
11474         for (;;) {
11475             yyboardindex = forwardMostMove;
11476             cm = (ChessMove) Myylex();
11477
11478             if (cm == EndOfFile ||
11479                 cm == GNUChessGame || cm == XBoardGame) {
11480                 /* Empty game; pretend end-of-file and handle later */
11481                 cm = EndOfFile;
11482                 break;
11483             }
11484
11485             if (cm == MoveNumberOne || cm == PositionDiagram ||
11486                 cm == PGNTag || cm == Comment)
11487               break;
11488         }
11489     } else if (cm == GNUChessGame) {
11490         if (gameInfo.event != NULL) {
11491             free(gameInfo.event);
11492         }
11493         gameInfo.event = StrSave(yy_text);
11494     }
11495
11496     startedFromSetupPosition = FALSE;
11497     while (cm == PGNTag) {
11498         if (appData.debugMode)
11499           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11500         err = ParsePGNTag(yy_text, &gameInfo);
11501         if (!err) numPGNTags++;
11502
11503         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11504         if(gameInfo.variant != oldVariant) {
11505             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11506             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11507             InitPosition(TRUE);
11508             oldVariant = gameInfo.variant;
11509             if (appData.debugMode)
11510               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11511         }
11512
11513
11514         if (gameInfo.fen != NULL) {
11515           Board initial_position;
11516           startedFromSetupPosition = TRUE;
11517           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11518             Reset(TRUE, TRUE);
11519             DisplayError(_("Bad FEN position in file"), 0);
11520             return FALSE;
11521           }
11522           CopyBoard(boards[0], initial_position);
11523           if (blackPlaysFirst) {
11524             currentMove = forwardMostMove = backwardMostMove = 1;
11525             CopyBoard(boards[1], initial_position);
11526             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11527             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11528             timeRemaining[0][1] = whiteTimeRemaining;
11529             timeRemaining[1][1] = blackTimeRemaining;
11530             if (commentList[0] != NULL) {
11531               commentList[1] = commentList[0];
11532               commentList[0] = NULL;
11533             }
11534           } else {
11535             currentMove = forwardMostMove = backwardMostMove = 0;
11536           }
11537           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11538           {   int i;
11539               initialRulePlies = FENrulePlies;
11540               for( i=0; i< nrCastlingRights; i++ )
11541                   initialRights[i] = initial_position[CASTLING][i];
11542           }
11543           yyboardindex = forwardMostMove;
11544           free(gameInfo.fen);
11545           gameInfo.fen = NULL;
11546         }
11547
11548         yyboardindex = forwardMostMove;
11549         cm = (ChessMove) Myylex();
11550
11551         /* Handle comments interspersed among the tags */
11552         while (cm == Comment) {
11553             char *p;
11554             if (appData.debugMode)
11555               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11556             p = yy_text;
11557             AppendComment(currentMove, p, FALSE);
11558             yyboardindex = forwardMostMove;
11559             cm = (ChessMove) Myylex();
11560         }
11561     }
11562
11563     /* don't rely on existence of Event tag since if game was
11564      * pasted from clipboard the Event tag may not exist
11565      */
11566     if (numPGNTags > 0){
11567         char *tags;
11568         if (gameInfo.variant == VariantNormal) {
11569           VariantClass v = StringToVariant(gameInfo.event);
11570           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11571           if(v < VariantShogi) gameInfo.variant = v;
11572         }
11573         if (!matchMode) {
11574           if( appData.autoDisplayTags ) {
11575             tags = PGNTags(&gameInfo);
11576             TagsPopUp(tags, CmailMsg());
11577             free(tags);
11578           }
11579         }
11580     } else {
11581         /* Make something up, but don't display it now */
11582         SetGameInfo();
11583         TagsPopDown();
11584     }
11585
11586     if (cm == PositionDiagram) {
11587         int i, j;
11588         char *p;
11589         Board initial_position;
11590
11591         if (appData.debugMode)
11592           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11593
11594         if (!startedFromSetupPosition) {
11595             p = yy_text;
11596             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11597               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11598                 switch (*p) {
11599                   case '{':
11600                   case '[':
11601                   case '-':
11602                   case ' ':
11603                   case '\t':
11604                   case '\n':
11605                   case '\r':
11606                     break;
11607                   default:
11608                     initial_position[i][j++] = CharToPiece(*p);
11609                     break;
11610                 }
11611             while (*p == ' ' || *p == '\t' ||
11612                    *p == '\n' || *p == '\r') p++;
11613
11614             if (strncmp(p, "black", strlen("black"))==0)
11615               blackPlaysFirst = TRUE;
11616             else
11617               blackPlaysFirst = FALSE;
11618             startedFromSetupPosition = TRUE;
11619
11620             CopyBoard(boards[0], initial_position);
11621             if (blackPlaysFirst) {
11622                 currentMove = forwardMostMove = backwardMostMove = 1;
11623                 CopyBoard(boards[1], initial_position);
11624                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11625                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11626                 timeRemaining[0][1] = whiteTimeRemaining;
11627                 timeRemaining[1][1] = blackTimeRemaining;
11628                 if (commentList[0] != NULL) {
11629                     commentList[1] = commentList[0];
11630                     commentList[0] = NULL;
11631                 }
11632             } else {
11633                 currentMove = forwardMostMove = backwardMostMove = 0;
11634             }
11635         }
11636         yyboardindex = forwardMostMove;
11637         cm = (ChessMove) Myylex();
11638     }
11639
11640     if (first.pr == NoProc) {
11641         StartChessProgram(&first);
11642     }
11643     InitChessProgram(&first, FALSE);
11644     SendToProgram("force\n", &first);
11645     if (startedFromSetupPosition) {
11646         SendBoard(&first, forwardMostMove);
11647     if (appData.debugMode) {
11648         fprintf(debugFP, "Load Game\n");
11649     }
11650         DisplayBothClocks();
11651     }
11652
11653     /* [HGM] server: flag to write setup moves in broadcast file as one */
11654     loadFlag = appData.suppressLoadMoves;
11655
11656     while (cm == Comment) {
11657         char *p;
11658         if (appData.debugMode)
11659           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11660         p = yy_text;
11661         AppendComment(currentMove, p, FALSE);
11662         yyboardindex = forwardMostMove;
11663         cm = (ChessMove) Myylex();
11664     }
11665
11666     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11667         cm == WhiteWins || cm == BlackWins ||
11668         cm == GameIsDrawn || cm == GameUnfinished) {
11669         DisplayMessage("", _("No moves in game"));
11670         if (cmailMsgLoaded) {
11671             if (appData.debugMode)
11672               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11673             ClearHighlights();
11674             flipView = FALSE;
11675         }
11676         DrawPosition(FALSE, boards[currentMove]);
11677         DisplayBothClocks();
11678         gameMode = EditGame;
11679         ModeHighlight();
11680         gameFileFP = NULL;
11681         cmailOldMove = 0;
11682         return TRUE;
11683     }
11684
11685     // [HGM] PV info: routine tests if comment empty
11686     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11687         DisplayComment(currentMove - 1, commentList[currentMove]);
11688     }
11689     if (!matchMode && appData.timeDelay != 0)
11690       DrawPosition(FALSE, boards[currentMove]);
11691
11692     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11693       programStats.ok_to_send = 1;
11694     }
11695
11696     /* if the first token after the PGN tags is a move
11697      * and not move number 1, retrieve it from the parser
11698      */
11699     if (cm != MoveNumberOne)
11700         LoadGameOneMove(cm);
11701
11702     /* load the remaining moves from the file */
11703     while (LoadGameOneMove(EndOfFile)) {
11704       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11705       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11706     }
11707
11708     /* rewind to the start of the game */
11709     currentMove = backwardMostMove;
11710
11711     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11712
11713     if (oldGameMode == AnalyzeFile ||
11714         oldGameMode == AnalyzeMode) {
11715       AnalyzeFileEvent();
11716     }
11717
11718     if (!matchMode && pos >= 0) {
11719         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11720     } else
11721     if (matchMode || appData.timeDelay == 0) {
11722       ToEndEvent();
11723     } else if (appData.timeDelay > 0) {
11724       AutoPlayGameLoop();
11725     }
11726
11727     if (appData.debugMode)
11728         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11729
11730     loadFlag = 0; /* [HGM] true game starts */
11731     return TRUE;
11732 }
11733
11734 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11735 int
11736 ReloadPosition(offset)
11737      int offset;
11738 {
11739     int positionNumber = lastLoadPositionNumber + offset;
11740     if (lastLoadPositionFP == NULL) {
11741         DisplayError(_("No position has been loaded yet"), 0);
11742         return FALSE;
11743     }
11744     if (positionNumber <= 0) {
11745         DisplayError(_("Can't back up any further"), 0);
11746         return FALSE;
11747     }
11748     return LoadPosition(lastLoadPositionFP, positionNumber,
11749                         lastLoadPositionTitle);
11750 }
11751
11752 /* Load the nth position from the given file */
11753 int
11754 LoadPositionFromFile(filename, n, title)
11755      char *filename;
11756      int n;
11757      char *title;
11758 {
11759     FILE *f;
11760     char buf[MSG_SIZ];
11761
11762     if (strcmp(filename, "-") == 0) {
11763         return LoadPosition(stdin, n, "stdin");
11764     } else {
11765         f = fopen(filename, "rb");
11766         if (f == NULL) {
11767             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11768             DisplayError(buf, errno);
11769             return FALSE;
11770         } else {
11771             return LoadPosition(f, n, title);
11772         }
11773     }
11774 }
11775
11776 /* Load the nth position from the given open file, and close it */
11777 int
11778 LoadPosition(f, positionNumber, title)
11779      FILE *f;
11780      int positionNumber;
11781      char *title;
11782 {
11783     char *p, line[MSG_SIZ];
11784     Board initial_position;
11785     int i, j, fenMode, pn;
11786
11787     if (gameMode == Training )
11788         SetTrainingModeOff();
11789
11790     if (gameMode != BeginningOfGame) {
11791         Reset(FALSE, TRUE);
11792     }
11793     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11794         fclose(lastLoadPositionFP);
11795     }
11796     if (positionNumber == 0) positionNumber = 1;
11797     lastLoadPositionFP = f;
11798     lastLoadPositionNumber = positionNumber;
11799     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11800     if (first.pr == NoProc) {
11801       StartChessProgram(&first);
11802       InitChessProgram(&first, FALSE);
11803     }
11804     pn = positionNumber;
11805     if (positionNumber < 0) {
11806         /* Negative position number means to seek to that byte offset */
11807         if (fseek(f, -positionNumber, 0) == -1) {
11808             DisplayError(_("Can't seek on position file"), 0);
11809             return FALSE;
11810         };
11811         pn = 1;
11812     } else {
11813         if (fseek(f, 0, 0) == -1) {
11814             if (f == lastLoadPositionFP ?
11815                 positionNumber == lastLoadPositionNumber + 1 :
11816                 positionNumber == 1) {
11817                 pn = 1;
11818             } else {
11819                 DisplayError(_("Can't seek on position file"), 0);
11820                 return FALSE;
11821             }
11822         }
11823     }
11824     /* See if this file is FEN or old-style xboard */
11825     if (fgets(line, MSG_SIZ, f) == NULL) {
11826         DisplayError(_("Position not found in file"), 0);
11827         return FALSE;
11828     }
11829     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11830     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11831
11832     if (pn >= 2) {
11833         if (fenMode || line[0] == '#') pn--;
11834         while (pn > 0) {
11835             /* skip positions before number pn */
11836             if (fgets(line, MSG_SIZ, f) == NULL) {
11837                 Reset(TRUE, TRUE);
11838                 DisplayError(_("Position not found in file"), 0);
11839                 return FALSE;
11840             }
11841             if (fenMode || line[0] == '#') pn--;
11842         }
11843     }
11844
11845     if (fenMode) {
11846         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11847             DisplayError(_("Bad FEN position in file"), 0);
11848             return FALSE;
11849         }
11850     } else {
11851         (void) fgets(line, MSG_SIZ, f);
11852         (void) fgets(line, MSG_SIZ, f);
11853
11854         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11855             (void) fgets(line, MSG_SIZ, f);
11856             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11857                 if (*p == ' ')
11858                   continue;
11859                 initial_position[i][j++] = CharToPiece(*p);
11860             }
11861         }
11862
11863         blackPlaysFirst = FALSE;
11864         if (!feof(f)) {
11865             (void) fgets(line, MSG_SIZ, f);
11866             if (strncmp(line, "black", strlen("black"))==0)
11867               blackPlaysFirst = TRUE;
11868         }
11869     }
11870     startedFromSetupPosition = TRUE;
11871
11872     SendToProgram("force\n", &first);
11873     CopyBoard(boards[0], initial_position);
11874     if (blackPlaysFirst) {
11875         currentMove = forwardMostMove = backwardMostMove = 1;
11876         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11877         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11878         CopyBoard(boards[1], initial_position);
11879         DisplayMessage("", _("Black to play"));
11880     } else {
11881         currentMove = forwardMostMove = backwardMostMove = 0;
11882         DisplayMessage("", _("White to play"));
11883     }
11884     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11885     SendBoard(&first, forwardMostMove);
11886     if (appData.debugMode) {
11887 int i, j;
11888   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11889   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11890         fprintf(debugFP, "Load Position\n");
11891     }
11892
11893     if (positionNumber > 1) {
11894       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11895         DisplayTitle(line);
11896     } else {
11897         DisplayTitle(title);
11898     }
11899     gameMode = EditGame;
11900     ModeHighlight();
11901     ResetClocks();
11902     timeRemaining[0][1] = whiteTimeRemaining;
11903     timeRemaining[1][1] = blackTimeRemaining;
11904     DrawPosition(FALSE, boards[currentMove]);
11905
11906     return TRUE;
11907 }
11908
11909
11910 void
11911 CopyPlayerNameIntoFileName(dest, src)
11912      char **dest, *src;
11913 {
11914     while (*src != NULLCHAR && *src != ',') {
11915         if (*src == ' ') {
11916             *(*dest)++ = '_';
11917             src++;
11918         } else {
11919             *(*dest)++ = *src++;
11920         }
11921     }
11922 }
11923
11924 char *DefaultFileName(ext)
11925      char *ext;
11926 {
11927     static char def[MSG_SIZ];
11928     char *p;
11929
11930     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11931         p = def;
11932         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11933         *p++ = '-';
11934         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11935         *p++ = '.';
11936         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11937     } else {
11938         def[0] = NULLCHAR;
11939     }
11940     return def;
11941 }
11942
11943 /* Save the current game to the given file */
11944 int
11945 SaveGameToFile(filename, append)
11946      char *filename;
11947      int append;
11948 {
11949     FILE *f;
11950     char buf[MSG_SIZ];
11951     int result;
11952
11953     if (strcmp(filename, "-") == 0) {
11954         return SaveGame(stdout, 0, NULL);
11955     } else {
11956         f = fopen(filename, append ? "a" : "w");
11957         if (f == NULL) {
11958             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11959             DisplayError(buf, errno);
11960             return FALSE;
11961         } else {
11962             safeStrCpy(buf, lastMsg, MSG_SIZ);
11963             DisplayMessage(_("Waiting for access to save file"), "");
11964             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11965             DisplayMessage(_("Saving game"), "");
11966             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11967             result = SaveGame(f, 0, NULL);
11968             DisplayMessage(buf, "");
11969             return result;
11970         }
11971     }
11972 }
11973
11974 char *
11975 SavePart(str)
11976      char *str;
11977 {
11978     static char buf[MSG_SIZ];
11979     char *p;
11980
11981     p = strchr(str, ' ');
11982     if (p == NULL) return str;
11983     strncpy(buf, str, p - str);
11984     buf[p - str] = NULLCHAR;
11985     return buf;
11986 }
11987
11988 #define PGN_MAX_LINE 75
11989
11990 #define PGN_SIDE_WHITE  0
11991 #define PGN_SIDE_BLACK  1
11992
11993 /* [AS] */
11994 static int FindFirstMoveOutOfBook( int side )
11995 {
11996     int result = -1;
11997
11998     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11999         int index = backwardMostMove;
12000         int has_book_hit = 0;
12001
12002         if( (index % 2) != side ) {
12003             index++;
12004         }
12005
12006         while( index < forwardMostMove ) {
12007             /* Check to see if engine is in book */
12008             int depth = pvInfoList[index].depth;
12009             int score = pvInfoList[index].score;
12010             int in_book = 0;
12011
12012             if( depth <= 2 ) {
12013                 in_book = 1;
12014             }
12015             else if( score == 0 && depth == 63 ) {
12016                 in_book = 1; /* Zappa */
12017             }
12018             else if( score == 2 && depth == 99 ) {
12019                 in_book = 1; /* Abrok */
12020             }
12021
12022             has_book_hit += in_book;
12023
12024             if( ! in_book ) {
12025                 result = index;
12026
12027                 break;
12028             }
12029
12030             index += 2;
12031         }
12032     }
12033
12034     return result;
12035 }
12036
12037 /* [AS] */
12038 void GetOutOfBookInfo( char * buf )
12039 {
12040     int oob[2];
12041     int i;
12042     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12043
12044     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12045     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12046
12047     *buf = '\0';
12048
12049     if( oob[0] >= 0 || oob[1] >= 0 ) {
12050         for( i=0; i<2; i++ ) {
12051             int idx = oob[i];
12052
12053             if( idx >= 0 ) {
12054                 if( i > 0 && oob[0] >= 0 ) {
12055                     strcat( buf, "   " );
12056                 }
12057
12058                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12059                 sprintf( buf+strlen(buf), "%s%.2f",
12060                     pvInfoList[idx].score >= 0 ? "+" : "",
12061                     pvInfoList[idx].score / 100.0 );
12062             }
12063         }
12064     }
12065 }
12066
12067 /* Save game in PGN style and close the file */
12068 int
12069 SaveGamePGN(f)
12070      FILE *f;
12071 {
12072     int i, offset, linelen, newblock;
12073     time_t tm;
12074 //    char *movetext;
12075     char numtext[32];
12076     int movelen, numlen, blank;
12077     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12078
12079     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12080
12081     tm = time((time_t *) NULL);
12082
12083     PrintPGNTags(f, &gameInfo);
12084
12085     if (backwardMostMove > 0 || startedFromSetupPosition) {
12086         char *fen = PositionToFEN(backwardMostMove, NULL);
12087         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12088         fprintf(f, "\n{--------------\n");
12089         PrintPosition(f, backwardMostMove);
12090         fprintf(f, "--------------}\n");
12091         free(fen);
12092     }
12093     else {
12094         /* [AS] Out of book annotation */
12095         if( appData.saveOutOfBookInfo ) {
12096             char buf[64];
12097
12098             GetOutOfBookInfo( buf );
12099
12100             if( buf[0] != '\0' ) {
12101                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12102             }
12103         }
12104
12105         fprintf(f, "\n");
12106     }
12107
12108     i = backwardMostMove;
12109     linelen = 0;
12110     newblock = TRUE;
12111
12112     while (i < forwardMostMove) {
12113         /* Print comments preceding this move */
12114         if (commentList[i] != NULL) {
12115             if (linelen > 0) fprintf(f, "\n");
12116             fprintf(f, "%s", commentList[i]);
12117             linelen = 0;
12118             newblock = TRUE;
12119         }
12120
12121         /* Format move number */
12122         if ((i % 2) == 0)
12123           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12124         else
12125           if (newblock)
12126             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12127           else
12128             numtext[0] = NULLCHAR;
12129
12130         numlen = strlen(numtext);
12131         newblock = FALSE;
12132
12133         /* Print move number */
12134         blank = linelen > 0 && numlen > 0;
12135         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12136             fprintf(f, "\n");
12137             linelen = 0;
12138             blank = 0;
12139         }
12140         if (blank) {
12141             fprintf(f, " ");
12142             linelen++;
12143         }
12144         fprintf(f, "%s", numtext);
12145         linelen += numlen;
12146
12147         /* Get move */
12148         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12149         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12150
12151         /* Print move */
12152         blank = linelen > 0 && movelen > 0;
12153         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12154             fprintf(f, "\n");
12155             linelen = 0;
12156             blank = 0;
12157         }
12158         if (blank) {
12159             fprintf(f, " ");
12160             linelen++;
12161         }
12162         fprintf(f, "%s", move_buffer);
12163         linelen += movelen;
12164
12165         /* [AS] Add PV info if present */
12166         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12167             /* [HGM] add time */
12168             char buf[MSG_SIZ]; int seconds;
12169
12170             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12171
12172             if( seconds <= 0)
12173               buf[0] = 0;
12174             else
12175               if( seconds < 30 )
12176                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12177               else
12178                 {
12179                   seconds = (seconds + 4)/10; // round to full seconds
12180                   if( seconds < 60 )
12181                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12182                   else
12183                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12184                 }
12185
12186             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12187                       pvInfoList[i].score >= 0 ? "+" : "",
12188                       pvInfoList[i].score / 100.0,
12189                       pvInfoList[i].depth,
12190                       buf );
12191
12192             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12193
12194             /* Print score/depth */
12195             blank = linelen > 0 && movelen > 0;
12196             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12197                 fprintf(f, "\n");
12198                 linelen = 0;
12199                 blank = 0;
12200             }
12201             if (blank) {
12202                 fprintf(f, " ");
12203                 linelen++;
12204             }
12205             fprintf(f, "%s", move_buffer);
12206             linelen += movelen;
12207         }
12208
12209         i++;
12210     }
12211
12212     /* Start a new line */
12213     if (linelen > 0) fprintf(f, "\n");
12214
12215     /* Print comments after last move */
12216     if (commentList[i] != NULL) {
12217         fprintf(f, "%s\n", commentList[i]);
12218     }
12219
12220     /* Print result */
12221     if (gameInfo.resultDetails != NULL &&
12222         gameInfo.resultDetails[0] != NULLCHAR) {
12223         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12224                 PGNResult(gameInfo.result));
12225     } else {
12226         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12227     }
12228
12229     fclose(f);
12230     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12231     return TRUE;
12232 }
12233
12234 /* Save game in old style and close the file */
12235 int
12236 SaveGameOldStyle(f)
12237      FILE *f;
12238 {
12239     int i, offset;
12240     time_t tm;
12241
12242     tm = time((time_t *) NULL);
12243
12244     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12245     PrintOpponents(f);
12246
12247     if (backwardMostMove > 0 || startedFromSetupPosition) {
12248         fprintf(f, "\n[--------------\n");
12249         PrintPosition(f, backwardMostMove);
12250         fprintf(f, "--------------]\n");
12251     } else {
12252         fprintf(f, "\n");
12253     }
12254
12255     i = backwardMostMove;
12256     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12257
12258     while (i < forwardMostMove) {
12259         if (commentList[i] != NULL) {
12260             fprintf(f, "[%s]\n", commentList[i]);
12261         }
12262
12263         if ((i % 2) == 1) {
12264             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12265             i++;
12266         } else {
12267             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12268             i++;
12269             if (commentList[i] != NULL) {
12270                 fprintf(f, "\n");
12271                 continue;
12272             }
12273             if (i >= forwardMostMove) {
12274                 fprintf(f, "\n");
12275                 break;
12276             }
12277             fprintf(f, "%s\n", parseList[i]);
12278             i++;
12279         }
12280     }
12281
12282     if (commentList[i] != NULL) {
12283         fprintf(f, "[%s]\n", commentList[i]);
12284     }
12285
12286     /* This isn't really the old style, but it's close enough */
12287     if (gameInfo.resultDetails != NULL &&
12288         gameInfo.resultDetails[0] != NULLCHAR) {
12289         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12290                 gameInfo.resultDetails);
12291     } else {
12292         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12293     }
12294
12295     fclose(f);
12296     return TRUE;
12297 }
12298
12299 /* Save the current game to open file f and close the file */
12300 int
12301 SaveGame(f, dummy, dummy2)
12302      FILE *f;
12303      int dummy;
12304      char *dummy2;
12305 {
12306     if (gameMode == EditPosition) EditPositionDone(TRUE);
12307     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12308     if (appData.oldSaveStyle)
12309       return SaveGameOldStyle(f);
12310     else
12311       return SaveGamePGN(f);
12312 }
12313
12314 /* Save the current position to the given file */
12315 int
12316 SavePositionToFile(filename)
12317      char *filename;
12318 {
12319     FILE *f;
12320     char buf[MSG_SIZ];
12321
12322     if (strcmp(filename, "-") == 0) {
12323         return SavePosition(stdout, 0, NULL);
12324     } else {
12325         f = fopen(filename, "a");
12326         if (f == NULL) {
12327             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12328             DisplayError(buf, errno);
12329             return FALSE;
12330         } else {
12331             safeStrCpy(buf, lastMsg, MSG_SIZ);
12332             DisplayMessage(_("Waiting for access to save file"), "");
12333             flock(fileno(f), LOCK_EX); // [HGM] lock
12334             DisplayMessage(_("Saving position"), "");
12335             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12336             SavePosition(f, 0, NULL);
12337             DisplayMessage(buf, "");
12338             return TRUE;
12339         }
12340     }
12341 }
12342
12343 /* Save the current position to the given open file and close the file */
12344 int
12345 SavePosition(f, dummy, dummy2)
12346      FILE *f;
12347      int dummy;
12348      char *dummy2;
12349 {
12350     time_t tm;
12351     char *fen;
12352
12353     if (gameMode == EditPosition) EditPositionDone(TRUE);
12354     if (appData.oldSaveStyle) {
12355         tm = time((time_t *) NULL);
12356
12357         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12358         PrintOpponents(f);
12359         fprintf(f, "[--------------\n");
12360         PrintPosition(f, currentMove);
12361         fprintf(f, "--------------]\n");
12362     } else {
12363         fen = PositionToFEN(currentMove, NULL);
12364         fprintf(f, "%s\n", fen);
12365         free(fen);
12366     }
12367     fclose(f);
12368     return TRUE;
12369 }
12370
12371 void
12372 ReloadCmailMsgEvent(unregister)
12373      int unregister;
12374 {
12375 #if !WIN32
12376     static char *inFilename = NULL;
12377     static char *outFilename;
12378     int i;
12379     struct stat inbuf, outbuf;
12380     int status;
12381
12382     /* Any registered moves are unregistered if unregister is set, */
12383     /* i.e. invoked by the signal handler */
12384     if (unregister) {
12385         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12386             cmailMoveRegistered[i] = FALSE;
12387             if (cmailCommentList[i] != NULL) {
12388                 free(cmailCommentList[i]);
12389                 cmailCommentList[i] = NULL;
12390             }
12391         }
12392         nCmailMovesRegistered = 0;
12393     }
12394
12395     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12396         cmailResult[i] = CMAIL_NOT_RESULT;
12397     }
12398     nCmailResults = 0;
12399
12400     if (inFilename == NULL) {
12401         /* Because the filenames are static they only get malloced once  */
12402         /* and they never get freed                                      */
12403         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12404         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12405
12406         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12407         sprintf(outFilename, "%s.out", appData.cmailGameName);
12408     }
12409
12410     status = stat(outFilename, &outbuf);
12411     if (status < 0) {
12412         cmailMailedMove = FALSE;
12413     } else {
12414         status = stat(inFilename, &inbuf);
12415         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12416     }
12417
12418     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12419        counts the games, notes how each one terminated, etc.
12420
12421        It would be nice to remove this kludge and instead gather all
12422        the information while building the game list.  (And to keep it
12423        in the game list nodes instead of having a bunch of fixed-size
12424        parallel arrays.)  Note this will require getting each game's
12425        termination from the PGN tags, as the game list builder does
12426        not process the game moves.  --mann
12427        */
12428     cmailMsgLoaded = TRUE;
12429     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12430
12431     /* Load first game in the file or popup game menu */
12432     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12433
12434 #endif /* !WIN32 */
12435     return;
12436 }
12437
12438 int
12439 RegisterMove()
12440 {
12441     FILE *f;
12442     char string[MSG_SIZ];
12443
12444     if (   cmailMailedMove
12445         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12446         return TRUE;            /* Allow free viewing  */
12447     }
12448
12449     /* Unregister move to ensure that we don't leave RegisterMove        */
12450     /* with the move registered when the conditions for registering no   */
12451     /* longer hold                                                       */
12452     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12453         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12454         nCmailMovesRegistered --;
12455
12456         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12457           {
12458               free(cmailCommentList[lastLoadGameNumber - 1]);
12459               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12460           }
12461     }
12462
12463     if (cmailOldMove == -1) {
12464         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12465         return FALSE;
12466     }
12467
12468     if (currentMove > cmailOldMove + 1) {
12469         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12470         return FALSE;
12471     }
12472
12473     if (currentMove < cmailOldMove) {
12474         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12475         return FALSE;
12476     }
12477
12478     if (forwardMostMove > currentMove) {
12479         /* Silently truncate extra moves */
12480         TruncateGame();
12481     }
12482
12483     if (   (currentMove == cmailOldMove + 1)
12484         || (   (currentMove == cmailOldMove)
12485             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12486                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12487         if (gameInfo.result != GameUnfinished) {
12488             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12489         }
12490
12491         if (commentList[currentMove] != NULL) {
12492             cmailCommentList[lastLoadGameNumber - 1]
12493               = StrSave(commentList[currentMove]);
12494         }
12495         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12496
12497         if (appData.debugMode)
12498           fprintf(debugFP, "Saving %s for game %d\n",
12499                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12500
12501         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12502
12503         f = fopen(string, "w");
12504         if (appData.oldSaveStyle) {
12505             SaveGameOldStyle(f); /* also closes the file */
12506
12507             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12508             f = fopen(string, "w");
12509             SavePosition(f, 0, NULL); /* also closes the file */
12510         } else {
12511             fprintf(f, "{--------------\n");
12512             PrintPosition(f, currentMove);
12513             fprintf(f, "--------------}\n\n");
12514
12515             SaveGame(f, 0, NULL); /* also closes the file*/
12516         }
12517
12518         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12519         nCmailMovesRegistered ++;
12520     } else if (nCmailGames == 1) {
12521         DisplayError(_("You have not made a move yet"), 0);
12522         return FALSE;
12523     }
12524
12525     return TRUE;
12526 }
12527
12528 void
12529 MailMoveEvent()
12530 {
12531 #if !WIN32
12532     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12533     FILE *commandOutput;
12534     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12535     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12536     int nBuffers;
12537     int i;
12538     int archived;
12539     char *arcDir;
12540
12541     if (! cmailMsgLoaded) {
12542         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12543         return;
12544     }
12545
12546     if (nCmailGames == nCmailResults) {
12547         DisplayError(_("No unfinished games"), 0);
12548         return;
12549     }
12550
12551 #if CMAIL_PROHIBIT_REMAIL
12552     if (cmailMailedMove) {
12553       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);
12554         DisplayError(msg, 0);
12555         return;
12556     }
12557 #endif
12558
12559     if (! (cmailMailedMove || RegisterMove())) return;
12560
12561     if (   cmailMailedMove
12562         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12563       snprintf(string, MSG_SIZ, partCommandString,
12564                appData.debugMode ? " -v" : "", appData.cmailGameName);
12565         commandOutput = popen(string, "r");
12566
12567         if (commandOutput == NULL) {
12568             DisplayError(_("Failed to invoke cmail"), 0);
12569         } else {
12570             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12571                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12572             }
12573             if (nBuffers > 1) {
12574                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12575                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12576                 nBytes = MSG_SIZ - 1;
12577             } else {
12578                 (void) memcpy(msg, buffer, nBytes);
12579             }
12580             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12581
12582             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12583                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12584
12585                 archived = TRUE;
12586                 for (i = 0; i < nCmailGames; i ++) {
12587                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12588                         archived = FALSE;
12589                     }
12590                 }
12591                 if (   archived
12592                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12593                         != NULL)) {
12594                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12595                            arcDir,
12596                            appData.cmailGameName,
12597                            gameInfo.date);
12598                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12599                     cmailMsgLoaded = FALSE;
12600                 }
12601             }
12602
12603             DisplayInformation(msg);
12604             pclose(commandOutput);
12605         }
12606     } else {
12607         if ((*cmailMsg) != '\0') {
12608             DisplayInformation(cmailMsg);
12609         }
12610     }
12611
12612     return;
12613 #endif /* !WIN32 */
12614 }
12615
12616 char *
12617 CmailMsg()
12618 {
12619 #if WIN32
12620     return NULL;
12621 #else
12622     int  prependComma = 0;
12623     char number[5];
12624     char string[MSG_SIZ];       /* Space for game-list */
12625     int  i;
12626
12627     if (!cmailMsgLoaded) return "";
12628
12629     if (cmailMailedMove) {
12630       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12631     } else {
12632         /* Create a list of games left */
12633       snprintf(string, MSG_SIZ, "[");
12634         for (i = 0; i < nCmailGames; i ++) {
12635             if (! (   cmailMoveRegistered[i]
12636                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12637                 if (prependComma) {
12638                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12639                 } else {
12640                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12641                     prependComma = 1;
12642                 }
12643
12644                 strcat(string, number);
12645             }
12646         }
12647         strcat(string, "]");
12648
12649         if (nCmailMovesRegistered + nCmailResults == 0) {
12650             switch (nCmailGames) {
12651               case 1:
12652                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12653                 break;
12654
12655               case 2:
12656                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12657                 break;
12658
12659               default:
12660                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12661                          nCmailGames);
12662                 break;
12663             }
12664         } else {
12665             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12666               case 1:
12667                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12668                          string);
12669                 break;
12670
12671               case 0:
12672                 if (nCmailResults == nCmailGames) {
12673                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12674                 } else {
12675                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12676                 }
12677                 break;
12678
12679               default:
12680                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12681                          string);
12682             }
12683         }
12684     }
12685     return cmailMsg;
12686 #endif /* WIN32 */
12687 }
12688
12689 void
12690 ResetGameEvent()
12691 {
12692     if (gameMode == Training)
12693       SetTrainingModeOff();
12694
12695     Reset(TRUE, TRUE);
12696     cmailMsgLoaded = FALSE;
12697     if (appData.icsActive) {
12698       SendToICS(ics_prefix);
12699       SendToICS("refresh\n");
12700     }
12701 }
12702
12703 void
12704 ExitEvent(status)
12705      int status;
12706 {
12707     exiting++;
12708     if (exiting > 2) {
12709       /* Give up on clean exit */
12710       exit(status);
12711     }
12712     if (exiting > 1) {
12713       /* Keep trying for clean exit */
12714       return;
12715     }
12716
12717     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12718
12719     if (telnetISR != NULL) {
12720       RemoveInputSource(telnetISR);
12721     }
12722     if (icsPR != NoProc) {
12723       DestroyChildProcess(icsPR, TRUE);
12724     }
12725
12726     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12727     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12728
12729     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12730     /* make sure this other one finishes before killing it!                  */
12731     if(endingGame) { int count = 0;
12732         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12733         while(endingGame && count++ < 10) DoSleep(1);
12734         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12735     }
12736
12737     /* Kill off chess programs */
12738     if (first.pr != NoProc) {
12739         ExitAnalyzeMode();
12740
12741         DoSleep( appData.delayBeforeQuit );
12742         SendToProgram("quit\n", &first);
12743         DoSleep( appData.delayAfterQuit );
12744         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12745     }
12746     if (second.pr != NoProc) {
12747         DoSleep( appData.delayBeforeQuit );
12748         SendToProgram("quit\n", &second);
12749         DoSleep( appData.delayAfterQuit );
12750         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12751     }
12752     if (first.isr != NULL) {
12753         RemoveInputSource(first.isr);
12754     }
12755     if (second.isr != NULL) {
12756         RemoveInputSource(second.isr);
12757     }
12758
12759     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12760     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12761
12762     ShutDownFrontEnd();
12763     exit(status);
12764 }
12765
12766 void
12767 PauseEvent()
12768 {
12769     if (appData.debugMode)
12770         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12771     if (pausing) {
12772         pausing = FALSE;
12773         ModeHighlight();
12774         if (gameMode == MachinePlaysWhite ||
12775             gameMode == MachinePlaysBlack) {
12776             StartClocks();
12777         } else {
12778             DisplayBothClocks();
12779         }
12780         if (gameMode == PlayFromGameFile) {
12781             if (appData.timeDelay >= 0)
12782                 AutoPlayGameLoop();
12783         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12784             Reset(FALSE, TRUE);
12785             SendToICS(ics_prefix);
12786             SendToICS("refresh\n");
12787         } else if (currentMove < forwardMostMove) {
12788             ForwardInner(forwardMostMove);
12789         }
12790         pauseExamInvalid = FALSE;
12791     } else {
12792         switch (gameMode) {
12793           default:
12794             return;
12795           case IcsExamining:
12796             pauseExamForwardMostMove = forwardMostMove;
12797             pauseExamInvalid = FALSE;
12798             /* fall through */
12799           case IcsObserving:
12800           case IcsPlayingWhite:
12801           case IcsPlayingBlack:
12802             pausing = TRUE;
12803             ModeHighlight();
12804             return;
12805           case PlayFromGameFile:
12806             (void) StopLoadGameTimer();
12807             pausing = TRUE;
12808             ModeHighlight();
12809             break;
12810           case BeginningOfGame:
12811             if (appData.icsActive) return;
12812             /* else fall through */
12813           case MachinePlaysWhite:
12814           case MachinePlaysBlack:
12815           case TwoMachinesPlay:
12816             if (forwardMostMove == 0)
12817               return;           /* don't pause if no one has moved */
12818             if ((gameMode == MachinePlaysWhite &&
12819                  !WhiteOnMove(forwardMostMove)) ||
12820                 (gameMode == MachinePlaysBlack &&
12821                  WhiteOnMove(forwardMostMove))) {
12822                 StopClocks();
12823             }
12824             pausing = TRUE;
12825             ModeHighlight();
12826             break;
12827         }
12828     }
12829 }
12830
12831 void
12832 EditCommentEvent()
12833 {
12834     char title[MSG_SIZ];
12835
12836     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12837       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12838     } else {
12839       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12840                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12841                parseList[currentMove - 1]);
12842     }
12843
12844     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12845 }
12846
12847
12848 void
12849 EditTagsEvent()
12850 {
12851     char *tags = PGNTags(&gameInfo);
12852     bookUp = FALSE;
12853     EditTagsPopUp(tags, NULL);
12854     free(tags);
12855 }
12856
12857 void
12858 AnalyzeModeEvent()
12859 {
12860     if (appData.noChessProgram || gameMode == AnalyzeMode)
12861       return;
12862
12863     if (gameMode != AnalyzeFile) {
12864         if (!appData.icsEngineAnalyze) {
12865                EditGameEvent();
12866                if (gameMode != EditGame) return;
12867         }
12868         ResurrectChessProgram();
12869         SendToProgram("analyze\n", &first);
12870         first.analyzing = TRUE;
12871         /*first.maybeThinking = TRUE;*/
12872         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12873         EngineOutputPopUp();
12874     }
12875     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12876     pausing = FALSE;
12877     ModeHighlight();
12878     SetGameInfo();
12879
12880     StartAnalysisClock();
12881     GetTimeMark(&lastNodeCountTime);
12882     lastNodeCount = 0;
12883 }
12884
12885 void
12886 AnalyzeFileEvent()
12887 {
12888     if (appData.noChessProgram || gameMode == AnalyzeFile)
12889       return;
12890
12891     if (gameMode != AnalyzeMode) {
12892         EditGameEvent();
12893         if (gameMode != EditGame) return;
12894         ResurrectChessProgram();
12895         SendToProgram("analyze\n", &first);
12896         first.analyzing = TRUE;
12897         /*first.maybeThinking = TRUE;*/
12898         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12899         EngineOutputPopUp();
12900     }
12901     gameMode = AnalyzeFile;
12902     pausing = FALSE;
12903     ModeHighlight();
12904     SetGameInfo();
12905
12906     StartAnalysisClock();
12907     GetTimeMark(&lastNodeCountTime);
12908     lastNodeCount = 0;
12909     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
12910 }
12911
12912 void
12913 MachineWhiteEvent()
12914 {
12915     char buf[MSG_SIZ];
12916     char *bookHit = NULL;
12917
12918     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12919       return;
12920
12921
12922     if (gameMode == PlayFromGameFile ||
12923         gameMode == TwoMachinesPlay  ||
12924         gameMode == Training         ||
12925         gameMode == AnalyzeMode      ||
12926         gameMode == EndOfGame)
12927         EditGameEvent();
12928
12929     if (gameMode == EditPosition)
12930         EditPositionDone(TRUE);
12931
12932     if (!WhiteOnMove(currentMove)) {
12933         DisplayError(_("It is not White's turn"), 0);
12934         return;
12935     }
12936
12937     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12938       ExitAnalyzeMode();
12939
12940     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12941         gameMode == AnalyzeFile)
12942         TruncateGame();
12943
12944     ResurrectChessProgram();    /* in case it isn't running */
12945     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12946         gameMode = MachinePlaysWhite;
12947         ResetClocks();
12948     } else
12949     gameMode = MachinePlaysWhite;
12950     pausing = FALSE;
12951     ModeHighlight();
12952     SetGameInfo();
12953     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12954     DisplayTitle(buf);
12955     if (first.sendName) {
12956       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12957       SendToProgram(buf, &first);
12958     }
12959     if (first.sendTime) {
12960       if (first.useColors) {
12961         SendToProgram("black\n", &first); /*gnu kludge*/
12962       }
12963       SendTimeRemaining(&first, TRUE);
12964     }
12965     if (first.useColors) {
12966       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12967     }
12968     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12969     SetMachineThinkingEnables();
12970     first.maybeThinking = TRUE;
12971     StartClocks();
12972     firstMove = FALSE;
12973
12974     if (appData.autoFlipView && !flipView) {
12975       flipView = !flipView;
12976       DrawPosition(FALSE, NULL);
12977       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12978     }
12979
12980     if(bookHit) { // [HGM] book: simulate book reply
12981         static char bookMove[MSG_SIZ]; // a bit generous?
12982
12983         programStats.nodes = programStats.depth = programStats.time =
12984         programStats.score = programStats.got_only_move = 0;
12985         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12986
12987         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12988         strcat(bookMove, bookHit);
12989         HandleMachineMove(bookMove, &first);
12990     }
12991 }
12992
12993 void
12994 MachineBlackEvent()
12995 {
12996   char buf[MSG_SIZ];
12997   char *bookHit = NULL;
12998
12999     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13000         return;
13001
13002
13003     if (gameMode == PlayFromGameFile ||
13004         gameMode == TwoMachinesPlay  ||
13005         gameMode == Training         ||
13006         gameMode == AnalyzeMode      ||
13007         gameMode == EndOfGame)
13008         EditGameEvent();
13009
13010     if (gameMode == EditPosition)
13011         EditPositionDone(TRUE);
13012
13013     if (WhiteOnMove(currentMove)) {
13014         DisplayError(_("It is not Black's turn"), 0);
13015         return;
13016     }
13017
13018     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13019       ExitAnalyzeMode();
13020
13021     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13022         gameMode == AnalyzeFile)
13023         TruncateGame();
13024
13025     ResurrectChessProgram();    /* in case it isn't running */
13026     gameMode = MachinePlaysBlack;
13027     pausing = FALSE;
13028     ModeHighlight();
13029     SetGameInfo();
13030     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13031     DisplayTitle(buf);
13032     if (first.sendName) {
13033       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13034       SendToProgram(buf, &first);
13035     }
13036     if (first.sendTime) {
13037       if (first.useColors) {
13038         SendToProgram("white\n", &first); /*gnu kludge*/
13039       }
13040       SendTimeRemaining(&first, FALSE);
13041     }
13042     if (first.useColors) {
13043       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13044     }
13045     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13046     SetMachineThinkingEnables();
13047     first.maybeThinking = TRUE;
13048     StartClocks();
13049
13050     if (appData.autoFlipView && flipView) {
13051       flipView = !flipView;
13052       DrawPosition(FALSE, NULL);
13053       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13054     }
13055     if(bookHit) { // [HGM] book: simulate book reply
13056         static char bookMove[MSG_SIZ]; // a bit generous?
13057
13058         programStats.nodes = programStats.depth = programStats.time =
13059         programStats.score = programStats.got_only_move = 0;
13060         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13061
13062         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13063         strcat(bookMove, bookHit);
13064         HandleMachineMove(bookMove, &first);
13065     }
13066 }
13067
13068
13069 void
13070 DisplayTwoMachinesTitle()
13071 {
13072     char buf[MSG_SIZ];
13073     if (appData.matchGames > 0) {
13074         if(appData.tourneyFile[0]) {
13075           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13076                    gameInfo.white, gameInfo.black,
13077                    nextGame+1, appData.matchGames+1,
13078                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13079         } else 
13080         if (first.twoMachinesColor[0] == 'w') {
13081           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13082                    gameInfo.white, gameInfo.black,
13083                    first.matchWins, second.matchWins,
13084                    matchGame - 1 - (first.matchWins + second.matchWins));
13085         } else {
13086           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13087                    gameInfo.white, gameInfo.black,
13088                    second.matchWins, first.matchWins,
13089                    matchGame - 1 - (first.matchWins + second.matchWins));
13090         }
13091     } else {
13092       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13093     }
13094     DisplayTitle(buf);
13095 }
13096
13097 void
13098 SettingsMenuIfReady()
13099 {
13100   if (second.lastPing != second.lastPong) {
13101     DisplayMessage("", _("Waiting for second chess program"));
13102     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13103     return;
13104   }
13105   ThawUI();
13106   DisplayMessage("", "");
13107   SettingsPopUp(&second);
13108 }
13109
13110 int
13111 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13112 {
13113     char buf[MSG_SIZ];
13114     if (cps->pr == NULL) {
13115         StartChessProgram(cps);
13116         if (cps->protocolVersion == 1) {
13117           retry();
13118         } else {
13119           /* kludge: allow timeout for initial "feature" command */
13120           FreezeUI();
13121           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13122           DisplayMessage("", buf);
13123           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13124         }
13125         return 1;
13126     }
13127     return 0;
13128 }
13129
13130 void
13131 TwoMachinesEvent P((void))
13132 {
13133     int i;
13134     char buf[MSG_SIZ];
13135     ChessProgramState *onmove;
13136     char *bookHit = NULL;
13137     static int stalling = 0;
13138     TimeMark now;
13139     long wait;
13140
13141     if (appData.noChessProgram) return;
13142
13143     switch (gameMode) {
13144       case TwoMachinesPlay:
13145         return;
13146       case MachinePlaysWhite:
13147       case MachinePlaysBlack:
13148         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13149             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13150             return;
13151         }
13152         /* fall through */
13153       case BeginningOfGame:
13154       case PlayFromGameFile:
13155       case EndOfGame:
13156         EditGameEvent();
13157         if (gameMode != EditGame) return;
13158         break;
13159       case EditPosition:
13160         EditPositionDone(TRUE);
13161         break;
13162       case AnalyzeMode:
13163       case AnalyzeFile:
13164         ExitAnalyzeMode();
13165         break;
13166       case EditGame:
13167       default:
13168         break;
13169     }
13170
13171 //    forwardMostMove = currentMove;
13172     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13173
13174     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13175
13176     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13177     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13178       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13179       return;
13180     }
13181     if(!stalling) {
13182       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13183       SendToProgram("force\n", &second);
13184       stalling = 1;
13185       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13186       return;
13187     }
13188     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13189     if(appData.matchPause>10000 || appData.matchPause<10)
13190                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13191     wait = SubtractTimeMarks(&now, &pauseStart);
13192     if(wait < appData.matchPause) {
13193         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13194         return;
13195     }
13196     stalling = 0;
13197     DisplayMessage("", "");
13198     if (startedFromSetupPosition) {
13199         SendBoard(&second, backwardMostMove);
13200     if (appData.debugMode) {
13201         fprintf(debugFP, "Two Machines\n");
13202     }
13203     }
13204     for (i = backwardMostMove; i < forwardMostMove; i++) {
13205         SendMoveToProgram(i, &second);
13206     }
13207
13208     gameMode = TwoMachinesPlay;
13209     pausing = FALSE;
13210     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13211     SetGameInfo();
13212     DisplayTwoMachinesTitle();
13213     firstMove = TRUE;
13214     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13215         onmove = &first;
13216     } else {
13217         onmove = &second;
13218     }
13219     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13220     SendToProgram(first.computerString, &first);
13221     if (first.sendName) {
13222       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13223       SendToProgram(buf, &first);
13224     }
13225     SendToProgram(second.computerString, &second);
13226     if (second.sendName) {
13227       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13228       SendToProgram(buf, &second);
13229     }
13230
13231     ResetClocks();
13232     if (!first.sendTime || !second.sendTime) {
13233         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13234         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13235     }
13236     if (onmove->sendTime) {
13237       if (onmove->useColors) {
13238         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13239       }
13240       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13241     }
13242     if (onmove->useColors) {
13243       SendToProgram(onmove->twoMachinesColor, onmove);
13244     }
13245     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13246 //    SendToProgram("go\n", onmove);
13247     onmove->maybeThinking = TRUE;
13248     SetMachineThinkingEnables();
13249
13250     StartClocks();
13251
13252     if(bookHit) { // [HGM] book: simulate book reply
13253         static char bookMove[MSG_SIZ]; // a bit generous?
13254
13255         programStats.nodes = programStats.depth = programStats.time =
13256         programStats.score = programStats.got_only_move = 0;
13257         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13258
13259         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13260         strcat(bookMove, bookHit);
13261         savedMessage = bookMove; // args for deferred call
13262         savedState = onmove;
13263         ScheduleDelayedEvent(DeferredBookMove, 1);
13264     }
13265 }
13266
13267 void
13268 TrainingEvent()
13269 {
13270     if (gameMode == Training) {
13271       SetTrainingModeOff();
13272       gameMode = PlayFromGameFile;
13273       DisplayMessage("", _("Training mode off"));
13274     } else {
13275       gameMode = Training;
13276       animateTraining = appData.animate;
13277
13278       /* make sure we are not already at the end of the game */
13279       if (currentMove < forwardMostMove) {
13280         SetTrainingModeOn();
13281         DisplayMessage("", _("Training mode on"));
13282       } else {
13283         gameMode = PlayFromGameFile;
13284         DisplayError(_("Already at end of game"), 0);
13285       }
13286     }
13287     ModeHighlight();
13288 }
13289
13290 void
13291 IcsClientEvent()
13292 {
13293     if (!appData.icsActive) return;
13294     switch (gameMode) {
13295       case IcsPlayingWhite:
13296       case IcsPlayingBlack:
13297       case IcsObserving:
13298       case IcsIdle:
13299       case BeginningOfGame:
13300       case IcsExamining:
13301         return;
13302
13303       case EditGame:
13304         break;
13305
13306       case EditPosition:
13307         EditPositionDone(TRUE);
13308         break;
13309
13310       case AnalyzeMode:
13311       case AnalyzeFile:
13312         ExitAnalyzeMode();
13313         break;
13314
13315       default:
13316         EditGameEvent();
13317         break;
13318     }
13319
13320     gameMode = IcsIdle;
13321     ModeHighlight();
13322     return;
13323 }
13324
13325
13326 void
13327 EditGameEvent()
13328 {
13329     int i;
13330
13331     switch (gameMode) {
13332       case Training:
13333         SetTrainingModeOff();
13334         break;
13335       case MachinePlaysWhite:
13336       case MachinePlaysBlack:
13337       case BeginningOfGame:
13338         SendToProgram("force\n", &first);
13339         SetUserThinkingEnables();
13340         break;
13341       case PlayFromGameFile:
13342         (void) StopLoadGameTimer();
13343         if (gameFileFP != NULL) {
13344             gameFileFP = NULL;
13345         }
13346         break;
13347       case EditPosition:
13348         EditPositionDone(TRUE);
13349         break;
13350       case AnalyzeMode:
13351       case AnalyzeFile:
13352         ExitAnalyzeMode();
13353         SendToProgram("force\n", &first);
13354         break;
13355       case TwoMachinesPlay:
13356         GameEnds(EndOfFile, NULL, GE_PLAYER);
13357         ResurrectChessProgram();
13358         SetUserThinkingEnables();
13359         break;
13360       case EndOfGame:
13361         ResurrectChessProgram();
13362         break;
13363       case IcsPlayingBlack:
13364       case IcsPlayingWhite:
13365         DisplayError(_("Warning: You are still playing a game"), 0);
13366         break;
13367       case IcsObserving:
13368         DisplayError(_("Warning: You are still observing a game"), 0);
13369         break;
13370       case IcsExamining:
13371         DisplayError(_("Warning: You are still examining a game"), 0);
13372         break;
13373       case IcsIdle:
13374         break;
13375       case EditGame:
13376       default:
13377         return;
13378     }
13379
13380     pausing = FALSE;
13381     StopClocks();
13382     first.offeredDraw = second.offeredDraw = 0;
13383
13384     if (gameMode == PlayFromGameFile) {
13385         whiteTimeRemaining = timeRemaining[0][currentMove];
13386         blackTimeRemaining = timeRemaining[1][currentMove];
13387         DisplayTitle("");
13388     }
13389
13390     if (gameMode == MachinePlaysWhite ||
13391         gameMode == MachinePlaysBlack ||
13392         gameMode == TwoMachinesPlay ||
13393         gameMode == EndOfGame) {
13394         i = forwardMostMove;
13395         while (i > currentMove) {
13396             SendToProgram("undo\n", &first);
13397             i--;
13398         }
13399         whiteTimeRemaining = timeRemaining[0][currentMove];
13400         blackTimeRemaining = timeRemaining[1][currentMove];
13401         DisplayBothClocks();
13402         if (whiteFlag || blackFlag) {
13403             whiteFlag = blackFlag = 0;
13404         }
13405         DisplayTitle("");
13406     }
13407
13408     gameMode = EditGame;
13409     ModeHighlight();
13410     SetGameInfo();
13411 }
13412
13413
13414 void
13415 EditPositionEvent()
13416 {
13417     if (gameMode == EditPosition) {
13418         EditGameEvent();
13419         return;
13420     }
13421
13422     EditGameEvent();
13423     if (gameMode != EditGame) return;
13424
13425     gameMode = EditPosition;
13426     ModeHighlight();
13427     SetGameInfo();
13428     if (currentMove > 0)
13429       CopyBoard(boards[0], boards[currentMove]);
13430
13431     blackPlaysFirst = !WhiteOnMove(currentMove);
13432     ResetClocks();
13433     currentMove = forwardMostMove = backwardMostMove = 0;
13434     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13435     DisplayMove(-1);
13436 }
13437
13438 void
13439 ExitAnalyzeMode()
13440 {
13441     /* [DM] icsEngineAnalyze - possible call from other functions */
13442     if (appData.icsEngineAnalyze) {
13443         appData.icsEngineAnalyze = FALSE;
13444
13445         DisplayMessage("",_("Close ICS engine analyze..."));
13446     }
13447     if (first.analysisSupport && first.analyzing) {
13448       SendToProgram("exit\n", &first);
13449       first.analyzing = FALSE;
13450     }
13451     thinkOutput[0] = NULLCHAR;
13452 }
13453
13454 void
13455 EditPositionDone(Boolean fakeRights)
13456 {
13457     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13458
13459     startedFromSetupPosition = TRUE;
13460     InitChessProgram(&first, FALSE);
13461     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13462       boards[0][EP_STATUS] = EP_NONE;
13463       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13464     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13465         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13466         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13467       } else boards[0][CASTLING][2] = NoRights;
13468     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13469         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13470         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13471       } else boards[0][CASTLING][5] = NoRights;
13472     }
13473     SendToProgram("force\n", &first);
13474     if (blackPlaysFirst) {
13475         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13476         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13477         currentMove = forwardMostMove = backwardMostMove = 1;
13478         CopyBoard(boards[1], boards[0]);
13479     } else {
13480         currentMove = forwardMostMove = backwardMostMove = 0;
13481     }
13482     SendBoard(&first, forwardMostMove);
13483     if (appData.debugMode) {
13484         fprintf(debugFP, "EditPosDone\n");
13485     }
13486     DisplayTitle("");
13487     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13488     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13489     gameMode = EditGame;
13490     ModeHighlight();
13491     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13492     ClearHighlights(); /* [AS] */
13493 }
13494
13495 /* Pause for `ms' milliseconds */
13496 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13497 void
13498 TimeDelay(ms)
13499      long ms;
13500 {
13501     TimeMark m1, m2;
13502
13503     GetTimeMark(&m1);
13504     do {
13505         GetTimeMark(&m2);
13506     } while (SubtractTimeMarks(&m2, &m1) < ms);
13507 }
13508
13509 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13510 void
13511 SendMultiLineToICS(buf)
13512      char *buf;
13513 {
13514     char temp[MSG_SIZ+1], *p;
13515     int len;
13516
13517     len = strlen(buf);
13518     if (len > MSG_SIZ)
13519       len = MSG_SIZ;
13520
13521     strncpy(temp, buf, len);
13522     temp[len] = 0;
13523
13524     p = temp;
13525     while (*p) {
13526         if (*p == '\n' || *p == '\r')
13527           *p = ' ';
13528         ++p;
13529     }
13530
13531     strcat(temp, "\n");
13532     SendToICS(temp);
13533     SendToPlayer(temp, strlen(temp));
13534 }
13535
13536 void
13537 SetWhiteToPlayEvent()
13538 {
13539     if (gameMode == EditPosition) {
13540         blackPlaysFirst = FALSE;
13541         DisplayBothClocks();    /* works because currentMove is 0 */
13542     } else if (gameMode == IcsExamining) {
13543         SendToICS(ics_prefix);
13544         SendToICS("tomove white\n");
13545     }
13546 }
13547
13548 void
13549 SetBlackToPlayEvent()
13550 {
13551     if (gameMode == EditPosition) {
13552         blackPlaysFirst = TRUE;
13553         currentMove = 1;        /* kludge */
13554         DisplayBothClocks();
13555         currentMove = 0;
13556     } else if (gameMode == IcsExamining) {
13557         SendToICS(ics_prefix);
13558         SendToICS("tomove black\n");
13559     }
13560 }
13561
13562 void
13563 EditPositionMenuEvent(selection, x, y)
13564      ChessSquare selection;
13565      int x, y;
13566 {
13567     char buf[MSG_SIZ];
13568     ChessSquare piece = boards[0][y][x];
13569
13570     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13571
13572     switch (selection) {
13573       case ClearBoard:
13574         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13575             SendToICS(ics_prefix);
13576             SendToICS("bsetup clear\n");
13577         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13578             SendToICS(ics_prefix);
13579             SendToICS("clearboard\n");
13580         } else {
13581             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13582                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13583                 for (y = 0; y < BOARD_HEIGHT; y++) {
13584                     if (gameMode == IcsExamining) {
13585                         if (boards[currentMove][y][x] != EmptySquare) {
13586                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13587                                     AAA + x, ONE + y);
13588                             SendToICS(buf);
13589                         }
13590                     } else {
13591                         boards[0][y][x] = p;
13592                     }
13593                 }
13594             }
13595         }
13596         if (gameMode == EditPosition) {
13597             DrawPosition(FALSE, boards[0]);
13598         }
13599         break;
13600
13601       case WhitePlay:
13602         SetWhiteToPlayEvent();
13603         break;
13604
13605       case BlackPlay:
13606         SetBlackToPlayEvent();
13607         break;
13608
13609       case EmptySquare:
13610         if (gameMode == IcsExamining) {
13611             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13612             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13613             SendToICS(buf);
13614         } else {
13615             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13616                 if(x == BOARD_LEFT-2) {
13617                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13618                     boards[0][y][1] = 0;
13619                 } else
13620                 if(x == BOARD_RGHT+1) {
13621                     if(y >= gameInfo.holdingsSize) break;
13622                     boards[0][y][BOARD_WIDTH-2] = 0;
13623                 } else break;
13624             }
13625             boards[0][y][x] = EmptySquare;
13626             DrawPosition(FALSE, boards[0]);
13627         }
13628         break;
13629
13630       case PromotePiece:
13631         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13632            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13633             selection = (ChessSquare) (PROMOTED piece);
13634         } else if(piece == EmptySquare) selection = WhiteSilver;
13635         else selection = (ChessSquare)((int)piece - 1);
13636         goto defaultlabel;
13637
13638       case DemotePiece:
13639         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13640            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13641             selection = (ChessSquare) (DEMOTED piece);
13642         } else if(piece == EmptySquare) selection = BlackSilver;
13643         else selection = (ChessSquare)((int)piece + 1);
13644         goto defaultlabel;
13645
13646       case WhiteQueen:
13647       case BlackQueen:
13648         if(gameInfo.variant == VariantShatranj ||
13649            gameInfo.variant == VariantXiangqi  ||
13650            gameInfo.variant == VariantCourier  ||
13651            gameInfo.variant == VariantMakruk     )
13652             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13653         goto defaultlabel;
13654
13655       case WhiteKing:
13656       case BlackKing:
13657         if(gameInfo.variant == VariantXiangqi)
13658             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13659         if(gameInfo.variant == VariantKnightmate)
13660             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13661       default:
13662         defaultlabel:
13663         if (gameMode == IcsExamining) {
13664             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13665             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13666                      PieceToChar(selection), AAA + x, ONE + y);
13667             SendToICS(buf);
13668         } else {
13669             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13670                 int n;
13671                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13672                     n = PieceToNumber(selection - BlackPawn);
13673                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13674                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13675                     boards[0][BOARD_HEIGHT-1-n][1]++;
13676                 } else
13677                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13678                     n = PieceToNumber(selection);
13679                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13680                     boards[0][n][BOARD_WIDTH-1] = selection;
13681                     boards[0][n][BOARD_WIDTH-2]++;
13682                 }
13683             } else
13684             boards[0][y][x] = selection;
13685             DrawPosition(TRUE, boards[0]);
13686         }
13687         break;
13688     }
13689 }
13690
13691
13692 void
13693 DropMenuEvent(selection, x, y)
13694      ChessSquare selection;
13695      int x, y;
13696 {
13697     ChessMove moveType;
13698
13699     switch (gameMode) {
13700       case IcsPlayingWhite:
13701       case MachinePlaysBlack:
13702         if (!WhiteOnMove(currentMove)) {
13703             DisplayMoveError(_("It is Black's turn"));
13704             return;
13705         }
13706         moveType = WhiteDrop;
13707         break;
13708       case IcsPlayingBlack:
13709       case MachinePlaysWhite:
13710         if (WhiteOnMove(currentMove)) {
13711             DisplayMoveError(_("It is White's turn"));
13712             return;
13713         }
13714         moveType = BlackDrop;
13715         break;
13716       case EditGame:
13717         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13718         break;
13719       default:
13720         return;
13721     }
13722
13723     if (moveType == BlackDrop && selection < BlackPawn) {
13724       selection = (ChessSquare) ((int) selection
13725                                  + (int) BlackPawn - (int) WhitePawn);
13726     }
13727     if (boards[currentMove][y][x] != EmptySquare) {
13728         DisplayMoveError(_("That square is occupied"));
13729         return;
13730     }
13731
13732     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13733 }
13734
13735 void
13736 AcceptEvent()
13737 {
13738     /* Accept a pending offer of any kind from opponent */
13739
13740     if (appData.icsActive) {
13741         SendToICS(ics_prefix);
13742         SendToICS("accept\n");
13743     } else if (cmailMsgLoaded) {
13744         if (currentMove == cmailOldMove &&
13745             commentList[cmailOldMove] != NULL &&
13746             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13747                    "Black offers a draw" : "White offers a draw")) {
13748             TruncateGame();
13749             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13750             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13751         } else {
13752             DisplayError(_("There is no pending offer on this move"), 0);
13753             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13754         }
13755     } else {
13756         /* Not used for offers from chess program */
13757     }
13758 }
13759
13760 void
13761 DeclineEvent()
13762 {
13763     /* Decline a pending offer of any kind from opponent */
13764
13765     if (appData.icsActive) {
13766         SendToICS(ics_prefix);
13767         SendToICS("decline\n");
13768     } else if (cmailMsgLoaded) {
13769         if (currentMove == cmailOldMove &&
13770             commentList[cmailOldMove] != NULL &&
13771             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13772                    "Black offers a draw" : "White offers a draw")) {
13773 #ifdef NOTDEF
13774             AppendComment(cmailOldMove, "Draw declined", TRUE);
13775             DisplayComment(cmailOldMove - 1, "Draw declined");
13776 #endif /*NOTDEF*/
13777         } else {
13778             DisplayError(_("There is no pending offer on this move"), 0);
13779         }
13780     } else {
13781         /* Not used for offers from chess program */
13782     }
13783 }
13784
13785 void
13786 RematchEvent()
13787 {
13788     /* Issue ICS rematch command */
13789     if (appData.icsActive) {
13790         SendToICS(ics_prefix);
13791         SendToICS("rematch\n");
13792     }
13793 }
13794
13795 void
13796 CallFlagEvent()
13797 {
13798     /* Call your opponent's flag (claim a win on time) */
13799     if (appData.icsActive) {
13800         SendToICS(ics_prefix);
13801         SendToICS("flag\n");
13802     } else {
13803         switch (gameMode) {
13804           default:
13805             return;
13806           case MachinePlaysWhite:
13807             if (whiteFlag) {
13808                 if (blackFlag)
13809                   GameEnds(GameIsDrawn, "Both players ran out of time",
13810                            GE_PLAYER);
13811                 else
13812                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13813             } else {
13814                 DisplayError(_("Your opponent is not out of time"), 0);
13815             }
13816             break;
13817           case MachinePlaysBlack:
13818             if (blackFlag) {
13819                 if (whiteFlag)
13820                   GameEnds(GameIsDrawn, "Both players ran out of time",
13821                            GE_PLAYER);
13822                 else
13823                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13824             } else {
13825                 DisplayError(_("Your opponent is not out of time"), 0);
13826             }
13827             break;
13828         }
13829     }
13830 }
13831
13832 void
13833 ClockClick(int which)
13834 {       // [HGM] code moved to back-end from winboard.c
13835         if(which) { // black clock
13836           if (gameMode == EditPosition || gameMode == IcsExamining) {
13837             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13838             SetBlackToPlayEvent();
13839           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13840           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13841           } else if (shiftKey) {
13842             AdjustClock(which, -1);
13843           } else if (gameMode == IcsPlayingWhite ||
13844                      gameMode == MachinePlaysBlack) {
13845             CallFlagEvent();
13846           }
13847         } else { // white clock
13848           if (gameMode == EditPosition || gameMode == IcsExamining) {
13849             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13850             SetWhiteToPlayEvent();
13851           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13852           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13853           } else if (shiftKey) {
13854             AdjustClock(which, -1);
13855           } else if (gameMode == IcsPlayingBlack ||
13856                    gameMode == MachinePlaysWhite) {
13857             CallFlagEvent();
13858           }
13859         }
13860 }
13861
13862 void
13863 DrawEvent()
13864 {
13865     /* Offer draw or accept pending draw offer from opponent */
13866
13867     if (appData.icsActive) {
13868         /* Note: tournament rules require draw offers to be
13869            made after you make your move but before you punch
13870            your clock.  Currently ICS doesn't let you do that;
13871            instead, you immediately punch your clock after making
13872            a move, but you can offer a draw at any time. */
13873
13874         SendToICS(ics_prefix);
13875         SendToICS("draw\n");
13876         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13877     } else if (cmailMsgLoaded) {
13878         if (currentMove == cmailOldMove &&
13879             commentList[cmailOldMove] != NULL &&
13880             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13881                    "Black offers a draw" : "White offers a draw")) {
13882             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13883             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13884         } else if (currentMove == cmailOldMove + 1) {
13885             char *offer = WhiteOnMove(cmailOldMove) ?
13886               "White offers a draw" : "Black offers a draw";
13887             AppendComment(currentMove, offer, TRUE);
13888             DisplayComment(currentMove - 1, offer);
13889             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13890         } else {
13891             DisplayError(_("You must make your move before offering a draw"), 0);
13892             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13893         }
13894     } else if (first.offeredDraw) {
13895         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13896     } else {
13897         if (first.sendDrawOffers) {
13898             SendToProgram("draw\n", &first);
13899             userOfferedDraw = TRUE;
13900         }
13901     }
13902 }
13903
13904 void
13905 AdjournEvent()
13906 {
13907     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13908
13909     if (appData.icsActive) {
13910         SendToICS(ics_prefix);
13911         SendToICS("adjourn\n");
13912     } else {
13913         /* Currently GNU Chess doesn't offer or accept Adjourns */
13914     }
13915 }
13916
13917
13918 void
13919 AbortEvent()
13920 {
13921     /* Offer Abort or accept pending Abort offer from opponent */
13922
13923     if (appData.icsActive) {
13924         SendToICS(ics_prefix);
13925         SendToICS("abort\n");
13926     } else {
13927         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13928     }
13929 }
13930
13931 void
13932 ResignEvent()
13933 {
13934     /* Resign.  You can do this even if it's not your turn. */
13935
13936     if (appData.icsActive) {
13937         SendToICS(ics_prefix);
13938         SendToICS("resign\n");
13939     } else {
13940         switch (gameMode) {
13941           case MachinePlaysWhite:
13942             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13943             break;
13944           case MachinePlaysBlack:
13945             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13946             break;
13947           case EditGame:
13948             if (cmailMsgLoaded) {
13949                 TruncateGame();
13950                 if (WhiteOnMove(cmailOldMove)) {
13951                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13952                 } else {
13953                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13954                 }
13955                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13956             }
13957             break;
13958           default:
13959             break;
13960         }
13961     }
13962 }
13963
13964
13965 void
13966 StopObservingEvent()
13967 {
13968     /* Stop observing current games */
13969     SendToICS(ics_prefix);
13970     SendToICS("unobserve\n");
13971 }
13972
13973 void
13974 StopExaminingEvent()
13975 {
13976     /* Stop observing current game */
13977     SendToICS(ics_prefix);
13978     SendToICS("unexamine\n");
13979 }
13980
13981 void
13982 ForwardInner(target)
13983      int target;
13984 {
13985     int limit;
13986
13987     if (appData.debugMode)
13988         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13989                 target, currentMove, forwardMostMove);
13990
13991     if (gameMode == EditPosition)
13992       return;
13993
13994     if (gameMode == PlayFromGameFile && !pausing)
13995       PauseEvent();
13996
13997     if (gameMode == IcsExamining && pausing)
13998       limit = pauseExamForwardMostMove;
13999     else
14000       limit = forwardMostMove;
14001
14002     if (target > limit) target = limit;
14003
14004     if (target > 0 && moveList[target - 1][0]) {
14005         int fromX, fromY, toX, toY;
14006         toX = moveList[target - 1][2] - AAA;
14007         toY = moveList[target - 1][3] - ONE;
14008         if (moveList[target - 1][1] == '@') {
14009             if (appData.highlightLastMove) {
14010                 SetHighlights(-1, -1, toX, toY);
14011             }
14012         } else {
14013             fromX = moveList[target - 1][0] - AAA;
14014             fromY = moveList[target - 1][1] - ONE;
14015             if (target == currentMove + 1) {
14016                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14017             }
14018             if (appData.highlightLastMove) {
14019                 SetHighlights(fromX, fromY, toX, toY);
14020             }
14021         }
14022     }
14023     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14024         gameMode == Training || gameMode == PlayFromGameFile ||
14025         gameMode == AnalyzeFile) {
14026         while (currentMove < target) {
14027             SendMoveToProgram(currentMove++, &first);
14028         }
14029     } else {
14030         currentMove = target;
14031     }
14032
14033     if (gameMode == EditGame || gameMode == EndOfGame) {
14034         whiteTimeRemaining = timeRemaining[0][currentMove];
14035         blackTimeRemaining = timeRemaining[1][currentMove];
14036     }
14037     DisplayBothClocks();
14038     DisplayMove(currentMove - 1);
14039     DrawPosition(FALSE, boards[currentMove]);
14040     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14041     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14042         DisplayComment(currentMove - 1, commentList[currentMove]);
14043     }
14044     DisplayBook(currentMove);
14045 }
14046
14047
14048 void
14049 ForwardEvent()
14050 {
14051     if (gameMode == IcsExamining && !pausing) {
14052         SendToICS(ics_prefix);
14053         SendToICS("forward\n");
14054     } else {
14055         ForwardInner(currentMove + 1);
14056     }
14057 }
14058
14059 void
14060 ToEndEvent()
14061 {
14062     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14063         /* to optimze, we temporarily turn off analysis mode while we feed
14064          * the remaining moves to the engine. Otherwise we get analysis output
14065          * after each move.
14066          */
14067         if (first.analysisSupport) {
14068           SendToProgram("exit\nforce\n", &first);
14069           first.analyzing = FALSE;
14070         }
14071     }
14072
14073     if (gameMode == IcsExamining && !pausing) {
14074         SendToICS(ics_prefix);
14075         SendToICS("forward 999999\n");
14076     } else {
14077         ForwardInner(forwardMostMove);
14078     }
14079
14080     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14081         /* we have fed all the moves, so reactivate analysis mode */
14082         SendToProgram("analyze\n", &first);
14083         first.analyzing = TRUE;
14084         /*first.maybeThinking = TRUE;*/
14085         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14086     }
14087 }
14088
14089 void
14090 BackwardInner(target)
14091      int target;
14092 {
14093     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14094
14095     if (appData.debugMode)
14096         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14097                 target, currentMove, forwardMostMove);
14098
14099     if (gameMode == EditPosition) return;
14100     if (currentMove <= backwardMostMove) {
14101         ClearHighlights();
14102         DrawPosition(full_redraw, boards[currentMove]);
14103         return;
14104     }
14105     if (gameMode == PlayFromGameFile && !pausing)
14106       PauseEvent();
14107
14108     if (moveList[target][0]) {
14109         int fromX, fromY, toX, toY;
14110         toX = moveList[target][2] - AAA;
14111         toY = moveList[target][3] - ONE;
14112         if (moveList[target][1] == '@') {
14113             if (appData.highlightLastMove) {
14114                 SetHighlights(-1, -1, toX, toY);
14115             }
14116         } else {
14117             fromX = moveList[target][0] - AAA;
14118             fromY = moveList[target][1] - ONE;
14119             if (target == currentMove - 1) {
14120                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14121             }
14122             if (appData.highlightLastMove) {
14123                 SetHighlights(fromX, fromY, toX, toY);
14124             }
14125         }
14126     }
14127     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14128         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14129         while (currentMove > target) {
14130             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14131                 // null move cannot be undone. Reload program with move history before it.
14132                 int i;
14133                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14134                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14135                 }
14136                 SendBoard(&first, i); 
14137                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14138                 break;
14139             }
14140             SendToProgram("undo\n", &first);
14141             currentMove--;
14142         }
14143     } else {
14144         currentMove = target;
14145     }
14146
14147     if (gameMode == EditGame || gameMode == EndOfGame) {
14148         whiteTimeRemaining = timeRemaining[0][currentMove];
14149         blackTimeRemaining = timeRemaining[1][currentMove];
14150     }
14151     DisplayBothClocks();
14152     DisplayMove(currentMove - 1);
14153     DrawPosition(full_redraw, boards[currentMove]);
14154     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14155     // [HGM] PV info: routine tests if comment empty
14156     DisplayComment(currentMove - 1, commentList[currentMove]);
14157     DisplayBook(currentMove);
14158 }
14159
14160 void
14161 BackwardEvent()
14162 {
14163     if (gameMode == IcsExamining && !pausing) {
14164         SendToICS(ics_prefix);
14165         SendToICS("backward\n");
14166     } else {
14167         BackwardInner(currentMove - 1);
14168     }
14169 }
14170
14171 void
14172 ToStartEvent()
14173 {
14174     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14175         /* to optimize, we temporarily turn off analysis mode while we undo
14176          * all the moves. Otherwise we get analysis output after each undo.
14177          */
14178         if (first.analysisSupport) {
14179           SendToProgram("exit\nforce\n", &first);
14180           first.analyzing = FALSE;
14181         }
14182     }
14183
14184     if (gameMode == IcsExamining && !pausing) {
14185         SendToICS(ics_prefix);
14186         SendToICS("backward 999999\n");
14187     } else {
14188         BackwardInner(backwardMostMove);
14189     }
14190
14191     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14192         /* we have fed all the moves, so reactivate analysis mode */
14193         SendToProgram("analyze\n", &first);
14194         first.analyzing = TRUE;
14195         /*first.maybeThinking = TRUE;*/
14196         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14197     }
14198 }
14199
14200 void
14201 ToNrEvent(int to)
14202 {
14203   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14204   if (to >= forwardMostMove) to = forwardMostMove;
14205   if (to <= backwardMostMove) to = backwardMostMove;
14206   if (to < currentMove) {
14207     BackwardInner(to);
14208   } else {
14209     ForwardInner(to);
14210   }
14211 }
14212
14213 void
14214 RevertEvent(Boolean annotate)
14215 {
14216     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14217         return;
14218     }
14219     if (gameMode != IcsExamining) {
14220         DisplayError(_("You are not examining a game"), 0);
14221         return;
14222     }
14223     if (pausing) {
14224         DisplayError(_("You can't revert while pausing"), 0);
14225         return;
14226     }
14227     SendToICS(ics_prefix);
14228     SendToICS("revert\n");
14229 }
14230
14231 void
14232 RetractMoveEvent()
14233 {
14234     switch (gameMode) {
14235       case MachinePlaysWhite:
14236       case MachinePlaysBlack:
14237         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14238             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14239             return;
14240         }
14241         if (forwardMostMove < 2) return;
14242         currentMove = forwardMostMove = forwardMostMove - 2;
14243         whiteTimeRemaining = timeRemaining[0][currentMove];
14244         blackTimeRemaining = timeRemaining[1][currentMove];
14245         DisplayBothClocks();
14246         DisplayMove(currentMove - 1);
14247         ClearHighlights();/*!! could figure this out*/
14248         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14249         SendToProgram("remove\n", &first);
14250         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14251         break;
14252
14253       case BeginningOfGame:
14254       default:
14255         break;
14256
14257       case IcsPlayingWhite:
14258       case IcsPlayingBlack:
14259         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14260             SendToICS(ics_prefix);
14261             SendToICS("takeback 2\n");
14262         } else {
14263             SendToICS(ics_prefix);
14264             SendToICS("takeback 1\n");
14265         }
14266         break;
14267     }
14268 }
14269
14270 void
14271 MoveNowEvent()
14272 {
14273     ChessProgramState *cps;
14274
14275     switch (gameMode) {
14276       case MachinePlaysWhite:
14277         if (!WhiteOnMove(forwardMostMove)) {
14278             DisplayError(_("It is your turn"), 0);
14279             return;
14280         }
14281         cps = &first;
14282         break;
14283       case MachinePlaysBlack:
14284         if (WhiteOnMove(forwardMostMove)) {
14285             DisplayError(_("It is your turn"), 0);
14286             return;
14287         }
14288         cps = &first;
14289         break;
14290       case TwoMachinesPlay:
14291         if (WhiteOnMove(forwardMostMove) ==
14292             (first.twoMachinesColor[0] == 'w')) {
14293             cps = &first;
14294         } else {
14295             cps = &second;
14296         }
14297         break;
14298       case BeginningOfGame:
14299       default:
14300         return;
14301     }
14302     SendToProgram("?\n", cps);
14303 }
14304
14305 void
14306 TruncateGameEvent()
14307 {
14308     EditGameEvent();
14309     if (gameMode != EditGame) return;
14310     TruncateGame();
14311 }
14312
14313 void
14314 TruncateGame()
14315 {
14316     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14317     if (forwardMostMove > currentMove) {
14318         if (gameInfo.resultDetails != NULL) {
14319             free(gameInfo.resultDetails);
14320             gameInfo.resultDetails = NULL;
14321             gameInfo.result = GameUnfinished;
14322         }
14323         forwardMostMove = currentMove;
14324         HistorySet(parseList, backwardMostMove, forwardMostMove,
14325                    currentMove-1);
14326     }
14327 }
14328
14329 void
14330 HintEvent()
14331 {
14332     if (appData.noChessProgram) return;
14333     switch (gameMode) {
14334       case MachinePlaysWhite:
14335         if (WhiteOnMove(forwardMostMove)) {
14336             DisplayError(_("Wait until your turn"), 0);
14337             return;
14338         }
14339         break;
14340       case BeginningOfGame:
14341       case MachinePlaysBlack:
14342         if (!WhiteOnMove(forwardMostMove)) {
14343             DisplayError(_("Wait until your turn"), 0);
14344             return;
14345         }
14346         break;
14347       default:
14348         DisplayError(_("No hint available"), 0);
14349         return;
14350     }
14351     SendToProgram("hint\n", &first);
14352     hintRequested = TRUE;
14353 }
14354
14355 void
14356 BookEvent()
14357 {
14358     if (appData.noChessProgram) return;
14359     switch (gameMode) {
14360       case MachinePlaysWhite:
14361         if (WhiteOnMove(forwardMostMove)) {
14362             DisplayError(_("Wait until your turn"), 0);
14363             return;
14364         }
14365         break;
14366       case BeginningOfGame:
14367       case MachinePlaysBlack:
14368         if (!WhiteOnMove(forwardMostMove)) {
14369             DisplayError(_("Wait until your turn"), 0);
14370             return;
14371         }
14372         break;
14373       case EditPosition:
14374         EditPositionDone(TRUE);
14375         break;
14376       case TwoMachinesPlay:
14377         return;
14378       default:
14379         break;
14380     }
14381     SendToProgram("bk\n", &first);
14382     bookOutput[0] = NULLCHAR;
14383     bookRequested = TRUE;
14384 }
14385
14386 void
14387 AboutGameEvent()
14388 {
14389     char *tags = PGNTags(&gameInfo);
14390     TagsPopUp(tags, CmailMsg());
14391     free(tags);
14392 }
14393
14394 /* end button procedures */
14395
14396 void
14397 PrintPosition(fp, move)
14398      FILE *fp;
14399      int move;
14400 {
14401     int i, j;
14402
14403     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14404         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14405             char c = PieceToChar(boards[move][i][j]);
14406             fputc(c == 'x' ? '.' : c, fp);
14407             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14408         }
14409     }
14410     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14411       fprintf(fp, "white to play\n");
14412     else
14413       fprintf(fp, "black to play\n");
14414 }
14415
14416 void
14417 PrintOpponents(fp)
14418      FILE *fp;
14419 {
14420     if (gameInfo.white != NULL) {
14421         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14422     } else {
14423         fprintf(fp, "\n");
14424     }
14425 }
14426
14427 /* Find last component of program's own name, using some heuristics */
14428 void
14429 TidyProgramName(prog, host, buf)
14430      char *prog, *host, buf[MSG_SIZ];
14431 {
14432     char *p, *q;
14433     int local = (strcmp(host, "localhost") == 0);
14434     while (!local && (p = strchr(prog, ';')) != NULL) {
14435         p++;
14436         while (*p == ' ') p++;
14437         prog = p;
14438     }
14439     if (*prog == '"' || *prog == '\'') {
14440         q = strchr(prog + 1, *prog);
14441     } else {
14442         q = strchr(prog, ' ');
14443     }
14444     if (q == NULL) q = prog + strlen(prog);
14445     p = q;
14446     while (p >= prog && *p != '/' && *p != '\\') p--;
14447     p++;
14448     if(p == prog && *p == '"') p++;
14449     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14450     memcpy(buf, p, q - p);
14451     buf[q - p] = NULLCHAR;
14452     if (!local) {
14453         strcat(buf, "@");
14454         strcat(buf, host);
14455     }
14456 }
14457
14458 char *
14459 TimeControlTagValue()
14460 {
14461     char buf[MSG_SIZ];
14462     if (!appData.clockMode) {
14463       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14464     } else if (movesPerSession > 0) {
14465       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14466     } else if (timeIncrement == 0) {
14467       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14468     } else {
14469       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14470     }
14471     return StrSave(buf);
14472 }
14473
14474 void
14475 SetGameInfo()
14476 {
14477     /* This routine is used only for certain modes */
14478     VariantClass v = gameInfo.variant;
14479     ChessMove r = GameUnfinished;
14480     char *p = NULL;
14481
14482     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14483         r = gameInfo.result;
14484         p = gameInfo.resultDetails;
14485         gameInfo.resultDetails = NULL;
14486     }
14487     ClearGameInfo(&gameInfo);
14488     gameInfo.variant = v;
14489
14490     switch (gameMode) {
14491       case MachinePlaysWhite:
14492         gameInfo.event = StrSave( appData.pgnEventHeader );
14493         gameInfo.site = StrSave(HostName());
14494         gameInfo.date = PGNDate();
14495         gameInfo.round = StrSave("-");
14496         gameInfo.white = StrSave(first.tidy);
14497         gameInfo.black = StrSave(UserName());
14498         gameInfo.timeControl = TimeControlTagValue();
14499         break;
14500
14501       case MachinePlaysBlack:
14502         gameInfo.event = StrSave( appData.pgnEventHeader );
14503         gameInfo.site = StrSave(HostName());
14504         gameInfo.date = PGNDate();
14505         gameInfo.round = StrSave("-");
14506         gameInfo.white = StrSave(UserName());
14507         gameInfo.black = StrSave(first.tidy);
14508         gameInfo.timeControl = TimeControlTagValue();
14509         break;
14510
14511       case TwoMachinesPlay:
14512         gameInfo.event = StrSave( appData.pgnEventHeader );
14513         gameInfo.site = StrSave(HostName());
14514         gameInfo.date = PGNDate();
14515         if (roundNr > 0) {
14516             char buf[MSG_SIZ];
14517             snprintf(buf, MSG_SIZ, "%d", roundNr);
14518             gameInfo.round = StrSave(buf);
14519         } else {
14520             gameInfo.round = StrSave("-");
14521         }
14522         if (first.twoMachinesColor[0] == 'w') {
14523             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14524             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14525         } else {
14526             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14527             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14528         }
14529         gameInfo.timeControl = TimeControlTagValue();
14530         break;
14531
14532       case EditGame:
14533         gameInfo.event = StrSave("Edited game");
14534         gameInfo.site = StrSave(HostName());
14535         gameInfo.date = PGNDate();
14536         gameInfo.round = StrSave("-");
14537         gameInfo.white = StrSave("-");
14538         gameInfo.black = StrSave("-");
14539         gameInfo.result = r;
14540         gameInfo.resultDetails = p;
14541         break;
14542
14543       case EditPosition:
14544         gameInfo.event = StrSave("Edited position");
14545         gameInfo.site = StrSave(HostName());
14546         gameInfo.date = PGNDate();
14547         gameInfo.round = StrSave("-");
14548         gameInfo.white = StrSave("-");
14549         gameInfo.black = StrSave("-");
14550         break;
14551
14552       case IcsPlayingWhite:
14553       case IcsPlayingBlack:
14554       case IcsObserving:
14555       case IcsExamining:
14556         break;
14557
14558       case PlayFromGameFile:
14559         gameInfo.event = StrSave("Game from non-PGN file");
14560         gameInfo.site = StrSave(HostName());
14561         gameInfo.date = PGNDate();
14562         gameInfo.round = StrSave("-");
14563         gameInfo.white = StrSave("?");
14564         gameInfo.black = StrSave("?");
14565         break;
14566
14567       default:
14568         break;
14569     }
14570 }
14571
14572 void
14573 ReplaceComment(index, text)
14574      int index;
14575      char *text;
14576 {
14577     int len;
14578     char *p;
14579     float score;
14580
14581     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14582        pvInfoList[index-1].depth == len &&
14583        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14584        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14585     while (*text == '\n') text++;
14586     len = strlen(text);
14587     while (len > 0 && text[len - 1] == '\n') len--;
14588
14589     if (commentList[index] != NULL)
14590       free(commentList[index]);
14591
14592     if (len == 0) {
14593         commentList[index] = NULL;
14594         return;
14595     }
14596   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14597       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14598       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14599     commentList[index] = (char *) malloc(len + 2);
14600     strncpy(commentList[index], text, len);
14601     commentList[index][len] = '\n';
14602     commentList[index][len + 1] = NULLCHAR;
14603   } else {
14604     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14605     char *p;
14606     commentList[index] = (char *) malloc(len + 7);
14607     safeStrCpy(commentList[index], "{\n", 3);
14608     safeStrCpy(commentList[index]+2, text, len+1);
14609     commentList[index][len+2] = NULLCHAR;
14610     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14611     strcat(commentList[index], "\n}\n");
14612   }
14613 }
14614
14615 void
14616 CrushCRs(text)
14617      char *text;
14618 {
14619   char *p = text;
14620   char *q = text;
14621   char ch;
14622
14623   do {
14624     ch = *p++;
14625     if (ch == '\r') continue;
14626     *q++ = ch;
14627   } while (ch != '\0');
14628 }
14629
14630 void
14631 AppendComment(index, text, addBraces)
14632      int index;
14633      char *text;
14634      Boolean addBraces; // [HGM] braces: tells if we should add {}
14635 {
14636     int oldlen, len;
14637     char *old;
14638
14639 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14640     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14641
14642     CrushCRs(text);
14643     while (*text == '\n') text++;
14644     len = strlen(text);
14645     while (len > 0 && text[len - 1] == '\n') len--;
14646
14647     if (len == 0) return;
14648
14649     if (commentList[index] != NULL) {
14650       Boolean addClosingBrace = addBraces;
14651         old = commentList[index];
14652         oldlen = strlen(old);
14653         while(commentList[index][oldlen-1] ==  '\n')
14654           commentList[index][--oldlen] = NULLCHAR;
14655         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14656         safeStrCpy(commentList[index], old, oldlen + len + 6);
14657         free(old);
14658         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14659         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14660           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14661           while (*text == '\n') { text++; len--; }
14662           commentList[index][--oldlen] = NULLCHAR;
14663       }
14664         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14665         else          strcat(commentList[index], "\n");
14666         strcat(commentList[index], text);
14667         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14668         else          strcat(commentList[index], "\n");
14669     } else {
14670         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14671         if(addBraces)
14672           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14673         else commentList[index][0] = NULLCHAR;
14674         strcat(commentList[index], text);
14675         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14676         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14677     }
14678 }
14679
14680 static char * FindStr( char * text, char * sub_text )
14681 {
14682     char * result = strstr( text, sub_text );
14683
14684     if( result != NULL ) {
14685         result += strlen( sub_text );
14686     }
14687
14688     return result;
14689 }
14690
14691 /* [AS] Try to extract PV info from PGN comment */
14692 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14693 char *GetInfoFromComment( int index, char * text )
14694 {
14695     char * sep = text, *p;
14696
14697     if( text != NULL && index > 0 ) {
14698         int score = 0;
14699         int depth = 0;
14700         int time = -1, sec = 0, deci;
14701         char * s_eval = FindStr( text, "[%eval " );
14702         char * s_emt = FindStr( text, "[%emt " );
14703
14704         if( s_eval != NULL || s_emt != NULL ) {
14705             /* New style */
14706             char delim;
14707
14708             if( s_eval != NULL ) {
14709                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14710                     return text;
14711                 }
14712
14713                 if( delim != ']' ) {
14714                     return text;
14715                 }
14716             }
14717
14718             if( s_emt != NULL ) {
14719             }
14720                 return text;
14721         }
14722         else {
14723             /* We expect something like: [+|-]nnn.nn/dd */
14724             int score_lo = 0;
14725
14726             if(*text != '{') return text; // [HGM] braces: must be normal comment
14727
14728             sep = strchr( text, '/' );
14729             if( sep == NULL || sep < (text+4) ) {
14730                 return text;
14731             }
14732
14733             p = text;
14734             if(p[1] == '(') { // comment starts with PV
14735                p = strchr(p, ')'); // locate end of PV
14736                if(p == NULL || sep < p+5) return text;
14737                // at this point we have something like "{(.*) +0.23/6 ..."
14738                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14739                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14740                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14741             }
14742             time = -1; sec = -1; deci = -1;
14743             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14744                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14745                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14746                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14747                 return text;
14748             }
14749
14750             if( score_lo < 0 || score_lo >= 100 ) {
14751                 return text;
14752             }
14753
14754             if(sec >= 0) time = 600*time + 10*sec; else
14755             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14756
14757             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14758
14759             /* [HGM] PV time: now locate end of PV info */
14760             while( *++sep >= '0' && *sep <= '9'); // strip depth
14761             if(time >= 0)
14762             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14763             if(sec >= 0)
14764             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14765             if(deci >= 0)
14766             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14767             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14768         }
14769
14770         if( depth <= 0 ) {
14771             return text;
14772         }
14773
14774         if( time < 0 ) {
14775             time = -1;
14776         }
14777
14778         pvInfoList[index-1].depth = depth;
14779         pvInfoList[index-1].score = score;
14780         pvInfoList[index-1].time  = 10*time; // centi-sec
14781         if(*sep == '}') *sep = 0; else *--sep = '{';
14782         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14783     }
14784     return sep;
14785 }
14786
14787 void
14788 SendToProgram(message, cps)
14789      char *message;
14790      ChessProgramState *cps;
14791 {
14792     int count, outCount, error;
14793     char buf[MSG_SIZ];
14794
14795     if (cps->pr == NULL) return;
14796     Attention(cps);
14797
14798     if (appData.debugMode) {
14799         TimeMark now;
14800         GetTimeMark(&now);
14801         fprintf(debugFP, "%ld >%-6s: %s",
14802                 SubtractTimeMarks(&now, &programStartTime),
14803                 cps->which, message);
14804     }
14805
14806     count = strlen(message);
14807     outCount = OutputToProcess(cps->pr, message, count, &error);
14808     if (outCount < count && !exiting
14809                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14810       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14811       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14812         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14813             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14814                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14815                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14816                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14817             } else {
14818                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14819                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14820                 gameInfo.result = res;
14821             }
14822             gameInfo.resultDetails = StrSave(buf);
14823         }
14824         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14825         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14826     }
14827 }
14828
14829 void
14830 ReceiveFromProgram(isr, closure, message, count, error)
14831      InputSourceRef isr;
14832      VOIDSTAR closure;
14833      char *message;
14834      int count;
14835      int error;
14836 {
14837     char *end_str;
14838     char buf[MSG_SIZ];
14839     ChessProgramState *cps = (ChessProgramState *)closure;
14840
14841     if (isr != cps->isr) return; /* Killed intentionally */
14842     if (count <= 0) {
14843         if (count == 0) {
14844             RemoveInputSource(cps->isr);
14845             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14846             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14847                     _(cps->which), cps->program);
14848         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14849                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14850                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14851                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14852                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14853                 } else {
14854                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14855                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14856                     gameInfo.result = res;
14857                 }
14858                 gameInfo.resultDetails = StrSave(buf);
14859             }
14860             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14861             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14862         } else {
14863             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14864                     _(cps->which), cps->program);
14865             RemoveInputSource(cps->isr);
14866
14867             /* [AS] Program is misbehaving badly... kill it */
14868             if( count == -2 ) {
14869                 DestroyChildProcess( cps->pr, 9 );
14870                 cps->pr = NoProc;
14871             }
14872
14873             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14874         }
14875         return;
14876     }
14877
14878     if ((end_str = strchr(message, '\r')) != NULL)
14879       *end_str = NULLCHAR;
14880     if ((end_str = strchr(message, '\n')) != NULL)
14881       *end_str = NULLCHAR;
14882
14883     if (appData.debugMode) {
14884         TimeMark now; int print = 1;
14885         char *quote = ""; char c; int i;
14886
14887         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14888                 char start = message[0];
14889                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14890                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14891                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14892                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14893                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14894                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14895                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14896                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14897                    sscanf(message, "hint: %c", &c)!=1 && 
14898                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14899                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14900                     print = (appData.engineComments >= 2);
14901                 }
14902                 message[0] = start; // restore original message
14903         }
14904         if(print) {
14905                 GetTimeMark(&now);
14906                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14907                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14908                         quote,
14909                         message);
14910         }
14911     }
14912
14913     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14914     if (appData.icsEngineAnalyze) {
14915         if (strstr(message, "whisper") != NULL ||
14916              strstr(message, "kibitz") != NULL ||
14917             strstr(message, "tellics") != NULL) return;
14918     }
14919
14920     HandleMachineMove(message, cps);
14921 }
14922
14923
14924 void
14925 SendTimeControl(cps, mps, tc, inc, sd, st)
14926      ChessProgramState *cps;
14927      int mps, inc, sd, st;
14928      long tc;
14929 {
14930     char buf[MSG_SIZ];
14931     int seconds;
14932
14933     if( timeControl_2 > 0 ) {
14934         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14935             tc = timeControl_2;
14936         }
14937     }
14938     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14939     inc /= cps->timeOdds;
14940     st  /= cps->timeOdds;
14941
14942     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14943
14944     if (st > 0) {
14945       /* Set exact time per move, normally using st command */
14946       if (cps->stKludge) {
14947         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14948         seconds = st % 60;
14949         if (seconds == 0) {
14950           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14951         } else {
14952           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14953         }
14954       } else {
14955         snprintf(buf, MSG_SIZ, "st %d\n", st);
14956       }
14957     } else {
14958       /* Set conventional or incremental time control, using level command */
14959       if (seconds == 0) {
14960         /* Note old gnuchess bug -- minutes:seconds used to not work.
14961            Fixed in later versions, but still avoid :seconds
14962            when seconds is 0. */
14963         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14964       } else {
14965         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14966                  seconds, inc/1000.);
14967       }
14968     }
14969     SendToProgram(buf, cps);
14970
14971     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14972     /* Orthogonally, limit search to given depth */
14973     if (sd > 0) {
14974       if (cps->sdKludge) {
14975         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14976       } else {
14977         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14978       }
14979       SendToProgram(buf, cps);
14980     }
14981
14982     if(cps->nps >= 0) { /* [HGM] nps */
14983         if(cps->supportsNPS == FALSE)
14984           cps->nps = -1; // don't use if engine explicitly says not supported!
14985         else {
14986           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14987           SendToProgram(buf, cps);
14988         }
14989     }
14990 }
14991
14992 ChessProgramState *WhitePlayer()
14993 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14994 {
14995     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14996        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14997         return &second;
14998     return &first;
14999 }
15000
15001 void
15002 SendTimeRemaining(cps, machineWhite)
15003      ChessProgramState *cps;
15004      int /*boolean*/ machineWhite;
15005 {
15006     char message[MSG_SIZ];
15007     long time, otime;
15008
15009     /* Note: this routine must be called when the clocks are stopped
15010        or when they have *just* been set or switched; otherwise
15011        it will be off by the time since the current tick started.
15012     */
15013     if (machineWhite) {
15014         time = whiteTimeRemaining / 10;
15015         otime = blackTimeRemaining / 10;
15016     } else {
15017         time = blackTimeRemaining / 10;
15018         otime = whiteTimeRemaining / 10;
15019     }
15020     /* [HGM] translate opponent's time by time-odds factor */
15021     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15022     if (appData.debugMode) {
15023         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15024     }
15025
15026     if (time <= 0) time = 1;
15027     if (otime <= 0) otime = 1;
15028
15029     snprintf(message, MSG_SIZ, "time %ld\n", time);
15030     SendToProgram(message, cps);
15031
15032     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15033     SendToProgram(message, cps);
15034 }
15035
15036 int
15037 BoolFeature(p, name, loc, cps)
15038      char **p;
15039      char *name;
15040      int *loc;
15041      ChessProgramState *cps;
15042 {
15043   char buf[MSG_SIZ];
15044   int len = strlen(name);
15045   int val;
15046
15047   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15048     (*p) += len + 1;
15049     sscanf(*p, "%d", &val);
15050     *loc = (val != 0);
15051     while (**p && **p != ' ')
15052       (*p)++;
15053     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15054     SendToProgram(buf, cps);
15055     return TRUE;
15056   }
15057   return FALSE;
15058 }
15059
15060 int
15061 IntFeature(p, name, loc, cps)
15062      char **p;
15063      char *name;
15064      int *loc;
15065      ChessProgramState *cps;
15066 {
15067   char buf[MSG_SIZ];
15068   int len = strlen(name);
15069   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15070     (*p) += len + 1;
15071     sscanf(*p, "%d", loc);
15072     while (**p && **p != ' ') (*p)++;
15073     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15074     SendToProgram(buf, cps);
15075     return TRUE;
15076   }
15077   return FALSE;
15078 }
15079
15080 int
15081 StringFeature(p, name, loc, cps)
15082      char **p;
15083      char *name;
15084      char loc[];
15085      ChessProgramState *cps;
15086 {
15087   char buf[MSG_SIZ];
15088   int len = strlen(name);
15089   if (strncmp((*p), name, len) == 0
15090       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15091     (*p) += len + 2;
15092     sscanf(*p, "%[^\"]", loc);
15093     while (**p && **p != '\"') (*p)++;
15094     if (**p == '\"') (*p)++;
15095     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15096     SendToProgram(buf, cps);
15097     return TRUE;
15098   }
15099   return FALSE;
15100 }
15101
15102 int
15103 ParseOption(Option *opt, ChessProgramState *cps)
15104 // [HGM] options: process the string that defines an engine option, and determine
15105 // name, type, default value, and allowed value range
15106 {
15107         char *p, *q, buf[MSG_SIZ];
15108         int n, min = (-1)<<31, max = 1<<31, def;
15109
15110         if(p = strstr(opt->name, " -spin ")) {
15111             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15112             if(max < min) max = min; // enforce consistency
15113             if(def < min) def = min;
15114             if(def > max) def = max;
15115             opt->value = def;
15116             opt->min = min;
15117             opt->max = max;
15118             opt->type = Spin;
15119         } else if((p = strstr(opt->name, " -slider "))) {
15120             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15121             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15122             if(max < min) max = min; // enforce consistency
15123             if(def < min) def = min;
15124             if(def > max) def = max;
15125             opt->value = def;
15126             opt->min = min;
15127             opt->max = max;
15128             opt->type = Spin; // Slider;
15129         } else if((p = strstr(opt->name, " -string "))) {
15130             opt->textValue = p+9;
15131             opt->type = TextBox;
15132         } else if((p = strstr(opt->name, " -file "))) {
15133             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15134             opt->textValue = p+7;
15135             opt->type = FileName; // FileName;
15136         } else if((p = strstr(opt->name, " -path "))) {
15137             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15138             opt->textValue = p+7;
15139             opt->type = PathName; // PathName;
15140         } else if(p = strstr(opt->name, " -check ")) {
15141             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15142             opt->value = (def != 0);
15143             opt->type = CheckBox;
15144         } else if(p = strstr(opt->name, " -combo ")) {
15145             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15146             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15147             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15148             opt->value = n = 0;
15149             while(q = StrStr(q, " /// ")) {
15150                 n++; *q = 0;    // count choices, and null-terminate each of them
15151                 q += 5;
15152                 if(*q == '*') { // remember default, which is marked with * prefix
15153                     q++;
15154                     opt->value = n;
15155                 }
15156                 cps->comboList[cps->comboCnt++] = q;
15157             }
15158             cps->comboList[cps->comboCnt++] = NULL;
15159             opt->max = n + 1;
15160             opt->type = ComboBox;
15161         } else if(p = strstr(opt->name, " -button")) {
15162             opt->type = Button;
15163         } else if(p = strstr(opt->name, " -save")) {
15164             opt->type = SaveButton;
15165         } else return FALSE;
15166         *p = 0; // terminate option name
15167         // now look if the command-line options define a setting for this engine option.
15168         if(cps->optionSettings && cps->optionSettings[0])
15169             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15170         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15171           snprintf(buf, MSG_SIZ, "option %s", p);
15172                 if(p = strstr(buf, ",")) *p = 0;
15173                 if(q = strchr(buf, '=')) switch(opt->type) {
15174                     case ComboBox:
15175                         for(n=0; n<opt->max; n++)
15176                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15177                         break;
15178                     case TextBox:
15179                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15180                         break;
15181                     case Spin:
15182                     case CheckBox:
15183                         opt->value = atoi(q+1);
15184                     default:
15185                         break;
15186                 }
15187                 strcat(buf, "\n");
15188                 SendToProgram(buf, cps);
15189         }
15190         return TRUE;
15191 }
15192
15193 void
15194 FeatureDone(cps, val)
15195      ChessProgramState* cps;
15196      int val;
15197 {
15198   DelayedEventCallback cb = GetDelayedEvent();
15199   if ((cb == InitBackEnd3 && cps == &first) ||
15200       (cb == SettingsMenuIfReady && cps == &second) ||
15201       (cb == LoadEngine) ||
15202       (cb == TwoMachinesEventIfReady)) {
15203     CancelDelayedEvent();
15204     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15205   }
15206   cps->initDone = val;
15207 }
15208
15209 /* Parse feature command from engine */
15210 void
15211 ParseFeatures(args, cps)
15212      char* args;
15213      ChessProgramState *cps;
15214 {
15215   char *p = args;
15216   char *q;
15217   int val;
15218   char buf[MSG_SIZ];
15219
15220   for (;;) {
15221     while (*p == ' ') p++;
15222     if (*p == NULLCHAR) return;
15223
15224     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15225     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15226     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15227     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15228     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15229     if (BoolFeature(&p, "reuse", &val, cps)) {
15230       /* Engine can disable reuse, but can't enable it if user said no */
15231       if (!val) cps->reuse = FALSE;
15232       continue;
15233     }
15234     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15235     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15236       if (gameMode == TwoMachinesPlay) {
15237         DisplayTwoMachinesTitle();
15238       } else {
15239         DisplayTitle("");
15240       }
15241       continue;
15242     }
15243     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15244     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15245     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15246     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15247     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15248     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15249     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15250     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15251     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15252     if (IntFeature(&p, "done", &val, cps)) {
15253       FeatureDone(cps, val);
15254       continue;
15255     }
15256     /* Added by Tord: */
15257     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15258     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15259     /* End of additions by Tord */
15260
15261     /* [HGM] added features: */
15262     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15263     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15264     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15265     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15266     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15267     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15268     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15269         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15270           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15271             SendToProgram(buf, cps);
15272             continue;
15273         }
15274         if(cps->nrOptions >= MAX_OPTIONS) {
15275             cps->nrOptions--;
15276             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15277             DisplayError(buf, 0);
15278         }
15279         continue;
15280     }
15281     /* End of additions by HGM */
15282
15283     /* unknown feature: complain and skip */
15284     q = p;
15285     while (*q && *q != '=') q++;
15286     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15287     SendToProgram(buf, cps);
15288     p = q;
15289     if (*p == '=') {
15290       p++;
15291       if (*p == '\"') {
15292         p++;
15293         while (*p && *p != '\"') p++;
15294         if (*p == '\"') p++;
15295       } else {
15296         while (*p && *p != ' ') p++;
15297       }
15298     }
15299   }
15300
15301 }
15302
15303 void
15304 PeriodicUpdatesEvent(newState)
15305      int newState;
15306 {
15307     if (newState == appData.periodicUpdates)
15308       return;
15309
15310     appData.periodicUpdates=newState;
15311
15312     /* Display type changes, so update it now */
15313 //    DisplayAnalysis();
15314
15315     /* Get the ball rolling again... */
15316     if (newState) {
15317         AnalysisPeriodicEvent(1);
15318         StartAnalysisClock();
15319     }
15320 }
15321
15322 void
15323 PonderNextMoveEvent(newState)
15324      int newState;
15325 {
15326     if (newState == appData.ponderNextMove) return;
15327     if (gameMode == EditPosition) EditPositionDone(TRUE);
15328     if (newState) {
15329         SendToProgram("hard\n", &first);
15330         if (gameMode == TwoMachinesPlay) {
15331             SendToProgram("hard\n", &second);
15332         }
15333     } else {
15334         SendToProgram("easy\n", &first);
15335         thinkOutput[0] = NULLCHAR;
15336         if (gameMode == TwoMachinesPlay) {
15337             SendToProgram("easy\n", &second);
15338         }
15339     }
15340     appData.ponderNextMove = newState;
15341 }
15342
15343 void
15344 NewSettingEvent(option, feature, command, value)
15345      char *command;
15346      int option, value, *feature;
15347 {
15348     char buf[MSG_SIZ];
15349
15350     if (gameMode == EditPosition) EditPositionDone(TRUE);
15351     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15352     if(feature == NULL || *feature) SendToProgram(buf, &first);
15353     if (gameMode == TwoMachinesPlay) {
15354         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15355     }
15356 }
15357
15358 void
15359 ShowThinkingEvent()
15360 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15361 {
15362     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15363     int newState = appData.showThinking
15364         // [HGM] thinking: other features now need thinking output as well
15365         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15366
15367     if (oldState == newState) return;
15368     oldState = newState;
15369     if (gameMode == EditPosition) EditPositionDone(TRUE);
15370     if (oldState) {
15371         SendToProgram("post\n", &first);
15372         if (gameMode == TwoMachinesPlay) {
15373             SendToProgram("post\n", &second);
15374         }
15375     } else {
15376         SendToProgram("nopost\n", &first);
15377         thinkOutput[0] = NULLCHAR;
15378         if (gameMode == TwoMachinesPlay) {
15379             SendToProgram("nopost\n", &second);
15380         }
15381     }
15382 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15383 }
15384
15385 void
15386 AskQuestionEvent(title, question, replyPrefix, which)
15387      char *title; char *question; char *replyPrefix; char *which;
15388 {
15389   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15390   if (pr == NoProc) return;
15391   AskQuestion(title, question, replyPrefix, pr);
15392 }
15393
15394 void
15395 TypeInEvent(char firstChar)
15396 {
15397     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15398         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15399         gameMode == AnalyzeMode || gameMode == EditGame || 
15400         gameMode == EditPosition || gameMode == IcsExamining ||
15401         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15402         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15403                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15404                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15405         gameMode == Training) PopUpMoveDialog(firstChar);
15406 }
15407
15408 void
15409 TypeInDoneEvent(char *move)
15410 {
15411         Board board;
15412         int n, fromX, fromY, toX, toY;
15413         char promoChar;
15414         ChessMove moveType;
15415
15416         // [HGM] FENedit
15417         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15418                 EditPositionPasteFEN(move);
15419                 return;
15420         }
15421         // [HGM] movenum: allow move number to be typed in any mode
15422         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15423           ToNrEvent(2*n-1);
15424           return;
15425         }
15426
15427       if (gameMode != EditGame && currentMove != forwardMostMove && 
15428         gameMode != Training) {
15429         DisplayMoveError(_("Displayed move is not current"));
15430       } else {
15431         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15432           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15433         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15434         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15435           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15436           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15437         } else {
15438           DisplayMoveError(_("Could not parse move"));
15439         }
15440       }
15441 }
15442
15443 void
15444 DisplayMove(moveNumber)
15445      int moveNumber;
15446 {
15447     char message[MSG_SIZ];
15448     char res[MSG_SIZ];
15449     char cpThinkOutput[MSG_SIZ];
15450
15451     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15452
15453     if (moveNumber == forwardMostMove - 1 ||
15454         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15455
15456         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15457
15458         if (strchr(cpThinkOutput, '\n')) {
15459             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15460         }
15461     } else {
15462         *cpThinkOutput = NULLCHAR;
15463     }
15464
15465     /* [AS] Hide thinking from human user */
15466     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15467         *cpThinkOutput = NULLCHAR;
15468         if( thinkOutput[0] != NULLCHAR ) {
15469             int i;
15470
15471             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15472                 cpThinkOutput[i] = '.';
15473             }
15474             cpThinkOutput[i] = NULLCHAR;
15475             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15476         }
15477     }
15478
15479     if (moveNumber == forwardMostMove - 1 &&
15480         gameInfo.resultDetails != NULL) {
15481         if (gameInfo.resultDetails[0] == NULLCHAR) {
15482           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15483         } else {
15484           snprintf(res, MSG_SIZ, " {%s} %s",
15485                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15486         }
15487     } else {
15488         res[0] = NULLCHAR;
15489     }
15490
15491     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15492         DisplayMessage(res, cpThinkOutput);
15493     } else {
15494       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15495                 WhiteOnMove(moveNumber) ? " " : ".. ",
15496                 parseList[moveNumber], res);
15497         DisplayMessage(message, cpThinkOutput);
15498     }
15499 }
15500
15501 void
15502 DisplayComment(moveNumber, text)
15503      int moveNumber;
15504      char *text;
15505 {
15506     char title[MSG_SIZ];
15507
15508     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15509       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15510     } else {
15511       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15512               WhiteOnMove(moveNumber) ? " " : ".. ",
15513               parseList[moveNumber]);
15514     }
15515     if (text != NULL && (appData.autoDisplayComment || commentUp))
15516         CommentPopUp(title, text);
15517 }
15518
15519 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15520  * might be busy thinking or pondering.  It can be omitted if your
15521  * gnuchess is configured to stop thinking immediately on any user
15522  * input.  However, that gnuchess feature depends on the FIONREAD
15523  * ioctl, which does not work properly on some flavors of Unix.
15524  */
15525 void
15526 Attention(cps)
15527      ChessProgramState *cps;
15528 {
15529 #if ATTENTION
15530     if (!cps->useSigint) return;
15531     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15532     switch (gameMode) {
15533       case MachinePlaysWhite:
15534       case MachinePlaysBlack:
15535       case TwoMachinesPlay:
15536       case IcsPlayingWhite:
15537       case IcsPlayingBlack:
15538       case AnalyzeMode:
15539       case AnalyzeFile:
15540         /* Skip if we know it isn't thinking */
15541         if (!cps->maybeThinking) return;
15542         if (appData.debugMode)
15543           fprintf(debugFP, "Interrupting %s\n", cps->which);
15544         InterruptChildProcess(cps->pr);
15545         cps->maybeThinking = FALSE;
15546         break;
15547       default:
15548         break;
15549     }
15550 #endif /*ATTENTION*/
15551 }
15552
15553 int
15554 CheckFlags()
15555 {
15556     if (whiteTimeRemaining <= 0) {
15557         if (!whiteFlag) {
15558             whiteFlag = TRUE;
15559             if (appData.icsActive) {
15560                 if (appData.autoCallFlag &&
15561                     gameMode == IcsPlayingBlack && !blackFlag) {
15562                   SendToICS(ics_prefix);
15563                   SendToICS("flag\n");
15564                 }
15565             } else {
15566                 if (blackFlag) {
15567                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15568                 } else {
15569                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15570                     if (appData.autoCallFlag) {
15571                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15572                         return TRUE;
15573                     }
15574                 }
15575             }
15576         }
15577     }
15578     if (blackTimeRemaining <= 0) {
15579         if (!blackFlag) {
15580             blackFlag = TRUE;
15581             if (appData.icsActive) {
15582                 if (appData.autoCallFlag &&
15583                     gameMode == IcsPlayingWhite && !whiteFlag) {
15584                   SendToICS(ics_prefix);
15585                   SendToICS("flag\n");
15586                 }
15587             } else {
15588                 if (whiteFlag) {
15589                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15590                 } else {
15591                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15592                     if (appData.autoCallFlag) {
15593                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15594                         return TRUE;
15595                     }
15596                 }
15597             }
15598         }
15599     }
15600     return FALSE;
15601 }
15602
15603 void
15604 CheckTimeControl()
15605 {
15606     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15607         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15608
15609     /*
15610      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15611      */
15612     if ( !WhiteOnMove(forwardMostMove) ) {
15613         /* White made time control */
15614         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15615         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15616         /* [HGM] time odds: correct new time quota for time odds! */
15617                                             / WhitePlayer()->timeOdds;
15618         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15619     } else {
15620         lastBlack -= blackTimeRemaining;
15621         /* Black made time control */
15622         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15623                                             / WhitePlayer()->other->timeOdds;
15624         lastWhite = whiteTimeRemaining;
15625     }
15626 }
15627
15628 void
15629 DisplayBothClocks()
15630 {
15631     int wom = gameMode == EditPosition ?
15632       !blackPlaysFirst : WhiteOnMove(currentMove);
15633     DisplayWhiteClock(whiteTimeRemaining, wom);
15634     DisplayBlackClock(blackTimeRemaining, !wom);
15635 }
15636
15637
15638 /* Timekeeping seems to be a portability nightmare.  I think everyone
15639    has ftime(), but I'm really not sure, so I'm including some ifdefs
15640    to use other calls if you don't.  Clocks will be less accurate if
15641    you have neither ftime nor gettimeofday.
15642 */
15643
15644 /* VS 2008 requires the #include outside of the function */
15645 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15646 #include <sys/timeb.h>
15647 #endif
15648
15649 /* Get the current time as a TimeMark */
15650 void
15651 GetTimeMark(tm)
15652      TimeMark *tm;
15653 {
15654 #if HAVE_GETTIMEOFDAY
15655
15656     struct timeval timeVal;
15657     struct timezone timeZone;
15658
15659     gettimeofday(&timeVal, &timeZone);
15660     tm->sec = (long) timeVal.tv_sec;
15661     tm->ms = (int) (timeVal.tv_usec / 1000L);
15662
15663 #else /*!HAVE_GETTIMEOFDAY*/
15664 #if HAVE_FTIME
15665
15666 // include <sys/timeb.h> / moved to just above start of function
15667     struct timeb timeB;
15668
15669     ftime(&timeB);
15670     tm->sec = (long) timeB.time;
15671     tm->ms = (int) timeB.millitm;
15672
15673 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15674     tm->sec = (long) time(NULL);
15675     tm->ms = 0;
15676 #endif
15677 #endif
15678 }
15679
15680 /* Return the difference in milliseconds between two
15681    time marks.  We assume the difference will fit in a long!
15682 */
15683 long
15684 SubtractTimeMarks(tm2, tm1)
15685      TimeMark *tm2, *tm1;
15686 {
15687     return 1000L*(tm2->sec - tm1->sec) +
15688            (long) (tm2->ms - tm1->ms);
15689 }
15690
15691
15692 /*
15693  * Code to manage the game clocks.
15694  *
15695  * In tournament play, black starts the clock and then white makes a move.
15696  * We give the human user a slight advantage if he is playing white---the
15697  * clocks don't run until he makes his first move, so it takes zero time.
15698  * Also, we don't account for network lag, so we could get out of sync
15699  * with GNU Chess's clock -- but then, referees are always right.
15700  */
15701
15702 static TimeMark tickStartTM;
15703 static long intendedTickLength;
15704
15705 long
15706 NextTickLength(timeRemaining)
15707      long timeRemaining;
15708 {
15709     long nominalTickLength, nextTickLength;
15710
15711     if (timeRemaining > 0L && timeRemaining <= 10000L)
15712       nominalTickLength = 100L;
15713     else
15714       nominalTickLength = 1000L;
15715     nextTickLength = timeRemaining % nominalTickLength;
15716     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15717
15718     return nextTickLength;
15719 }
15720
15721 /* Adjust clock one minute up or down */
15722 void
15723 AdjustClock(Boolean which, int dir)
15724 {
15725     if(which) blackTimeRemaining += 60000*dir;
15726     else      whiteTimeRemaining += 60000*dir;
15727     DisplayBothClocks();
15728 }
15729
15730 /* Stop clocks and reset to a fresh time control */
15731 void
15732 ResetClocks()
15733 {
15734     (void) StopClockTimer();
15735     if (appData.icsActive) {
15736         whiteTimeRemaining = blackTimeRemaining = 0;
15737     } else if (searchTime) {
15738         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15739         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15740     } else { /* [HGM] correct new time quote for time odds */
15741         whiteTC = blackTC = fullTimeControlString;
15742         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15743         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15744     }
15745     if (whiteFlag || blackFlag) {
15746         DisplayTitle("");
15747         whiteFlag = blackFlag = FALSE;
15748     }
15749     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15750     DisplayBothClocks();
15751 }
15752
15753 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15754
15755 /* Decrement running clock by amount of time that has passed */
15756 void
15757 DecrementClocks()
15758 {
15759     long timeRemaining;
15760     long lastTickLength, fudge;
15761     TimeMark now;
15762
15763     if (!appData.clockMode) return;
15764     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15765
15766     GetTimeMark(&now);
15767
15768     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15769
15770     /* Fudge if we woke up a little too soon */
15771     fudge = intendedTickLength - lastTickLength;
15772     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15773
15774     if (WhiteOnMove(forwardMostMove)) {
15775         if(whiteNPS >= 0) lastTickLength = 0;
15776         timeRemaining = whiteTimeRemaining -= lastTickLength;
15777         if(timeRemaining < 0 && !appData.icsActive) {
15778             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15779             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15780                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15781                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15782             }
15783         }
15784         DisplayWhiteClock(whiteTimeRemaining - fudge,
15785                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15786     } else {
15787         if(blackNPS >= 0) lastTickLength = 0;
15788         timeRemaining = blackTimeRemaining -= lastTickLength;
15789         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15790             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15791             if(suddenDeath) {
15792                 blackStartMove = forwardMostMove;
15793                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15794             }
15795         }
15796         DisplayBlackClock(blackTimeRemaining - fudge,
15797                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15798     }
15799     if (CheckFlags()) return;
15800
15801     tickStartTM = now;
15802     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15803     StartClockTimer(intendedTickLength);
15804
15805     /* if the time remaining has fallen below the alarm threshold, sound the
15806      * alarm. if the alarm has sounded and (due to a takeback or time control
15807      * with increment) the time remaining has increased to a level above the
15808      * threshold, reset the alarm so it can sound again.
15809      */
15810
15811     if (appData.icsActive && appData.icsAlarm) {
15812
15813         /* make sure we are dealing with the user's clock */
15814         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15815                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15816            )) return;
15817
15818         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15819             alarmSounded = FALSE;
15820         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15821             PlayAlarmSound();
15822             alarmSounded = TRUE;
15823         }
15824     }
15825 }
15826
15827
15828 /* A player has just moved, so stop the previously running
15829    clock and (if in clock mode) start the other one.
15830    We redisplay both clocks in case we're in ICS mode, because
15831    ICS gives us an update to both clocks after every move.
15832    Note that this routine is called *after* forwardMostMove
15833    is updated, so the last fractional tick must be subtracted
15834    from the color that is *not* on move now.
15835 */
15836 void
15837 SwitchClocks(int newMoveNr)
15838 {
15839     long lastTickLength;
15840     TimeMark now;
15841     int flagged = FALSE;
15842
15843     GetTimeMark(&now);
15844
15845     if (StopClockTimer() && appData.clockMode) {
15846         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15847         if (!WhiteOnMove(forwardMostMove)) {
15848             if(blackNPS >= 0) lastTickLength = 0;
15849             blackTimeRemaining -= lastTickLength;
15850            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15851 //         if(pvInfoList[forwardMostMove].time == -1)
15852                  pvInfoList[forwardMostMove].time =               // use GUI time
15853                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15854         } else {
15855            if(whiteNPS >= 0) lastTickLength = 0;
15856            whiteTimeRemaining -= lastTickLength;
15857            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15858 //         if(pvInfoList[forwardMostMove].time == -1)
15859                  pvInfoList[forwardMostMove].time =
15860                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15861         }
15862         flagged = CheckFlags();
15863     }
15864     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15865     CheckTimeControl();
15866
15867     if (flagged || !appData.clockMode) return;
15868
15869     switch (gameMode) {
15870       case MachinePlaysBlack:
15871       case MachinePlaysWhite:
15872       case BeginningOfGame:
15873         if (pausing) return;
15874         break;
15875
15876       case EditGame:
15877       case PlayFromGameFile:
15878       case IcsExamining:
15879         return;
15880
15881       default:
15882         break;
15883     }
15884
15885     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15886         if(WhiteOnMove(forwardMostMove))
15887              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15888         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15889     }
15890
15891     tickStartTM = now;
15892     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15893       whiteTimeRemaining : blackTimeRemaining);
15894     StartClockTimer(intendedTickLength);
15895 }
15896
15897
15898 /* Stop both clocks */
15899 void
15900 StopClocks()
15901 {
15902     long lastTickLength;
15903     TimeMark now;
15904
15905     if (!StopClockTimer()) return;
15906     if (!appData.clockMode) return;
15907
15908     GetTimeMark(&now);
15909
15910     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15911     if (WhiteOnMove(forwardMostMove)) {
15912         if(whiteNPS >= 0) lastTickLength = 0;
15913         whiteTimeRemaining -= lastTickLength;
15914         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15915     } else {
15916         if(blackNPS >= 0) lastTickLength = 0;
15917         blackTimeRemaining -= lastTickLength;
15918         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15919     }
15920     CheckFlags();
15921 }
15922
15923 /* Start clock of player on move.  Time may have been reset, so
15924    if clock is already running, stop and restart it. */
15925 void
15926 StartClocks()
15927 {
15928     (void) StopClockTimer(); /* in case it was running already */
15929     DisplayBothClocks();
15930     if (CheckFlags()) return;
15931
15932     if (!appData.clockMode) return;
15933     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15934
15935     GetTimeMark(&tickStartTM);
15936     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15937       whiteTimeRemaining : blackTimeRemaining);
15938
15939    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15940     whiteNPS = blackNPS = -1;
15941     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15942        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15943         whiteNPS = first.nps;
15944     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15945        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15946         blackNPS = first.nps;
15947     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15948         whiteNPS = second.nps;
15949     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15950         blackNPS = second.nps;
15951     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15952
15953     StartClockTimer(intendedTickLength);
15954 }
15955
15956 char *
15957 TimeString(ms)
15958      long ms;
15959 {
15960     long second, minute, hour, day;
15961     char *sign = "";
15962     static char buf[32];
15963
15964     if (ms > 0 && ms <= 9900) {
15965       /* convert milliseconds to tenths, rounding up */
15966       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15967
15968       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15969       return buf;
15970     }
15971
15972     /* convert milliseconds to seconds, rounding up */
15973     /* use floating point to avoid strangeness of integer division
15974        with negative dividends on many machines */
15975     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15976
15977     if (second < 0) {
15978         sign = "-";
15979         second = -second;
15980     }
15981
15982     day = second / (60 * 60 * 24);
15983     second = second % (60 * 60 * 24);
15984     hour = second / (60 * 60);
15985     second = second % (60 * 60);
15986     minute = second / 60;
15987     second = second % 60;
15988
15989     if (day > 0)
15990       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15991               sign, day, hour, minute, second);
15992     else if (hour > 0)
15993       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15994     else
15995       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15996
15997     return buf;
15998 }
15999
16000
16001 /*
16002  * This is necessary because some C libraries aren't ANSI C compliant yet.
16003  */
16004 char *
16005 StrStr(string, match)
16006      char *string, *match;
16007 {
16008     int i, length;
16009
16010     length = strlen(match);
16011
16012     for (i = strlen(string) - length; i >= 0; i--, string++)
16013       if (!strncmp(match, string, length))
16014         return string;
16015
16016     return NULL;
16017 }
16018
16019 char *
16020 StrCaseStr(string, match)
16021      char *string, *match;
16022 {
16023     int i, j, length;
16024
16025     length = strlen(match);
16026
16027     for (i = strlen(string) - length; i >= 0; i--, string++) {
16028         for (j = 0; j < length; j++) {
16029             if (ToLower(match[j]) != ToLower(string[j]))
16030               break;
16031         }
16032         if (j == length) return string;
16033     }
16034
16035     return NULL;
16036 }
16037
16038 #ifndef _amigados
16039 int
16040 StrCaseCmp(s1, s2)
16041      char *s1, *s2;
16042 {
16043     char c1, c2;
16044
16045     for (;;) {
16046         c1 = ToLower(*s1++);
16047         c2 = ToLower(*s2++);
16048         if (c1 > c2) return 1;
16049         if (c1 < c2) return -1;
16050         if (c1 == NULLCHAR) return 0;
16051     }
16052 }
16053
16054
16055 int
16056 ToLower(c)
16057      int c;
16058 {
16059     return isupper(c) ? tolower(c) : c;
16060 }
16061
16062
16063 int
16064 ToUpper(c)
16065      int c;
16066 {
16067     return islower(c) ? toupper(c) : c;
16068 }
16069 #endif /* !_amigados    */
16070
16071 char *
16072 StrSave(s)
16073      char *s;
16074 {
16075   char *ret;
16076
16077   if ((ret = (char *) malloc(strlen(s) + 1)))
16078     {
16079       safeStrCpy(ret, s, strlen(s)+1);
16080     }
16081   return ret;
16082 }
16083
16084 char *
16085 StrSavePtr(s, savePtr)
16086      char *s, **savePtr;
16087 {
16088     if (*savePtr) {
16089         free(*savePtr);
16090     }
16091     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16092       safeStrCpy(*savePtr, s, strlen(s)+1);
16093     }
16094     return(*savePtr);
16095 }
16096
16097 char *
16098 PGNDate()
16099 {
16100     time_t clock;
16101     struct tm *tm;
16102     char buf[MSG_SIZ];
16103
16104     clock = time((time_t *)NULL);
16105     tm = localtime(&clock);
16106     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16107             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16108     return StrSave(buf);
16109 }
16110
16111
16112 char *
16113 PositionToFEN(move, overrideCastling)
16114      int move;
16115      char *overrideCastling;
16116 {
16117     int i, j, fromX, fromY, toX, toY;
16118     int whiteToPlay;
16119     char buf[MSG_SIZ];
16120     char *p, *q;
16121     int emptycount;
16122     ChessSquare piece;
16123
16124     whiteToPlay = (gameMode == EditPosition) ?
16125       !blackPlaysFirst : (move % 2 == 0);
16126     p = buf;
16127
16128     /* Piece placement data */
16129     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16130         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16131         emptycount = 0;
16132         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16133             if (boards[move][i][j] == EmptySquare) {
16134                 emptycount++;
16135             } else { ChessSquare piece = boards[move][i][j];
16136                 if (emptycount > 0) {
16137                     if(emptycount<10) /* [HGM] can be >= 10 */
16138                         *p++ = '0' + emptycount;
16139                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16140                     emptycount = 0;
16141                 }
16142                 if(PieceToChar(piece) == '+') {
16143                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16144                     *p++ = '+';
16145                     piece = (ChessSquare)(DEMOTED piece);
16146                 }
16147                 *p++ = PieceToChar(piece);
16148                 if(p[-1] == '~') {
16149                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16150                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16151                     *p++ = '~';
16152                 }
16153             }
16154         }
16155         if (emptycount > 0) {
16156             if(emptycount<10) /* [HGM] can be >= 10 */
16157                 *p++ = '0' + emptycount;
16158             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16159             emptycount = 0;
16160         }
16161         *p++ = '/';
16162     }
16163     *(p - 1) = ' ';
16164
16165     /* [HGM] print Crazyhouse or Shogi holdings */
16166     if( gameInfo.holdingsWidth ) {
16167         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16168         q = p;
16169         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16170             piece = boards[move][i][BOARD_WIDTH-1];
16171             if( piece != EmptySquare )
16172               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16173                   *p++ = PieceToChar(piece);
16174         }
16175         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16176             piece = boards[move][BOARD_HEIGHT-i-1][0];
16177             if( piece != EmptySquare )
16178               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16179                   *p++ = PieceToChar(piece);
16180         }
16181
16182         if( q == p ) *p++ = '-';
16183         *p++ = ']';
16184         *p++ = ' ';
16185     }
16186
16187     /* Active color */
16188     *p++ = whiteToPlay ? 'w' : 'b';
16189     *p++ = ' ';
16190
16191   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16192     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16193   } else {
16194   if(nrCastlingRights) {
16195      q = p;
16196      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16197        /* [HGM] write directly from rights */
16198            if(boards[move][CASTLING][2] != NoRights &&
16199               boards[move][CASTLING][0] != NoRights   )
16200                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16201            if(boards[move][CASTLING][2] != NoRights &&
16202               boards[move][CASTLING][1] != NoRights   )
16203                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16204            if(boards[move][CASTLING][5] != NoRights &&
16205               boards[move][CASTLING][3] != NoRights   )
16206                 *p++ = boards[move][CASTLING][3] + AAA;
16207            if(boards[move][CASTLING][5] != NoRights &&
16208               boards[move][CASTLING][4] != NoRights   )
16209                 *p++ = boards[move][CASTLING][4] + AAA;
16210      } else {
16211
16212         /* [HGM] write true castling rights */
16213         if( nrCastlingRights == 6 ) {
16214             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16215                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16216             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16217                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16218             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16219                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16220             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16221                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16222         }
16223      }
16224      if (q == p) *p++ = '-'; /* No castling rights */
16225      *p++ = ' ';
16226   }
16227
16228   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16229      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16230     /* En passant target square */
16231     if (move > backwardMostMove) {
16232         fromX = moveList[move - 1][0] - AAA;
16233         fromY = moveList[move - 1][1] - ONE;
16234         toX = moveList[move - 1][2] - AAA;
16235         toY = moveList[move - 1][3] - ONE;
16236         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16237             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16238             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16239             fromX == toX) {
16240             /* 2-square pawn move just happened */
16241             *p++ = toX + AAA;
16242             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16243         } else {
16244             *p++ = '-';
16245         }
16246     } else if(move == backwardMostMove) {
16247         // [HGM] perhaps we should always do it like this, and forget the above?
16248         if((signed char)boards[move][EP_STATUS] >= 0) {
16249             *p++ = boards[move][EP_STATUS] + AAA;
16250             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16251         } else {
16252             *p++ = '-';
16253         }
16254     } else {
16255         *p++ = '-';
16256     }
16257     *p++ = ' ';
16258   }
16259   }
16260
16261     /* [HGM] find reversible plies */
16262     {   int i = 0, j=move;
16263
16264         if (appData.debugMode) { int k;
16265             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16266             for(k=backwardMostMove; k<=forwardMostMove; k++)
16267                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16268
16269         }
16270
16271         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16272         if( j == backwardMostMove ) i += initialRulePlies;
16273         sprintf(p, "%d ", i);
16274         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16275     }
16276     /* Fullmove number */
16277     sprintf(p, "%d", (move / 2) + 1);
16278
16279     return StrSave(buf);
16280 }
16281
16282 Boolean
16283 ParseFEN(board, blackPlaysFirst, fen)
16284     Board board;
16285      int *blackPlaysFirst;
16286      char *fen;
16287 {
16288     int i, j;
16289     char *p, c;
16290     int emptycount;
16291     ChessSquare piece;
16292
16293     p = fen;
16294
16295     /* [HGM] by default clear Crazyhouse holdings, if present */
16296     if(gameInfo.holdingsWidth) {
16297        for(i=0; i<BOARD_HEIGHT; i++) {
16298            board[i][0]             = EmptySquare; /* black holdings */
16299            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16300            board[i][1]             = (ChessSquare) 0; /* black counts */
16301            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16302        }
16303     }
16304
16305     /* Piece placement data */
16306     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16307         j = 0;
16308         for (;;) {
16309             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16310                 if (*p == '/') p++;
16311                 emptycount = gameInfo.boardWidth - j;
16312                 while (emptycount--)
16313                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16314                 break;
16315 #if(BOARD_FILES >= 10)
16316             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16317                 p++; emptycount=10;
16318                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16319                 while (emptycount--)
16320                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16321 #endif
16322             } else if (isdigit(*p)) {
16323                 emptycount = *p++ - '0';
16324                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16325                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16326                 while (emptycount--)
16327                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16328             } else if (*p == '+' || isalpha(*p)) {
16329                 if (j >= gameInfo.boardWidth) return FALSE;
16330                 if(*p=='+') {
16331                     piece = CharToPiece(*++p);
16332                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16333                     piece = (ChessSquare) (PROMOTED piece ); p++;
16334                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16335                 } else piece = CharToPiece(*p++);
16336
16337                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16338                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16339                     piece = (ChessSquare) (PROMOTED piece);
16340                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16341                     p++;
16342                 }
16343                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16344             } else {
16345                 return FALSE;
16346             }
16347         }
16348     }
16349     while (*p == '/' || *p == ' ') p++;
16350
16351     /* [HGM] look for Crazyhouse holdings here */
16352     while(*p==' ') p++;
16353     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16354         if(*p == '[') p++;
16355         if(*p == '-' ) p++; /* empty holdings */ else {
16356             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16357             /* if we would allow FEN reading to set board size, we would   */
16358             /* have to add holdings and shift the board read so far here   */
16359             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16360                 p++;
16361                 if((int) piece >= (int) BlackPawn ) {
16362                     i = (int)piece - (int)BlackPawn;
16363                     i = PieceToNumber((ChessSquare)i);
16364                     if( i >= gameInfo.holdingsSize ) return FALSE;
16365                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16366                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16367                 } else {
16368                     i = (int)piece - (int)WhitePawn;
16369                     i = PieceToNumber((ChessSquare)i);
16370                     if( i >= gameInfo.holdingsSize ) return FALSE;
16371                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16372                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16373                 }
16374             }
16375         }
16376         if(*p == ']') p++;
16377     }
16378
16379     while(*p == ' ') p++;
16380
16381     /* Active color */
16382     c = *p++;
16383     if(appData.colorNickNames) {
16384       if( c == appData.colorNickNames[0] ) c = 'w'; else
16385       if( c == appData.colorNickNames[1] ) c = 'b';
16386     }
16387     switch (c) {
16388       case 'w':
16389         *blackPlaysFirst = FALSE;
16390         break;
16391       case 'b':
16392         *blackPlaysFirst = TRUE;
16393         break;
16394       default:
16395         return FALSE;
16396     }
16397
16398     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16399     /* return the extra info in global variiables             */
16400
16401     /* set defaults in case FEN is incomplete */
16402     board[EP_STATUS] = EP_UNKNOWN;
16403     for(i=0; i<nrCastlingRights; i++ ) {
16404         board[CASTLING][i] =
16405             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16406     }   /* assume possible unless obviously impossible */
16407     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16408     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16409     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16410                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16411     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16412     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16413     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16414                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16415     FENrulePlies = 0;
16416
16417     while(*p==' ') p++;
16418     if(nrCastlingRights) {
16419       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16420           /* castling indicator present, so default becomes no castlings */
16421           for(i=0; i<nrCastlingRights; i++ ) {
16422                  board[CASTLING][i] = NoRights;
16423           }
16424       }
16425       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16426              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16427              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16428              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16429         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16430
16431         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16432             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16433             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16434         }
16435         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16436             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16437         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16438                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16439         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16440                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16441         switch(c) {
16442           case'K':
16443               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16444               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16445               board[CASTLING][2] = whiteKingFile;
16446               break;
16447           case'Q':
16448               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16449               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16450               board[CASTLING][2] = whiteKingFile;
16451               break;
16452           case'k':
16453               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16454               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16455               board[CASTLING][5] = blackKingFile;
16456               break;
16457           case'q':
16458               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16459               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16460               board[CASTLING][5] = blackKingFile;
16461           case '-':
16462               break;
16463           default: /* FRC castlings */
16464               if(c >= 'a') { /* black rights */
16465                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16466                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16467                   if(i == BOARD_RGHT) break;
16468                   board[CASTLING][5] = i;
16469                   c -= AAA;
16470                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16471                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16472                   if(c > i)
16473                       board[CASTLING][3] = c;
16474                   else
16475                       board[CASTLING][4] = c;
16476               } else { /* white rights */
16477                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16478                     if(board[0][i] == WhiteKing) break;
16479                   if(i == BOARD_RGHT) break;
16480                   board[CASTLING][2] = i;
16481                   c -= AAA - 'a' + 'A';
16482                   if(board[0][c] >= WhiteKing) break;
16483                   if(c > i)
16484                       board[CASTLING][0] = c;
16485                   else
16486                       board[CASTLING][1] = c;
16487               }
16488         }
16489       }
16490       for(i=0; i<nrCastlingRights; i++)
16491         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16492     if (appData.debugMode) {
16493         fprintf(debugFP, "FEN castling rights:");
16494         for(i=0; i<nrCastlingRights; i++)
16495         fprintf(debugFP, " %d", board[CASTLING][i]);
16496         fprintf(debugFP, "\n");
16497     }
16498
16499       while(*p==' ') p++;
16500     }
16501
16502     /* read e.p. field in games that know e.p. capture */
16503     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16504        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16505       if(*p=='-') {
16506         p++; board[EP_STATUS] = EP_NONE;
16507       } else {
16508          char c = *p++ - AAA;
16509
16510          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16511          if(*p >= '0' && *p <='9') p++;
16512          board[EP_STATUS] = c;
16513       }
16514     }
16515
16516
16517     if(sscanf(p, "%d", &i) == 1) {
16518         FENrulePlies = i; /* 50-move ply counter */
16519         /* (The move number is still ignored)    */
16520     }
16521
16522     return TRUE;
16523 }
16524
16525 void
16526 EditPositionPasteFEN(char *fen)
16527 {
16528   if (fen != NULL) {
16529     Board initial_position;
16530
16531     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16532       DisplayError(_("Bad FEN position in clipboard"), 0);
16533       return ;
16534     } else {
16535       int savedBlackPlaysFirst = blackPlaysFirst;
16536       EditPositionEvent();
16537       blackPlaysFirst = savedBlackPlaysFirst;
16538       CopyBoard(boards[0], initial_position);
16539       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16540       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16541       DisplayBothClocks();
16542       DrawPosition(FALSE, boards[currentMove]);
16543     }
16544   }
16545 }
16546
16547 static char cseq[12] = "\\   ";
16548
16549 Boolean set_cont_sequence(char *new_seq)
16550 {
16551     int len;
16552     Boolean ret;
16553
16554     // handle bad attempts to set the sequence
16555         if (!new_seq)
16556                 return 0; // acceptable error - no debug
16557
16558     len = strlen(new_seq);
16559     ret = (len > 0) && (len < sizeof(cseq));
16560     if (ret)
16561       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16562     else if (appData.debugMode)
16563       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16564     return ret;
16565 }
16566
16567 /*
16568     reformat a source message so words don't cross the width boundary.  internal
16569     newlines are not removed.  returns the wrapped size (no null character unless
16570     included in source message).  If dest is NULL, only calculate the size required
16571     for the dest buffer.  lp argument indicats line position upon entry, and it's
16572     passed back upon exit.
16573 */
16574 int wrap(char *dest, char *src, int count, int width, int *lp)
16575 {
16576     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16577
16578     cseq_len = strlen(cseq);
16579     old_line = line = *lp;
16580     ansi = len = clen = 0;
16581
16582     for (i=0; i < count; i++)
16583     {
16584         if (src[i] == '\033')
16585             ansi = 1;
16586
16587         // if we hit the width, back up
16588         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16589         {
16590             // store i & len in case the word is too long
16591             old_i = i, old_len = len;
16592
16593             // find the end of the last word
16594             while (i && src[i] != ' ' && src[i] != '\n')
16595             {
16596                 i--;
16597                 len--;
16598             }
16599
16600             // word too long?  restore i & len before splitting it
16601             if ((old_i-i+clen) >= width)
16602             {
16603                 i = old_i;
16604                 len = old_len;
16605             }
16606
16607             // extra space?
16608             if (i && src[i-1] == ' ')
16609                 len--;
16610
16611             if (src[i] != ' ' && src[i] != '\n')
16612             {
16613                 i--;
16614                 if (len)
16615                     len--;
16616             }
16617
16618             // now append the newline and continuation sequence
16619             if (dest)
16620                 dest[len] = '\n';
16621             len++;
16622             if (dest)
16623                 strncpy(dest+len, cseq, cseq_len);
16624             len += cseq_len;
16625             line = cseq_len;
16626             clen = cseq_len;
16627             continue;
16628         }
16629
16630         if (dest)
16631             dest[len] = src[i];
16632         len++;
16633         if (!ansi)
16634             line++;
16635         if (src[i] == '\n')
16636             line = 0;
16637         if (src[i] == 'm')
16638             ansi = 0;
16639     }
16640     if (dest && appData.debugMode)
16641     {
16642         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16643             count, width, line, len, *lp);
16644         show_bytes(debugFP, src, count);
16645         fprintf(debugFP, "\ndest: ");
16646         show_bytes(debugFP, dest, len);
16647         fprintf(debugFP, "\n");
16648     }
16649     *lp = dest ? line : old_line;
16650
16651     return len;
16652 }
16653
16654 // [HGM] vari: routines for shelving variations
16655 Boolean modeRestore = FALSE;
16656
16657 void
16658 PushInner(int firstMove, int lastMove)
16659 {
16660         int i, j, nrMoves = lastMove - firstMove;
16661
16662         // push current tail of game on stack
16663         savedResult[storedGames] = gameInfo.result;
16664         savedDetails[storedGames] = gameInfo.resultDetails;
16665         gameInfo.resultDetails = NULL;
16666         savedFirst[storedGames] = firstMove;
16667         savedLast [storedGames] = lastMove;
16668         savedFramePtr[storedGames] = framePtr;
16669         framePtr -= nrMoves; // reserve space for the boards
16670         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16671             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16672             for(j=0; j<MOVE_LEN; j++)
16673                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16674             for(j=0; j<2*MOVE_LEN; j++)
16675                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16676             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16677             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16678             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16679             pvInfoList[firstMove+i-1].depth = 0;
16680             commentList[framePtr+i] = commentList[firstMove+i];
16681             commentList[firstMove+i] = NULL;
16682         }
16683
16684         storedGames++;
16685         forwardMostMove = firstMove; // truncate game so we can start variation
16686 }
16687
16688 void
16689 PushTail(int firstMove, int lastMove)
16690 {
16691         if(appData.icsActive) { // only in local mode
16692                 forwardMostMove = currentMove; // mimic old ICS behavior
16693                 return;
16694         }
16695         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16696
16697         PushInner(firstMove, lastMove);
16698         if(storedGames == 1) GreyRevert(FALSE);
16699         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16700 }
16701
16702 void
16703 PopInner(Boolean annotate)
16704 {
16705         int i, j, nrMoves;
16706         char buf[8000], moveBuf[20];
16707
16708         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16709         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16710         nrMoves = savedLast[storedGames] - currentMove;
16711         if(annotate) {
16712                 int cnt = 10;
16713                 if(!WhiteOnMove(currentMove))
16714                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16715                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16716                 for(i=currentMove; i<forwardMostMove; i++) {
16717                         if(WhiteOnMove(i))
16718                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16719                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16720                         strcat(buf, moveBuf);
16721                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16722                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16723                 }
16724                 strcat(buf, ")");
16725         }
16726         for(i=1; i<=nrMoves; i++) { // copy last variation back
16727             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16728             for(j=0; j<MOVE_LEN; j++)
16729                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16730             for(j=0; j<2*MOVE_LEN; j++)
16731                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16732             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16733             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16734             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16735             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16736             commentList[currentMove+i] = commentList[framePtr+i];
16737             commentList[framePtr+i] = NULL;
16738         }
16739         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16740         framePtr = savedFramePtr[storedGames];
16741         gameInfo.result = savedResult[storedGames];
16742         if(gameInfo.resultDetails != NULL) {
16743             free(gameInfo.resultDetails);
16744       }
16745         gameInfo.resultDetails = savedDetails[storedGames];
16746         forwardMostMove = currentMove + nrMoves;
16747 }
16748
16749 Boolean
16750 PopTail(Boolean annotate)
16751 {
16752         if(appData.icsActive) return FALSE; // only in local mode
16753         if(!storedGames) return FALSE; // sanity
16754         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16755
16756         PopInner(annotate);
16757         if(currentMove < forwardMostMove) ForwardEvent(); else
16758         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16759
16760         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16761         return TRUE;
16762 }
16763
16764 void
16765 CleanupTail()
16766 {       // remove all shelved variations
16767         int i;
16768         for(i=0; i<storedGames; i++) {
16769             if(savedDetails[i])
16770                 free(savedDetails[i]);
16771             savedDetails[i] = NULL;
16772         }
16773         for(i=framePtr; i<MAX_MOVES; i++) {
16774                 if(commentList[i]) free(commentList[i]);
16775                 commentList[i] = NULL;
16776         }
16777         framePtr = MAX_MOVES-1;
16778         storedGames = 0;
16779 }
16780
16781 void
16782 LoadVariation(int index, char *text)
16783 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16784         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16785         int level = 0, move;
16786
16787         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16788         // first find outermost bracketing variation
16789         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16790             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16791                 if(*p == '{') wait = '}'; else
16792                 if(*p == '[') wait = ']'; else
16793                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16794                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16795             }
16796             if(*p == wait) wait = NULLCHAR; // closing ]} found
16797             p++;
16798         }
16799         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16800         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16801         end[1] = NULLCHAR; // clip off comment beyond variation
16802         ToNrEvent(currentMove-1);
16803         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16804         // kludge: use ParsePV() to append variation to game
16805         move = currentMove;
16806         ParsePV(start, TRUE, TRUE);
16807         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16808         ClearPremoveHighlights();
16809         CommentPopDown();
16810         ToNrEvent(currentMove+1);
16811 }
16812