Implement searching games in Game List for a position
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 int flock(int f, int code);
61 #define LOCK_EX 2
62 #define SLASH '\\'
63
64 #else
65
66 #include <sys/file.h>
67 #define DoSleep( n ) if( (n) >= 0) sleep(n)
68 #define SLASH '/'
69
70 #endif
71
72 #include "config.h"
73
74 #include <assert.h>
75 #include <stdio.h>
76 #include <ctype.h>
77 #include <errno.h>
78 #include <sys/types.h>
79 #include <sys/stat.h>
80 #include <math.h>
81 #include <ctype.h>
82
83 #if STDC_HEADERS
84 # include <stdlib.h>
85 # include <string.h>
86 # include <stdarg.h>
87 #else /* not STDC_HEADERS */
88 # if HAVE_STRING_H
89 #  include <string.h>
90 # else /* not HAVE_STRING_H */
91 #  include <strings.h>
92 # endif /* not HAVE_STRING_H */
93 #endif /* not STDC_HEADERS */
94
95 #if HAVE_SYS_FCNTL_H
96 # include <sys/fcntl.h>
97 #else /* not HAVE_SYS_FCNTL_H */
98 # if HAVE_FCNTL_H
99 #  include <fcntl.h>
100 # endif /* HAVE_FCNTL_H */
101 #endif /* not HAVE_SYS_FCNTL_H */
102
103 #if TIME_WITH_SYS_TIME
104 # include <sys/time.h>
105 # include <time.h>
106 #else
107 # if HAVE_SYS_TIME_H
108 #  include <sys/time.h>
109 # else
110 #  include <time.h>
111 # endif
112 #endif
113
114 #if defined(_amigados) && !defined(__GNUC__)
115 struct timezone {
116     int tz_minuteswest;
117     int tz_dsttime;
118 };
119 extern int gettimeofday(struct timeval *, struct timezone *);
120 #endif
121
122 #if HAVE_UNISTD_H
123 # include <unistd.h>
124 #endif
125
126 #include "common.h"
127 #include "frontend.h"
128 #include "backend.h"
129 #include "parser.h"
130 #include "moves.h"
131 #if ZIPPY
132 # include "zippy.h"
133 #endif
134 #include "backendz.h"
135 #include "gettext.h"
136
137 #ifdef ENABLE_NLS
138 # define _(s) gettext (s)
139 # define N_(s) gettext_noop (s)
140 # define T_(s) gettext(s)
141 #else
142 # ifdef WIN32
143 #   define _(s) T_(s)
144 #   define N_(s) s
145 # else
146 #   define _(s) (s)
147 #   define N_(s) s
148 #   define T_(s) s
149 # endif
150 #endif
151
152
153 /* A point in time */
154 typedef struct {
155     long sec;  /* Assuming this is >= 32 bits */
156     int ms;    /* Assuming this is >= 16 bits */
157 } TimeMark;
158
159 int establish P((void));
160 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
161                          char *buf, int count, int error));
162 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
163                       char *buf, int count, int error));
164 void ics_printf P((char *format, ...));
165 void SendToICS P((char *s));
166 void SendToICSDelayed P((char *s, long msdelay));
167 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
168 void HandleMachineMove P((char *message, ChessProgramState *cps));
169 int AutoPlayOneMove P((void));
170 int LoadGameOneMove P((ChessMove readAhead));
171 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
172 int LoadPositionFromFile P((char *filename, int n, char *title));
173 int SavePositionToFile P((char *filename));
174 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
175                                                                                 Board board));
176 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
177 void ShowMove P((int fromX, int fromY, int toX, int toY));
178 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
179                    /*char*/int promoChar));
180 void BackwardInner P((int target));
181 void ForwardInner P((int target));
182 int Adjudicate P((ChessProgramState *cps));
183 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
184 void EditPositionDone P((Boolean fakeRights));
185 void PrintOpponents P((FILE *fp));
186 void PrintPosition P((FILE *fp, int move));
187 void StartChessProgram P((ChessProgramState *cps));
188 void SendToProgram P((char *message, ChessProgramState *cps));
189 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
190 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
191                            char *buf, int count, int error));
192 void SendTimeControl P((ChessProgramState *cps,
193                         int mps, long tc, int inc, int sd, int st));
194 char *TimeControlTagValue P((void));
195 void Attention P((ChessProgramState *cps));
196 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
197 int ResurrectChessProgram P((void));
198 void DisplayComment P((int moveNumber, char *text));
199 void DisplayMove P((int moveNumber));
200
201 void ParseGameHistory P((char *game));
202 void ParseBoard12 P((char *string));
203 void KeepAlive P((void));
204 void StartClocks P((void));
205 void SwitchClocks P((int nr));
206 void StopClocks P((void));
207 void ResetClocks P((void));
208 char *PGNDate P((void));
209 void SetGameInfo P((void));
210 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
211 int RegisterMove P((void));
212 void MakeRegisteredMove P((void));
213 void TruncateGame P((void));
214 int looking_at P((char *, int *, char *));
215 void CopyPlayerNameIntoFileName P((char **, char *));
216 char *SavePart P((char *));
217 int SaveGameOldStyle P((FILE *));
218 int SaveGamePGN P((FILE *));
219 void GetTimeMark P((TimeMark *));
220 long SubtractTimeMarks P((TimeMark *, TimeMark *));
221 int CheckFlags P((void));
222 long NextTickLength P((long));
223 void CheckTimeControl P((void));
224 void show_bytes P((FILE *, char *, int));
225 int string_to_rating P((char *str));
226 void ParseFeatures P((char* args, ChessProgramState *cps));
227 void InitBackEnd3 P((void));
228 void FeatureDone P((ChessProgramState* cps, int val));
229 void InitChessProgram P((ChessProgramState *cps, int setup));
230 void OutputKibitz(int window, char *text);
231 int PerpetualChase(int first, int last);
232 int EngineOutputIsUp();
233 void InitDrawingSizes(int x, int y);
234 void NextMatchGame P((void));
235 int NextTourneyGame P((int nr, int *swap));
236 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
237 FILE *WriteTourneyFile P((char *results));
238 void DisplayTwoMachinesTitle P(());
239
240 #ifdef WIN32
241        extern void ConsoleCreate();
242 #endif
243
244 ChessProgramState *WhitePlayer();
245 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
246 int VerifyDisplayMode P(());
247
248 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
249 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
250 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
251 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
252 void ics_update_width P((int new_width));
253 extern char installDir[MSG_SIZ];
254 VariantClass startVariant; /* [HGM] nicks: initial variant */
255 Boolean abortMatch;
256
257 extern int tinyLayout, smallLayout;
258 ChessProgramStats programStats;
259 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
260 int endPV = -1;
261 static int exiting = 0; /* [HGM] moved to top */
262 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
263 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
264 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
265 int partnerHighlight[2];
266 Boolean partnerBoardValid = 0;
267 char partnerStatus[MSG_SIZ];
268 Boolean partnerUp;
269 Boolean originalFlip;
270 Boolean twoBoards = 0;
271 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
272 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
273 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
274 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
275 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
276 int opponentKibitzes;
277 int lastSavedGame; /* [HGM] save: ID of game */
278 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
279 extern int chatCount;
280 int chattingPartner;
281 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
282 char lastMsg[MSG_SIZ];
283 ChessSquare pieceSweep = EmptySquare;
284 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
285 int promoDefaultAltered;
286
287 /* States for ics_getting_history */
288 #define H_FALSE 0
289 #define H_REQUESTED 1
290 #define H_GOT_REQ_HEADER 2
291 #define H_GOT_UNREQ_HEADER 3
292 #define H_GETTING_MOVES 4
293 #define H_GOT_UNWANTED_HEADER 5
294
295 /* whosays values for GameEnds */
296 #define GE_ICS 0
297 #define GE_ENGINE 1
298 #define GE_PLAYER 2
299 #define GE_FILE 3
300 #define GE_XBOARD 4
301 #define GE_ENGINE1 5
302 #define GE_ENGINE2 6
303
304 /* Maximum number of games in a cmail message */
305 #define CMAIL_MAX_GAMES 20
306
307 /* Different types of move when calling RegisterMove */
308 #define CMAIL_MOVE   0
309 #define CMAIL_RESIGN 1
310 #define CMAIL_DRAW   2
311 #define CMAIL_ACCEPT 3
312
313 /* Different types of result to remember for each game */
314 #define CMAIL_NOT_RESULT 0
315 #define CMAIL_OLD_RESULT 1
316 #define CMAIL_NEW_RESULT 2
317
318 /* Telnet protocol constants */
319 #define TN_WILL 0373
320 #define TN_WONT 0374
321 #define TN_DO   0375
322 #define TN_DONT 0376
323 #define TN_IAC  0377
324 #define TN_ECHO 0001
325 #define TN_SGA  0003
326 #define TN_PORT 23
327
328 char*
329 safeStrCpy( char *dst, const char *src, size_t count )
330 { // [HGM] made safe
331   int i;
332   assert( dst != NULL );
333   assert( src != NULL );
334   assert( count > 0 );
335
336   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
337   if(  i == count && dst[count-1] != NULLCHAR)
338     {
339       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
340       if(appData.debugMode)
341       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
342     }
343
344   return dst;
345 }
346
347 /* Some compiler can't cast u64 to double
348  * This function do the job for us:
349
350  * We use the highest bit for cast, this only
351  * works if the highest bit is not
352  * in use (This should not happen)
353  *
354  * We used this for all compiler
355  */
356 double
357 u64ToDouble(u64 value)
358 {
359   double r;
360   u64 tmp = value & u64Const(0x7fffffffffffffff);
361   r = (double)(s64)tmp;
362   if (value & u64Const(0x8000000000000000))
363        r +=  9.2233720368547758080e18; /* 2^63 */
364  return r;
365 }
366
367 /* Fake up flags for now, as we aren't keeping track of castling
368    availability yet. [HGM] Change of logic: the flag now only
369    indicates the type of castlings allowed by the rule of the game.
370    The actual rights themselves are maintained in the array
371    castlingRights, as part of the game history, and are not probed
372    by this function.
373  */
374 int
375 PosFlags(index)
376 {
377   int flags = F_ALL_CASTLE_OK;
378   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
379   switch (gameInfo.variant) {
380   case VariantSuicide:
381     flags &= ~F_ALL_CASTLE_OK;
382   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
383     flags |= F_IGNORE_CHECK;
384   case VariantLosers:
385     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
386     break;
387   case VariantAtomic:
388     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
389     break;
390   case VariantKriegspiel:
391     flags |= F_KRIEGSPIEL_CAPTURE;
392     break;
393   case VariantCapaRandom:
394   case VariantFischeRandom:
395     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
396   case VariantNoCastle:
397   case VariantShatranj:
398   case VariantCourier:
399   case VariantMakruk:
400   case VariantGrand:
401     flags &= ~F_ALL_CASTLE_OK;
402     break;
403   default:
404     break;
405   }
406   return flags;
407 }
408
409 FILE *gameFileFP, *debugFP;
410
411 /*
412     [AS] Note: sometimes, the sscanf() function is used to parse the input
413     into a fixed-size buffer. Because of this, we must be prepared to
414     receive strings as long as the size of the input buffer, which is currently
415     set to 4K for Windows and 8K for the rest.
416     So, we must either allocate sufficiently large buffers here, or
417     reduce the size of the input buffer in the input reading part.
418 */
419
420 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
421 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
422 char thinkOutput1[MSG_SIZ*10];
423
424 ChessProgramState first, second, pairing;
425
426 /* premove variables */
427 int premoveToX = 0;
428 int premoveToY = 0;
429 int premoveFromX = 0;
430 int premoveFromY = 0;
431 int premovePromoChar = 0;
432 int gotPremove = 0;
433 Boolean alarmSounded;
434 /* end premove variables */
435
436 char *ics_prefix = "$";
437 int ics_type = ICS_GENERIC;
438
439 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
440 int pauseExamForwardMostMove = 0;
441 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
442 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
443 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
444 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
445 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
446 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
447 int whiteFlag = FALSE, blackFlag = FALSE;
448 int userOfferedDraw = FALSE;
449 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
450 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
451 int cmailMoveType[CMAIL_MAX_GAMES];
452 long ics_clock_paused = 0;
453 ProcRef icsPR = NoProc, cmailPR = NoProc;
454 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
455 GameMode gameMode = BeginningOfGame;
456 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
457 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
458 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
459 int hiddenThinkOutputState = 0; /* [AS] */
460 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
461 int adjudicateLossPlies = 6;
462 char white_holding[64], black_holding[64];
463 TimeMark lastNodeCountTime;
464 long lastNodeCount=0;
465 int shiftKey; // [HGM] set by mouse handler
466
467 int have_sent_ICS_logon = 0;
468 int movesPerSession;
469 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
470 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
471 long timeControl_2; /* [AS] Allow separate time controls */
472 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
473 long timeRemaining[2][MAX_MOVES];
474 int matchGame = 0, nextGame = 0, roundNr = 0;
475 Boolean waitingForGame = FALSE;
476 TimeMark programStartTime, pauseStart;
477 char ics_handle[MSG_SIZ];
478 int have_set_title = 0;
479
480 /* animateTraining preserves the state of appData.animate
481  * when Training mode is activated. This allows the
482  * response to be animated when appData.animate == TRUE and
483  * appData.animateDragging == TRUE.
484  */
485 Boolean animateTraining;
486
487 GameInfo gameInfo;
488
489 AppData appData;
490
491 Board boards[MAX_MOVES];
492 /* [HGM] Following 7 needed for accurate legality tests: */
493 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
494 signed char  initialRights[BOARD_FILES];
495 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
496 int   initialRulePlies, FENrulePlies;
497 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
498 int loadFlag = 0;
499 Boolean shuffleOpenings;
500 int mute; // mute all sounds
501
502 // [HGM] vari: next 12 to save and restore variations
503 #define MAX_VARIATIONS 10
504 int framePtr = MAX_MOVES-1; // points to free stack entry
505 int storedGames = 0;
506 int savedFirst[MAX_VARIATIONS];
507 int savedLast[MAX_VARIATIONS];
508 int savedFramePtr[MAX_VARIATIONS];
509 char *savedDetails[MAX_VARIATIONS];
510 ChessMove savedResult[MAX_VARIATIONS];
511
512 void PushTail P((int firstMove, int lastMove));
513 Boolean PopTail P((Boolean annotate));
514 void PushInner P((int firstMove, int lastMove));
515 void PopInner P((Boolean annotate));
516 void CleanupTail P((void));
517
518 ChessSquare  FIDEArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
520         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
522         BlackKing, BlackBishop, BlackKnight, BlackRook }
523 };
524
525 ChessSquare twoKingsArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
527         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
528     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
529         BlackKing, BlackKing, BlackKnight, BlackRook }
530 };
531
532 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
534         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
535     { BlackRook, BlackMan, BlackBishop, BlackQueen,
536         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
537 };
538
539 ChessSquare SpartanArray[2][BOARD_FILES] = {
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
542     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
543         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
544 };
545
546 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
547     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
548         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
549     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
550         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
551 };
552
553 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
554     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
555         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
556     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
557         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
558 };
559
560 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
561     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
562         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
563     { BlackRook, BlackKnight, BlackMan, BlackFerz,
564         BlackKing, BlackMan, BlackKnight, BlackRook }
565 };
566
567
568 #if (BOARD_FILES>=10)
569 ChessSquare ShogiArray[2][BOARD_FILES] = {
570     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
571         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
572     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
573         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
574 };
575
576 ChessSquare XiangqiArray[2][BOARD_FILES] = {
577     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
578         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
580         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
581 };
582
583 ChessSquare CapablancaArray[2][BOARD_FILES] = {
584     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
585         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
587         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
588 };
589
590 ChessSquare GreatArray[2][BOARD_FILES] = {
591     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
592         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
593     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
594         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
595 };
596
597 ChessSquare JanusArray[2][BOARD_FILES] = {
598     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
599         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
600     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
601         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
602 };
603
604 ChessSquare GrandArray[2][BOARD_FILES] = {
605     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
606         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
607     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
608         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
609 };
610
611 #ifdef GOTHIC
612 ChessSquare GothicArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
614         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
616         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
617 };
618 #else // !GOTHIC
619 #define GothicArray CapablancaArray
620 #endif // !GOTHIC
621
622 #ifdef FALCON
623 ChessSquare FalconArray[2][BOARD_FILES] = {
624     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
625         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
626     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
627         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
628 };
629 #else // !FALCON
630 #define FalconArray CapablancaArray
631 #endif // !FALCON
632
633 #else // !(BOARD_FILES>=10)
634 #define XiangqiPosition FIDEArray
635 #define CapablancaArray FIDEArray
636 #define GothicArray FIDEArray
637 #define GreatArray FIDEArray
638 #endif // !(BOARD_FILES>=10)
639
640 #if (BOARD_FILES>=12)
641 ChessSquare CourierArray[2][BOARD_FILES] = {
642     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
643         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
644     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
645         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
646 };
647 #else // !(BOARD_FILES>=12)
648 #define CourierArray CapablancaArray
649 #endif // !(BOARD_FILES>=12)
650
651
652 Board initialPosition;
653
654
655 /* Convert str to a rating. Checks for special cases of "----",
656
657    "++++", etc. Also strips ()'s */
658 int
659 string_to_rating(str)
660   char *str;
661 {
662   while(*str && !isdigit(*str)) ++str;
663   if (!*str)
664     return 0;   /* One of the special "no rating" cases */
665   else
666     return atoi(str);
667 }
668
669 void
670 ClearProgramStats()
671 {
672     /* Init programStats */
673     programStats.movelist[0] = 0;
674     programStats.depth = 0;
675     programStats.nr_moves = 0;
676     programStats.moves_left = 0;
677     programStats.nodes = 0;
678     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
679     programStats.score = 0;
680     programStats.got_only_move = 0;
681     programStats.got_fail = 0;
682     programStats.line_is_book = 0;
683 }
684
685 void
686 CommonEngineInit()
687 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
688     if (appData.firstPlaysBlack) {
689         first.twoMachinesColor = "black\n";
690         second.twoMachinesColor = "white\n";
691     } else {
692         first.twoMachinesColor = "white\n";
693         second.twoMachinesColor = "black\n";
694     }
695
696     first.other = &second;
697     second.other = &first;
698
699     { float norm = 1;
700         if(appData.timeOddsMode) {
701             norm = appData.timeOdds[0];
702             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
703         }
704         first.timeOdds  = appData.timeOdds[0]/norm;
705         second.timeOdds = appData.timeOdds[1]/norm;
706     }
707
708     if(programVersion) free(programVersion);
709     if (appData.noChessProgram) {
710         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
711         sprintf(programVersion, "%s", PACKAGE_STRING);
712     } else {
713       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
714       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
715       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
716     }
717 }
718
719 void
720 UnloadEngine(ChessProgramState *cps)
721 {
722         /* Kill off first chess program */
723         if (cps->isr != NULL)
724           RemoveInputSource(cps->isr);
725         cps->isr = NULL;
726
727         if (cps->pr != NoProc) {
728             ExitAnalyzeMode();
729             DoSleep( appData.delayBeforeQuit );
730             SendToProgram("quit\n", cps);
731             DoSleep( appData.delayAfterQuit );
732             DestroyChildProcess(cps->pr, cps->useSigterm);
733         }
734         cps->pr = NoProc;
735         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
736 }
737
738 void
739 ClearOptions(ChessProgramState *cps)
740 {
741     int i;
742     cps->nrOptions = cps->comboCnt = 0;
743     for(i=0; i<MAX_OPTIONS; i++) {
744         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
745         cps->option[i].textValue = 0;
746     }
747 }
748
749 char *engineNames[] = {
750 "first",
751 "second"
752 };
753
754 void
755 InitEngine(ChessProgramState *cps, int n)
756 {   // [HGM] all engine initialiation put in a function that does one engine
757
758     ClearOptions(cps);
759
760     cps->which = engineNames[n];
761     cps->maybeThinking = FALSE;
762     cps->pr = NoProc;
763     cps->isr = NULL;
764     cps->sendTime = 2;
765     cps->sendDrawOffers = 1;
766
767     cps->program = appData.chessProgram[n];
768     cps->host = appData.host[n];
769     cps->dir = appData.directory[n];
770     cps->initString = appData.engInitString[n];
771     cps->computerString = appData.computerString[n];
772     cps->useSigint  = TRUE;
773     cps->useSigterm = TRUE;
774     cps->reuse = appData.reuse[n];
775     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
776     cps->useSetboard = FALSE;
777     cps->useSAN = FALSE;
778     cps->usePing = FALSE;
779     cps->lastPing = 0;
780     cps->lastPong = 0;
781     cps->usePlayother = FALSE;
782     cps->useColors = TRUE;
783     cps->useUsermove = FALSE;
784     cps->sendICS = FALSE;
785     cps->sendName = appData.icsActive;
786     cps->sdKludge = FALSE;
787     cps->stKludge = FALSE;
788     TidyProgramName(cps->program, cps->host, cps->tidy);
789     cps->matchWins = 0;
790     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
791     cps->analysisSupport = 2; /* detect */
792     cps->analyzing = FALSE;
793     cps->initDone = FALSE;
794
795     /* New features added by Tord: */
796     cps->useFEN960 = FALSE;
797     cps->useOOCastle = TRUE;
798     /* End of new features added by Tord. */
799     cps->fenOverride  = appData.fenOverride[n];
800
801     /* [HGM] time odds: set factor for each machine */
802     cps->timeOdds  = appData.timeOdds[n];
803
804     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
805     cps->accumulateTC = appData.accumulateTC[n];
806     cps->maxNrOfSessions = 1;
807
808     /* [HGM] debug */
809     cps->debug = FALSE;
810
811     cps->supportsNPS = UNKNOWN;
812     cps->memSize = FALSE;
813     cps->maxCores = FALSE;
814     cps->egtFormats[0] = NULLCHAR;
815
816     /* [HGM] options */
817     cps->optionSettings  = appData.engOptions[n];
818
819     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
820     cps->isUCI = appData.isUCI[n]; /* [AS] */
821     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
822
823     if (appData.protocolVersion[n] > PROTOVER
824         || appData.protocolVersion[n] < 1)
825       {
826         char buf[MSG_SIZ];
827         int len;
828
829         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
830                        appData.protocolVersion[n]);
831         if( (len > MSG_SIZ) && appData.debugMode )
832           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
833
834         DisplayFatalError(buf, 0, 2);
835       }
836     else
837       {
838         cps->protocolVersion = appData.protocolVersion[n];
839       }
840
841     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
842 }
843
844 ChessProgramState *savCps;
845
846 void
847 LoadEngine()
848 {
849     int i;
850     if(WaitForEngine(savCps, LoadEngine)) return;
851     CommonEngineInit(); // recalculate time odds
852     if(gameInfo.variant != StringToVariant(appData.variant)) {
853         // we changed variant when loading the engine; this forces us to reset
854         Reset(TRUE, savCps != &first);
855         EditGameEvent(); // for consistency with other path, as Reset changes mode
856     }
857     InitChessProgram(savCps, FALSE);
858     SendToProgram("force\n", savCps);
859     DisplayMessage("", "");
860     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
861     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
862     ThawUI();
863     SetGNUMode();
864 }
865
866 void
867 ReplaceEngine(ChessProgramState *cps, int n)
868 {
869     EditGameEvent();
870     UnloadEngine(cps);
871     appData.noChessProgram = FALSE;
872     appData.clockMode = TRUE;
873     InitEngine(cps, n);
874     UpdateLogos(TRUE);
875     if(n) return; // only startup first engine immediately; second can wait
876     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
877     LoadEngine();
878 }
879
880 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
881 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
882
883 static char resetOptions[] = 
884         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
885         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
886
887 void
888 Load(ChessProgramState *cps, int i)
889 {
890     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
891     if(engineLine[0]) { // an engine was selected from the combo box
892         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
893         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
894         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
895         ParseArgsFromString(buf);
896         SwapEngines(i);
897         ReplaceEngine(cps, i);
898         return;
899     }
900     p = engineName;
901     while(q = strchr(p, SLASH)) p = q+1;
902     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
903     if(engineDir[0] != NULLCHAR)
904         appData.directory[i] = engineDir;
905     else if(p != engineName) { // derive directory from engine path, when not given
906         p[-1] = 0;
907         appData.directory[i] = strdup(engineName);
908         p[-1] = SLASH;
909     } else appData.directory[i] = ".";
910     if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
911     if(params[0]) {
912         snprintf(command, MSG_SIZ, "%s %s", p, params);
913         p = command;
914     }
915     appData.chessProgram[i] = strdup(p);
916     appData.isUCI[i] = isUCI;
917     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
918     appData.hasOwnBookUCI[i] = hasBook;
919     if(!nickName[0]) useNick = FALSE;
920     if(useNick) ASSIGN(appData.pgnName[i], nickName);
921     if(addToList) {
922         int len;
923         char quote;
924         q = firstChessProgramNames;
925         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
926         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
927         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
928                         quote, p, quote, appData.directory[i], 
929                         useNick ? " -fn \"" : "",
930                         useNick ? nickName : "",
931                         useNick ? "\"" : "",
932                         v1 ? " -firstProtocolVersion 1" : "",
933                         hasBook ? "" : " -fNoOwnBookUCI",
934                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
935                         storeVariant ? " -variant " : "",
936                         storeVariant ? VariantName(gameInfo.variant) : "");
937         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
938         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
939         if(q)   free(q);
940     }
941     ReplaceEngine(cps, i);
942 }
943
944 void
945 InitTimeControls()
946 {
947     int matched, min, sec;
948     /*
949      * Parse timeControl resource
950      */
951     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
952                           appData.movesPerSession)) {
953         char buf[MSG_SIZ];
954         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
955         DisplayFatalError(buf, 0, 2);
956     }
957
958     /*
959      * Parse searchTime resource
960      */
961     if (*appData.searchTime != NULLCHAR) {
962         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
963         if (matched == 1) {
964             searchTime = min * 60;
965         } else if (matched == 2) {
966             searchTime = min * 60 + sec;
967         } else {
968             char buf[MSG_SIZ];
969             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
970             DisplayFatalError(buf, 0, 2);
971         }
972     }
973 }
974
975 void
976 InitBackEnd1()
977 {
978
979     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
980     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
981
982     GetTimeMark(&programStartTime);
983     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
984     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
985
986     ClearProgramStats();
987     programStats.ok_to_send = 1;
988     programStats.seen_stat = 0;
989
990     /*
991      * Initialize game list
992      */
993     ListNew(&gameList);
994
995
996     /*
997      * Internet chess server status
998      */
999     if (appData.icsActive) {
1000         appData.matchMode = FALSE;
1001         appData.matchGames = 0;
1002 #if ZIPPY
1003         appData.noChessProgram = !appData.zippyPlay;
1004 #else
1005         appData.zippyPlay = FALSE;
1006         appData.zippyTalk = FALSE;
1007         appData.noChessProgram = TRUE;
1008 #endif
1009         if (*appData.icsHelper != NULLCHAR) {
1010             appData.useTelnet = TRUE;
1011             appData.telnetProgram = appData.icsHelper;
1012         }
1013     } else {
1014         appData.zippyTalk = appData.zippyPlay = FALSE;
1015     }
1016
1017     /* [AS] Initialize pv info list [HGM] and game state */
1018     {
1019         int i, j;
1020
1021         for( i=0; i<=framePtr; i++ ) {
1022             pvInfoList[i].depth = -1;
1023             boards[i][EP_STATUS] = EP_NONE;
1024             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1025         }
1026     }
1027
1028     InitTimeControls();
1029
1030     /* [AS] Adjudication threshold */
1031     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1032
1033     InitEngine(&first, 0);
1034     InitEngine(&second, 1);
1035     CommonEngineInit();
1036
1037     pairing.which = "pairing"; // pairing engine
1038     pairing.pr = NoProc;
1039     pairing.isr = NULL;
1040     pairing.program = appData.pairingEngine;
1041     pairing.host = "localhost";
1042     pairing.dir = ".";
1043
1044     if (appData.icsActive) {
1045         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1046     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1047         appData.clockMode = FALSE;
1048         first.sendTime = second.sendTime = 0;
1049     }
1050
1051 #if ZIPPY
1052     /* Override some settings from environment variables, for backward
1053        compatibility.  Unfortunately it's not feasible to have the env
1054        vars just set defaults, at least in xboard.  Ugh.
1055     */
1056     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1057       ZippyInit();
1058     }
1059 #endif
1060
1061     if (!appData.icsActive) {
1062       char buf[MSG_SIZ];
1063       int len;
1064
1065       /* Check for variants that are supported only in ICS mode,
1066          or not at all.  Some that are accepted here nevertheless
1067          have bugs; see comments below.
1068       */
1069       VariantClass variant = StringToVariant(appData.variant);
1070       switch (variant) {
1071       case VariantBughouse:     /* need four players and two boards */
1072       case VariantKriegspiel:   /* need to hide pieces and move details */
1073         /* case VariantFischeRandom: (Fabien: moved below) */
1074         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1075         if( (len > MSG_SIZ) && appData.debugMode )
1076           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1077
1078         DisplayFatalError(buf, 0, 2);
1079         return;
1080
1081       case VariantUnknown:
1082       case VariantLoadable:
1083       case Variant29:
1084       case Variant30:
1085       case Variant31:
1086       case Variant32:
1087       case Variant33:
1088       case Variant34:
1089       case Variant35:
1090       case Variant36:
1091       default:
1092         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1093         if( (len > MSG_SIZ) && appData.debugMode )
1094           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1095
1096         DisplayFatalError(buf, 0, 2);
1097         return;
1098
1099       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1100       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1101       case VariantGothic:     /* [HGM] should work */
1102       case VariantCapablanca: /* [HGM] should work */
1103       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1104       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1105       case VariantKnightmate: /* [HGM] should work */
1106       case VariantCylinder:   /* [HGM] untested */
1107       case VariantFalcon:     /* [HGM] untested */
1108       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1109                                  offboard interposition not understood */
1110       case VariantNormal:     /* definitely works! */
1111       case VariantWildCastle: /* pieces not automatically shuffled */
1112       case VariantNoCastle:   /* pieces not automatically shuffled */
1113       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1114       case VariantLosers:     /* should work except for win condition,
1115                                  and doesn't know captures are mandatory */
1116       case VariantSuicide:    /* should work except for win condition,
1117                                  and doesn't know captures are mandatory */
1118       case VariantGiveaway:   /* should work except for win condition,
1119                                  and doesn't know captures are mandatory */
1120       case VariantTwoKings:   /* should work */
1121       case VariantAtomic:     /* should work except for win condition */
1122       case Variant3Check:     /* should work except for win condition */
1123       case VariantShatranj:   /* should work except for all win conditions */
1124       case VariantMakruk:     /* should work except for draw countdown */
1125       case VariantBerolina:   /* might work if TestLegality is off */
1126       case VariantCapaRandom: /* should work */
1127       case VariantJanus:      /* should work */
1128       case VariantSuper:      /* experimental */
1129       case VariantGreat:      /* experimental, requires legality testing to be off */
1130       case VariantSChess:     /* S-Chess, should work */
1131       case VariantGrand:      /* should work */
1132       case VariantSpartan:    /* should work */
1133         break;
1134       }
1135     }
1136
1137 }
1138
1139 int NextIntegerFromString( char ** str, long * value )
1140 {
1141     int result = -1;
1142     char * s = *str;
1143
1144     while( *s == ' ' || *s == '\t' ) {
1145         s++;
1146     }
1147
1148     *value = 0;
1149
1150     if( *s >= '0' && *s <= '9' ) {
1151         while( *s >= '0' && *s <= '9' ) {
1152             *value = *value * 10 + (*s - '0');
1153             s++;
1154         }
1155
1156         result = 0;
1157     }
1158
1159     *str = s;
1160
1161     return result;
1162 }
1163
1164 int NextTimeControlFromString( char ** str, long * value )
1165 {
1166     long temp;
1167     int result = NextIntegerFromString( str, &temp );
1168
1169     if( result == 0 ) {
1170         *value = temp * 60; /* Minutes */
1171         if( **str == ':' ) {
1172             (*str)++;
1173             result = NextIntegerFromString( str, &temp );
1174             *value += temp; /* Seconds */
1175         }
1176     }
1177
1178     return result;
1179 }
1180
1181 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1182 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1183     int result = -1, type = 0; long temp, temp2;
1184
1185     if(**str != ':') return -1; // old params remain in force!
1186     (*str)++;
1187     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1188     if( NextIntegerFromString( str, &temp ) ) return -1;
1189     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1190
1191     if(**str != '/') {
1192         /* time only: incremental or sudden-death time control */
1193         if(**str == '+') { /* increment follows; read it */
1194             (*str)++;
1195             if(**str == '!') type = *(*str)++; // Bronstein TC
1196             if(result = NextIntegerFromString( str, &temp2)) return -1;
1197             *inc = temp2 * 1000;
1198             if(**str == '.') { // read fraction of increment
1199                 char *start = ++(*str);
1200                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1201                 temp2 *= 1000;
1202                 while(start++ < *str) temp2 /= 10;
1203                 *inc += temp2;
1204             }
1205         } else *inc = 0;
1206         *moves = 0; *tc = temp * 1000; *incType = type;
1207         return 0;
1208     }
1209
1210     (*str)++; /* classical time control */
1211     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1212
1213     if(result == 0) {
1214         *moves = temp;
1215         *tc    = temp2 * 1000;
1216         *inc   = 0;
1217         *incType = type;
1218     }
1219     return result;
1220 }
1221
1222 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1223 {   /* [HGM] get time to add from the multi-session time-control string */
1224     int incType, moves=1; /* kludge to force reading of first session */
1225     long time, increment;
1226     char *s = tcString;
1227
1228     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1229     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1230     do {
1231         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1232         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1233         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1234         if(movenr == -1) return time;    /* last move before new session     */
1235         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1236         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1237         if(!moves) return increment;     /* current session is incremental   */
1238         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1239     } while(movenr >= -1);               /* try again for next session       */
1240
1241     return 0; // no new time quota on this move
1242 }
1243
1244 int
1245 ParseTimeControl(tc, ti, mps)
1246      char *tc;
1247      float ti;
1248      int mps;
1249 {
1250   long tc1;
1251   long tc2;
1252   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1253   int min, sec=0;
1254
1255   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1256   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1257       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1258   if(ti > 0) {
1259
1260     if(mps)
1261       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1262     else 
1263       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1264   } else {
1265     if(mps)
1266       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1267     else 
1268       snprintf(buf, MSG_SIZ, ":%s", mytc);
1269   }
1270   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1271   
1272   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1273     return FALSE;
1274   }
1275
1276   if( *tc == '/' ) {
1277     /* Parse second time control */
1278     tc++;
1279
1280     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1281       return FALSE;
1282     }
1283
1284     if( tc2 == 0 ) {
1285       return FALSE;
1286     }
1287
1288     timeControl_2 = tc2 * 1000;
1289   }
1290   else {
1291     timeControl_2 = 0;
1292   }
1293
1294   if( tc1 == 0 ) {
1295     return FALSE;
1296   }
1297
1298   timeControl = tc1 * 1000;
1299
1300   if (ti >= 0) {
1301     timeIncrement = ti * 1000;  /* convert to ms */
1302     movesPerSession = 0;
1303   } else {
1304     timeIncrement = 0;
1305     movesPerSession = mps;
1306   }
1307   return TRUE;
1308 }
1309
1310 void
1311 InitBackEnd2()
1312 {
1313     if (appData.debugMode) {
1314         fprintf(debugFP, "%s\n", programVersion);
1315     }
1316
1317     set_cont_sequence(appData.wrapContSeq);
1318     if (appData.matchGames > 0) {
1319         appData.matchMode = TRUE;
1320     } else if (appData.matchMode) {
1321         appData.matchGames = 1;
1322     }
1323     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1324         appData.matchGames = appData.sameColorGames;
1325     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1326         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1327         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1328     }
1329     Reset(TRUE, FALSE);
1330     if (appData.noChessProgram || first.protocolVersion == 1) {
1331       InitBackEnd3();
1332     } else {
1333       /* kludge: allow timeout for initial "feature" commands */
1334       FreezeUI();
1335       DisplayMessage("", _("Starting chess program"));
1336       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1337     }
1338 }
1339
1340 int
1341 CalculateIndex(int index, int gameNr)
1342 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1343     int res;
1344     if(index > 0) return index; // fixed nmber
1345     if(index == 0) return 1;
1346     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1347     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1348     return res;
1349 }
1350
1351 int
1352 LoadGameOrPosition(int gameNr)
1353 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1354     if (*appData.loadGameFile != NULLCHAR) {
1355         if (!LoadGameFromFile(appData.loadGameFile,
1356                 CalculateIndex(appData.loadGameIndex, gameNr),
1357                               appData.loadGameFile, FALSE)) {
1358             DisplayFatalError(_("Bad game file"), 0, 1);
1359             return 0;
1360         }
1361     } else if (*appData.loadPositionFile != NULLCHAR) {
1362         if (!LoadPositionFromFile(appData.loadPositionFile,
1363                 CalculateIndex(appData.loadPositionIndex, gameNr),
1364                                   appData.loadPositionFile)) {
1365             DisplayFatalError(_("Bad position file"), 0, 1);
1366             return 0;
1367         }
1368     }
1369     return 1;
1370 }
1371
1372 void
1373 ReserveGame(int gameNr, char resChar)
1374 {
1375     FILE *tf = fopen(appData.tourneyFile, "r+");
1376     char *p, *q, c, buf[MSG_SIZ];
1377     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1378     safeStrCpy(buf, lastMsg, MSG_SIZ);
1379     DisplayMessage(_("Pick new game"), "");
1380     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1381     ParseArgsFromFile(tf);
1382     p = q = appData.results;
1383     if(appData.debugMode) {
1384       char *r = appData.participants;
1385       fprintf(debugFP, "results = '%s'\n", p);
1386       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1387       fprintf(debugFP, "\n");
1388     }
1389     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1390     nextGame = q - p;
1391     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1392     safeStrCpy(q, p, strlen(p) + 2);
1393     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1394     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1395     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1396         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1397         q[nextGame] = '*';
1398     }
1399     fseek(tf, -(strlen(p)+4), SEEK_END);
1400     c = fgetc(tf);
1401     if(c != '"') // depending on DOS or Unix line endings we can be one off
1402          fseek(tf, -(strlen(p)+2), SEEK_END);
1403     else fseek(tf, -(strlen(p)+3), SEEK_END);
1404     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1405     DisplayMessage(buf, "");
1406     free(p); appData.results = q;
1407     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1408        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1409         UnloadEngine(&first);  // next game belongs to other pairing;
1410         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1411     }
1412 }
1413
1414 void
1415 MatchEvent(int mode)
1416 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1417         int dummy;
1418         if(matchMode) { // already in match mode: switch it off
1419             abortMatch = TRUE;
1420             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1421             return;
1422         }
1423 //      if(gameMode != BeginningOfGame) {
1424 //          DisplayError(_("You can only start a match from the initial position."), 0);
1425 //          return;
1426 //      }
1427         abortMatch = FALSE;
1428         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1429         /* Set up machine vs. machine match */
1430         nextGame = 0;
1431         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1432         if(appData.tourneyFile[0]) {
1433             ReserveGame(-1, 0);
1434             if(nextGame > appData.matchGames) {
1435                 char buf[MSG_SIZ];
1436                 if(strchr(appData.results, '*') == NULL) {
1437                     FILE *f;
1438                     appData.tourneyCycles++;
1439                     if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1440                         fclose(f);
1441                         NextTourneyGame(-1, &dummy);
1442                         ReserveGame(-1, 0);
1443                         if(nextGame <= appData.matchGames) {
1444                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1445                             matchMode = mode;
1446                             ScheduleDelayedEvent(NextMatchGame, 10000);
1447                             return;
1448                         }
1449                     }
1450                 }
1451                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1452                 DisplayError(buf, 0);
1453                 appData.tourneyFile[0] = 0;
1454                 return;
1455             }
1456         } else
1457         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1458             DisplayFatalError(_("Can't have a match with no chess programs"),
1459                               0, 2);
1460             return;
1461         }
1462         matchMode = mode;
1463         matchGame = roundNr = 1;
1464         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1465         NextMatchGame();
1466 }
1467
1468 void
1469 InitBackEnd3 P((void))
1470 {
1471     GameMode initialMode;
1472     char buf[MSG_SIZ];
1473     int err, len;
1474
1475     InitChessProgram(&first, startedFromSetupPosition);
1476
1477     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1478         free(programVersion);
1479         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1480         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1481     }
1482
1483     if (appData.icsActive) {
1484 #ifdef WIN32
1485         /* [DM] Make a console window if needed [HGM] merged ifs */
1486         ConsoleCreate();
1487 #endif
1488         err = establish();
1489         if (err != 0)
1490           {
1491             if (*appData.icsCommPort != NULLCHAR)
1492               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1493                              appData.icsCommPort);
1494             else
1495               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1496                         appData.icsHost, appData.icsPort);
1497
1498             if( (len > MSG_SIZ) && appData.debugMode )
1499               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1500
1501             DisplayFatalError(buf, err, 1);
1502             return;
1503         }
1504         SetICSMode();
1505         telnetISR =
1506           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1507         fromUserISR =
1508           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1509         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1510             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1511     } else if (appData.noChessProgram) {
1512         SetNCPMode();
1513     } else {
1514         SetGNUMode();
1515     }
1516
1517     if (*appData.cmailGameName != NULLCHAR) {
1518         SetCmailMode();
1519         OpenLoopback(&cmailPR);
1520         cmailISR =
1521           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1522     }
1523
1524     ThawUI();
1525     DisplayMessage("", "");
1526     if (StrCaseCmp(appData.initialMode, "") == 0) {
1527       initialMode = BeginningOfGame;
1528       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1529         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1530         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1531         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1532         ModeHighlight();
1533       }
1534     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1535       initialMode = TwoMachinesPlay;
1536     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1537       initialMode = AnalyzeFile;
1538     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1539       initialMode = AnalyzeMode;
1540     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1541       initialMode = MachinePlaysWhite;
1542     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1543       initialMode = MachinePlaysBlack;
1544     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1545       initialMode = EditGame;
1546     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1547       initialMode = EditPosition;
1548     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1549       initialMode = Training;
1550     } else {
1551       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1552       if( (len > MSG_SIZ) && appData.debugMode )
1553         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1554
1555       DisplayFatalError(buf, 0, 2);
1556       return;
1557     }
1558
1559     if (appData.matchMode) {
1560         if(appData.tourneyFile[0]) { // start tourney from command line
1561             FILE *f;
1562             if(f = fopen(appData.tourneyFile, "r")) {
1563                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1564                 fclose(f);
1565                 appData.clockMode = TRUE;
1566                 SetGNUMode();
1567             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1568         }
1569         MatchEvent(TRUE);
1570     } else if (*appData.cmailGameName != NULLCHAR) {
1571         /* Set up cmail mode */
1572         ReloadCmailMsgEvent(TRUE);
1573     } else {
1574         /* Set up other modes */
1575         if (initialMode == AnalyzeFile) {
1576           if (*appData.loadGameFile == NULLCHAR) {
1577             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1578             return;
1579           }
1580         }
1581         if (*appData.loadGameFile != NULLCHAR) {
1582             (void) LoadGameFromFile(appData.loadGameFile,
1583                                     appData.loadGameIndex,
1584                                     appData.loadGameFile, TRUE);
1585         } else if (*appData.loadPositionFile != NULLCHAR) {
1586             (void) LoadPositionFromFile(appData.loadPositionFile,
1587                                         appData.loadPositionIndex,
1588                                         appData.loadPositionFile);
1589             /* [HGM] try to make self-starting even after FEN load */
1590             /* to allow automatic setup of fairy variants with wtm */
1591             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1592                 gameMode = BeginningOfGame;
1593                 setboardSpoiledMachineBlack = 1;
1594             }
1595             /* [HGM] loadPos: make that every new game uses the setup */
1596             /* from file as long as we do not switch variant          */
1597             if(!blackPlaysFirst) {
1598                 startedFromPositionFile = TRUE;
1599                 CopyBoard(filePosition, boards[0]);
1600             }
1601         }
1602         if (initialMode == AnalyzeMode) {
1603           if (appData.noChessProgram) {
1604             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1605             return;
1606           }
1607           if (appData.icsActive) {
1608             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1609             return;
1610           }
1611           AnalyzeModeEvent();
1612         } else if (initialMode == AnalyzeFile) {
1613           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1614           ShowThinkingEvent();
1615           AnalyzeFileEvent();
1616           AnalysisPeriodicEvent(1);
1617         } else if (initialMode == MachinePlaysWhite) {
1618           if (appData.noChessProgram) {
1619             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1620                               0, 2);
1621             return;
1622           }
1623           if (appData.icsActive) {
1624             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1625                               0, 2);
1626             return;
1627           }
1628           MachineWhiteEvent();
1629         } else if (initialMode == MachinePlaysBlack) {
1630           if (appData.noChessProgram) {
1631             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1632                               0, 2);
1633             return;
1634           }
1635           if (appData.icsActive) {
1636             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1637                               0, 2);
1638             return;
1639           }
1640           MachineBlackEvent();
1641         } else if (initialMode == TwoMachinesPlay) {
1642           if (appData.noChessProgram) {
1643             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1644                               0, 2);
1645             return;
1646           }
1647           if (appData.icsActive) {
1648             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1649                               0, 2);
1650             return;
1651           }
1652           TwoMachinesEvent();
1653         } else if (initialMode == EditGame) {
1654           EditGameEvent();
1655         } else if (initialMode == EditPosition) {
1656           EditPositionEvent();
1657         } else if (initialMode == Training) {
1658           if (*appData.loadGameFile == NULLCHAR) {
1659             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1660             return;
1661           }
1662           TrainingEvent();
1663         }
1664     }
1665 }
1666
1667 /*
1668  * Establish will establish a contact to a remote host.port.
1669  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1670  *  used to talk to the host.
1671  * Returns 0 if okay, error code if not.
1672  */
1673 int
1674 establish()
1675 {
1676     char buf[MSG_SIZ];
1677
1678     if (*appData.icsCommPort != NULLCHAR) {
1679         /* Talk to the host through a serial comm port */
1680         return OpenCommPort(appData.icsCommPort, &icsPR);
1681
1682     } else if (*appData.gateway != NULLCHAR) {
1683         if (*appData.remoteShell == NULLCHAR) {
1684             /* Use the rcmd protocol to run telnet program on a gateway host */
1685             snprintf(buf, sizeof(buf), "%s %s %s",
1686                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1687             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1688
1689         } else {
1690             /* Use the rsh program to run telnet program on a gateway host */
1691             if (*appData.remoteUser == NULLCHAR) {
1692                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1693                         appData.gateway, appData.telnetProgram,
1694                         appData.icsHost, appData.icsPort);
1695             } else {
1696                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1697                         appData.remoteShell, appData.gateway,
1698                         appData.remoteUser, appData.telnetProgram,
1699                         appData.icsHost, appData.icsPort);
1700             }
1701             return StartChildProcess(buf, "", &icsPR);
1702
1703         }
1704     } else if (appData.useTelnet) {
1705         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1706
1707     } else {
1708         /* TCP socket interface differs somewhat between
1709            Unix and NT; handle details in the front end.
1710            */
1711         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1712     }
1713 }
1714
1715 void EscapeExpand(char *p, char *q)
1716 {       // [HGM] initstring: routine to shape up string arguments
1717         while(*p++ = *q++) if(p[-1] == '\\')
1718             switch(*q++) {
1719                 case 'n': p[-1] = '\n'; break;
1720                 case 'r': p[-1] = '\r'; break;
1721                 case 't': p[-1] = '\t'; break;
1722                 case '\\': p[-1] = '\\'; break;
1723                 case 0: *p = 0; return;
1724                 default: p[-1] = q[-1]; break;
1725             }
1726 }
1727
1728 void
1729 show_bytes(fp, buf, count)
1730      FILE *fp;
1731      char *buf;
1732      int count;
1733 {
1734     while (count--) {
1735         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1736             fprintf(fp, "\\%03o", *buf & 0xff);
1737         } else {
1738             putc(*buf, fp);
1739         }
1740         buf++;
1741     }
1742     fflush(fp);
1743 }
1744
1745 /* Returns an errno value */
1746 int
1747 OutputMaybeTelnet(pr, message, count, outError)
1748      ProcRef pr;
1749      char *message;
1750      int count;
1751      int *outError;
1752 {
1753     char buf[8192], *p, *q, *buflim;
1754     int left, newcount, outcount;
1755
1756     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1757         *appData.gateway != NULLCHAR) {
1758         if (appData.debugMode) {
1759             fprintf(debugFP, ">ICS: ");
1760             show_bytes(debugFP, message, count);
1761             fprintf(debugFP, "\n");
1762         }
1763         return OutputToProcess(pr, message, count, outError);
1764     }
1765
1766     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1767     p = message;
1768     q = buf;
1769     left = count;
1770     newcount = 0;
1771     while (left) {
1772         if (q >= buflim) {
1773             if (appData.debugMode) {
1774                 fprintf(debugFP, ">ICS: ");
1775                 show_bytes(debugFP, buf, newcount);
1776                 fprintf(debugFP, "\n");
1777             }
1778             outcount = OutputToProcess(pr, buf, newcount, outError);
1779             if (outcount < newcount) return -1; /* to be sure */
1780             q = buf;
1781             newcount = 0;
1782         }
1783         if (*p == '\n') {
1784             *q++ = '\r';
1785             newcount++;
1786         } else if (((unsigned char) *p) == TN_IAC) {
1787             *q++ = (char) TN_IAC;
1788             newcount ++;
1789         }
1790         *q++ = *p++;
1791         newcount++;
1792         left--;
1793     }
1794     if (appData.debugMode) {
1795         fprintf(debugFP, ">ICS: ");
1796         show_bytes(debugFP, buf, newcount);
1797         fprintf(debugFP, "\n");
1798     }
1799     outcount = OutputToProcess(pr, buf, newcount, outError);
1800     if (outcount < newcount) return -1; /* to be sure */
1801     return count;
1802 }
1803
1804 void
1805 read_from_player(isr, closure, message, count, error)
1806      InputSourceRef isr;
1807      VOIDSTAR closure;
1808      char *message;
1809      int count;
1810      int error;
1811 {
1812     int outError, outCount;
1813     static int gotEof = 0;
1814
1815     /* Pass data read from player on to ICS */
1816     if (count > 0) {
1817         gotEof = 0;
1818         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1819         if (outCount < count) {
1820             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1821         }
1822     } else if (count < 0) {
1823         RemoveInputSource(isr);
1824         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1825     } else if (gotEof++ > 0) {
1826         RemoveInputSource(isr);
1827         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1828     }
1829 }
1830
1831 void
1832 KeepAlive()
1833 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1834     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1835     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1836     SendToICS("date\n");
1837     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1838 }
1839
1840 /* added routine for printf style output to ics */
1841 void ics_printf(char *format, ...)
1842 {
1843     char buffer[MSG_SIZ];
1844     va_list args;
1845
1846     va_start(args, format);
1847     vsnprintf(buffer, sizeof(buffer), format, args);
1848     buffer[sizeof(buffer)-1] = '\0';
1849     SendToICS(buffer);
1850     va_end(args);
1851 }
1852
1853 void
1854 SendToICS(s)
1855      char *s;
1856 {
1857     int count, outCount, outError;
1858
1859     if (icsPR == NULL) return;
1860
1861     count = strlen(s);
1862     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1863     if (outCount < count) {
1864         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1865     }
1866 }
1867
1868 /* This is used for sending logon scripts to the ICS. Sending
1869    without a delay causes problems when using timestamp on ICC
1870    (at least on my machine). */
1871 void
1872 SendToICSDelayed(s,msdelay)
1873      char *s;
1874      long msdelay;
1875 {
1876     int count, outCount, outError;
1877
1878     if (icsPR == NULL) return;
1879
1880     count = strlen(s);
1881     if (appData.debugMode) {
1882         fprintf(debugFP, ">ICS: ");
1883         show_bytes(debugFP, s, count);
1884         fprintf(debugFP, "\n");
1885     }
1886     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1887                                       msdelay);
1888     if (outCount < count) {
1889         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1890     }
1891 }
1892
1893
1894 /* Remove all highlighting escape sequences in s
1895    Also deletes any suffix starting with '('
1896    */
1897 char *
1898 StripHighlightAndTitle(s)
1899      char *s;
1900 {
1901     static char retbuf[MSG_SIZ];
1902     char *p = retbuf;
1903
1904     while (*s != NULLCHAR) {
1905         while (*s == '\033') {
1906             while (*s != NULLCHAR && !isalpha(*s)) s++;
1907             if (*s != NULLCHAR) s++;
1908         }
1909         while (*s != NULLCHAR && *s != '\033') {
1910             if (*s == '(' || *s == '[') {
1911                 *p = NULLCHAR;
1912                 return retbuf;
1913             }
1914             *p++ = *s++;
1915         }
1916     }
1917     *p = NULLCHAR;
1918     return retbuf;
1919 }
1920
1921 /* Remove all highlighting escape sequences in s */
1922 char *
1923 StripHighlight(s)
1924      char *s;
1925 {
1926     static char retbuf[MSG_SIZ];
1927     char *p = retbuf;
1928
1929     while (*s != NULLCHAR) {
1930         while (*s == '\033') {
1931             while (*s != NULLCHAR && !isalpha(*s)) s++;
1932             if (*s != NULLCHAR) s++;
1933         }
1934         while (*s != NULLCHAR && *s != '\033') {
1935             *p++ = *s++;
1936         }
1937     }
1938     *p = NULLCHAR;
1939     return retbuf;
1940 }
1941
1942 char *variantNames[] = VARIANT_NAMES;
1943 char *
1944 VariantName(v)
1945      VariantClass v;
1946 {
1947     return variantNames[v];
1948 }
1949
1950
1951 /* Identify a variant from the strings the chess servers use or the
1952    PGN Variant tag names we use. */
1953 VariantClass
1954 StringToVariant(e)
1955      char *e;
1956 {
1957     char *p;
1958     int wnum = -1;
1959     VariantClass v = VariantNormal;
1960     int i, found = FALSE;
1961     char buf[MSG_SIZ];
1962     int len;
1963
1964     if (!e) return v;
1965
1966     /* [HGM] skip over optional board-size prefixes */
1967     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1968         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1969         while( *e++ != '_');
1970     }
1971
1972     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1973         v = VariantNormal;
1974         found = TRUE;
1975     } else
1976     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1977       if (StrCaseStr(e, variantNames[i])) {
1978         v = (VariantClass) i;
1979         found = TRUE;
1980         break;
1981       }
1982     }
1983
1984     if (!found) {
1985       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1986           || StrCaseStr(e, "wild/fr")
1987           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1988         v = VariantFischeRandom;
1989       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1990                  (i = 1, p = StrCaseStr(e, "w"))) {
1991         p += i;
1992         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1993         if (isdigit(*p)) {
1994           wnum = atoi(p);
1995         } else {
1996           wnum = -1;
1997         }
1998         switch (wnum) {
1999         case 0: /* FICS only, actually */
2000         case 1:
2001           /* Castling legal even if K starts on d-file */
2002           v = VariantWildCastle;
2003           break;
2004         case 2:
2005         case 3:
2006         case 4:
2007           /* Castling illegal even if K & R happen to start in
2008              normal positions. */
2009           v = VariantNoCastle;
2010           break;
2011         case 5:
2012         case 7:
2013         case 8:
2014         case 10:
2015         case 11:
2016         case 12:
2017         case 13:
2018         case 14:
2019         case 15:
2020         case 18:
2021         case 19:
2022           /* Castling legal iff K & R start in normal positions */
2023           v = VariantNormal;
2024           break;
2025         case 6:
2026         case 20:
2027         case 21:
2028           /* Special wilds for position setup; unclear what to do here */
2029           v = VariantLoadable;
2030           break;
2031         case 9:
2032           /* Bizarre ICC game */
2033           v = VariantTwoKings;
2034           break;
2035         case 16:
2036           v = VariantKriegspiel;
2037           break;
2038         case 17:
2039           v = VariantLosers;
2040           break;
2041         case 22:
2042           v = VariantFischeRandom;
2043           break;
2044         case 23:
2045           v = VariantCrazyhouse;
2046           break;
2047         case 24:
2048           v = VariantBughouse;
2049           break;
2050         case 25:
2051           v = Variant3Check;
2052           break;
2053         case 26:
2054           /* Not quite the same as FICS suicide! */
2055           v = VariantGiveaway;
2056           break;
2057         case 27:
2058           v = VariantAtomic;
2059           break;
2060         case 28:
2061           v = VariantShatranj;
2062           break;
2063
2064         /* Temporary names for future ICC types.  The name *will* change in
2065            the next xboard/WinBoard release after ICC defines it. */
2066         case 29:
2067           v = Variant29;
2068           break;
2069         case 30:
2070           v = Variant30;
2071           break;
2072         case 31:
2073           v = Variant31;
2074           break;
2075         case 32:
2076           v = Variant32;
2077           break;
2078         case 33:
2079           v = Variant33;
2080           break;
2081         case 34:
2082           v = Variant34;
2083           break;
2084         case 35:
2085           v = Variant35;
2086           break;
2087         case 36:
2088           v = Variant36;
2089           break;
2090         case 37:
2091           v = VariantShogi;
2092           break;
2093         case 38:
2094           v = VariantXiangqi;
2095           break;
2096         case 39:
2097           v = VariantCourier;
2098           break;
2099         case 40:
2100           v = VariantGothic;
2101           break;
2102         case 41:
2103           v = VariantCapablanca;
2104           break;
2105         case 42:
2106           v = VariantKnightmate;
2107           break;
2108         case 43:
2109           v = VariantFairy;
2110           break;
2111         case 44:
2112           v = VariantCylinder;
2113           break;
2114         case 45:
2115           v = VariantFalcon;
2116           break;
2117         case 46:
2118           v = VariantCapaRandom;
2119           break;
2120         case 47:
2121           v = VariantBerolina;
2122           break;
2123         case 48:
2124           v = VariantJanus;
2125           break;
2126         case 49:
2127           v = VariantSuper;
2128           break;
2129         case 50:
2130           v = VariantGreat;
2131           break;
2132         case -1:
2133           /* Found "wild" or "w" in the string but no number;
2134              must assume it's normal chess. */
2135           v = VariantNormal;
2136           break;
2137         default:
2138           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2139           if( (len > MSG_SIZ) && appData.debugMode )
2140             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2141
2142           DisplayError(buf, 0);
2143           v = VariantUnknown;
2144           break;
2145         }
2146       }
2147     }
2148     if (appData.debugMode) {
2149       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2150               e, wnum, VariantName(v));
2151     }
2152     return v;
2153 }
2154
2155 static int leftover_start = 0, leftover_len = 0;
2156 char star_match[STAR_MATCH_N][MSG_SIZ];
2157
2158 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2159    advance *index beyond it, and set leftover_start to the new value of
2160    *index; else return FALSE.  If pattern contains the character '*', it
2161    matches any sequence of characters not containing '\r', '\n', or the
2162    character following the '*' (if any), and the matched sequence(s) are
2163    copied into star_match.
2164    */
2165 int
2166 looking_at(buf, index, pattern)
2167      char *buf;
2168      int *index;
2169      char *pattern;
2170 {
2171     char *bufp = &buf[*index], *patternp = pattern;
2172     int star_count = 0;
2173     char *matchp = star_match[0];
2174
2175     for (;;) {
2176         if (*patternp == NULLCHAR) {
2177             *index = leftover_start = bufp - buf;
2178             *matchp = NULLCHAR;
2179             return TRUE;
2180         }
2181         if (*bufp == NULLCHAR) return FALSE;
2182         if (*patternp == '*') {
2183             if (*bufp == *(patternp + 1)) {
2184                 *matchp = NULLCHAR;
2185                 matchp = star_match[++star_count];
2186                 patternp += 2;
2187                 bufp++;
2188                 continue;
2189             } else if (*bufp == '\n' || *bufp == '\r') {
2190                 patternp++;
2191                 if (*patternp == NULLCHAR)
2192                   continue;
2193                 else
2194                   return FALSE;
2195             } else {
2196                 *matchp++ = *bufp++;
2197                 continue;
2198             }
2199         }
2200         if (*patternp != *bufp) return FALSE;
2201         patternp++;
2202         bufp++;
2203     }
2204 }
2205
2206 void
2207 SendToPlayer(data, length)
2208      char *data;
2209      int length;
2210 {
2211     int error, outCount;
2212     outCount = OutputToProcess(NoProc, data, length, &error);
2213     if (outCount < length) {
2214         DisplayFatalError(_("Error writing to display"), error, 1);
2215     }
2216 }
2217
2218 void
2219 PackHolding(packed, holding)
2220      char packed[];
2221      char *holding;
2222 {
2223     char *p = holding;
2224     char *q = packed;
2225     int runlength = 0;
2226     int curr = 9999;
2227     do {
2228         if (*p == curr) {
2229             runlength++;
2230         } else {
2231             switch (runlength) {
2232               case 0:
2233                 break;
2234               case 1:
2235                 *q++ = curr;
2236                 break;
2237               case 2:
2238                 *q++ = curr;
2239                 *q++ = curr;
2240                 break;
2241               default:
2242                 sprintf(q, "%d", runlength);
2243                 while (*q) q++;
2244                 *q++ = curr;
2245                 break;
2246             }
2247             runlength = 1;
2248             curr = *p;
2249         }
2250     } while (*p++);
2251     *q = NULLCHAR;
2252 }
2253
2254 /* Telnet protocol requests from the front end */
2255 void
2256 TelnetRequest(ddww, option)
2257      unsigned char ddww, option;
2258 {
2259     unsigned char msg[3];
2260     int outCount, outError;
2261
2262     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2263
2264     if (appData.debugMode) {
2265         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2266         switch (ddww) {
2267           case TN_DO:
2268             ddwwStr = "DO";
2269             break;
2270           case TN_DONT:
2271             ddwwStr = "DONT";
2272             break;
2273           case TN_WILL:
2274             ddwwStr = "WILL";
2275             break;
2276           case TN_WONT:
2277             ddwwStr = "WONT";
2278             break;
2279           default:
2280             ddwwStr = buf1;
2281             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2282             break;
2283         }
2284         switch (option) {
2285           case TN_ECHO:
2286             optionStr = "ECHO";
2287             break;
2288           default:
2289             optionStr = buf2;
2290             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2291             break;
2292         }
2293         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2294     }
2295     msg[0] = TN_IAC;
2296     msg[1] = ddww;
2297     msg[2] = option;
2298     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2299     if (outCount < 3) {
2300         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2301     }
2302 }
2303
2304 void
2305 DoEcho()
2306 {
2307     if (!appData.icsActive) return;
2308     TelnetRequest(TN_DO, TN_ECHO);
2309 }
2310
2311 void
2312 DontEcho()
2313 {
2314     if (!appData.icsActive) return;
2315     TelnetRequest(TN_DONT, TN_ECHO);
2316 }
2317
2318 void
2319 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2320 {
2321     /* put the holdings sent to us by the server on the board holdings area */
2322     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2323     char p;
2324     ChessSquare piece;
2325
2326     if(gameInfo.holdingsWidth < 2)  return;
2327     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2328         return; // prevent overwriting by pre-board holdings
2329
2330     if( (int)lowestPiece >= BlackPawn ) {
2331         holdingsColumn = 0;
2332         countsColumn = 1;
2333         holdingsStartRow = BOARD_HEIGHT-1;
2334         direction = -1;
2335     } else {
2336         holdingsColumn = BOARD_WIDTH-1;
2337         countsColumn = BOARD_WIDTH-2;
2338         holdingsStartRow = 0;
2339         direction = 1;
2340     }
2341
2342     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2343         board[i][holdingsColumn] = EmptySquare;
2344         board[i][countsColumn]   = (ChessSquare) 0;
2345     }
2346     while( (p=*holdings++) != NULLCHAR ) {
2347         piece = CharToPiece( ToUpper(p) );
2348         if(piece == EmptySquare) continue;
2349         /*j = (int) piece - (int) WhitePawn;*/
2350         j = PieceToNumber(piece);
2351         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2352         if(j < 0) continue;               /* should not happen */
2353         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2354         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2355         board[holdingsStartRow+j*direction][countsColumn]++;
2356     }
2357 }
2358
2359
2360 void
2361 VariantSwitch(Board board, VariantClass newVariant)
2362 {
2363    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2364    static Board oldBoard;
2365
2366    startedFromPositionFile = FALSE;
2367    if(gameInfo.variant == newVariant) return;
2368
2369    /* [HGM] This routine is called each time an assignment is made to
2370     * gameInfo.variant during a game, to make sure the board sizes
2371     * are set to match the new variant. If that means adding or deleting
2372     * holdings, we shift the playing board accordingly
2373     * This kludge is needed because in ICS observe mode, we get boards
2374     * of an ongoing game without knowing the variant, and learn about the
2375     * latter only later. This can be because of the move list we requested,
2376     * in which case the game history is refilled from the beginning anyway,
2377     * but also when receiving holdings of a crazyhouse game. In the latter
2378     * case we want to add those holdings to the already received position.
2379     */
2380
2381
2382    if (appData.debugMode) {
2383      fprintf(debugFP, "Switch board from %s to %s\n",
2384              VariantName(gameInfo.variant), VariantName(newVariant));
2385      setbuf(debugFP, NULL);
2386    }
2387    shuffleOpenings = 0;       /* [HGM] shuffle */
2388    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2389    switch(newVariant)
2390      {
2391      case VariantShogi:
2392        newWidth = 9;  newHeight = 9;
2393        gameInfo.holdingsSize = 7;
2394      case VariantBughouse:
2395      case VariantCrazyhouse:
2396        newHoldingsWidth = 2; break;
2397      case VariantGreat:
2398        newWidth = 10;
2399      case VariantSuper:
2400        newHoldingsWidth = 2;
2401        gameInfo.holdingsSize = 8;
2402        break;
2403      case VariantGothic:
2404      case VariantCapablanca:
2405      case VariantCapaRandom:
2406        newWidth = 10;
2407      default:
2408        newHoldingsWidth = gameInfo.holdingsSize = 0;
2409      };
2410
2411    if(newWidth  != gameInfo.boardWidth  ||
2412       newHeight != gameInfo.boardHeight ||
2413       newHoldingsWidth != gameInfo.holdingsWidth ) {
2414
2415      /* shift position to new playing area, if needed */
2416      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2417        for(i=0; i<BOARD_HEIGHT; i++)
2418          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2419            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2420              board[i][j];
2421        for(i=0; i<newHeight; i++) {
2422          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2423          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2424        }
2425      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2426        for(i=0; i<BOARD_HEIGHT; i++)
2427          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2428            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2429              board[i][j];
2430      }
2431      gameInfo.boardWidth  = newWidth;
2432      gameInfo.boardHeight = newHeight;
2433      gameInfo.holdingsWidth = newHoldingsWidth;
2434      gameInfo.variant = newVariant;
2435      InitDrawingSizes(-2, 0);
2436    } else gameInfo.variant = newVariant;
2437    CopyBoard(oldBoard, board);   // remember correctly formatted board
2438      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2439    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2440 }
2441
2442 static int loggedOn = FALSE;
2443
2444 /*-- Game start info cache: --*/
2445 int gs_gamenum;
2446 char gs_kind[MSG_SIZ];
2447 static char player1Name[128] = "";
2448 static char player2Name[128] = "";
2449 static char cont_seq[] = "\n\\   ";
2450 static int player1Rating = -1;
2451 static int player2Rating = -1;
2452 /*----------------------------*/
2453
2454 ColorClass curColor = ColorNormal;
2455 int suppressKibitz = 0;
2456
2457 // [HGM] seekgraph
2458 Boolean soughtPending = FALSE;
2459 Boolean seekGraphUp;
2460 #define MAX_SEEK_ADS 200
2461 #define SQUARE 0x80
2462 char *seekAdList[MAX_SEEK_ADS];
2463 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2464 float tcList[MAX_SEEK_ADS];
2465 char colorList[MAX_SEEK_ADS];
2466 int nrOfSeekAds = 0;
2467 int minRating = 1010, maxRating = 2800;
2468 int hMargin = 10, vMargin = 20, h, w;
2469 extern int squareSize, lineGap;
2470
2471 void
2472 PlotSeekAd(int i)
2473 {
2474         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2475         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2476         if(r < minRating+100 && r >=0 ) r = minRating+100;
2477         if(r > maxRating) r = maxRating;
2478         if(tc < 1.) tc = 1.;
2479         if(tc > 95.) tc = 95.;
2480         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2481         y = ((double)r - minRating)/(maxRating - minRating)
2482             * (h-vMargin-squareSize/8-1) + vMargin;
2483         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2484         if(strstr(seekAdList[i], " u ")) color = 1;
2485         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2486            !strstr(seekAdList[i], "bullet") &&
2487            !strstr(seekAdList[i], "blitz") &&
2488            !strstr(seekAdList[i], "standard") ) color = 2;
2489         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2490         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2491 }
2492
2493 void
2494 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2495 {
2496         char buf[MSG_SIZ], *ext = "";
2497         VariantClass v = StringToVariant(type);
2498         if(strstr(type, "wild")) {
2499             ext = type + 4; // append wild number
2500             if(v == VariantFischeRandom) type = "chess960"; else
2501             if(v == VariantLoadable) type = "setup"; else
2502             type = VariantName(v);
2503         }
2504         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2505         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2506             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2507             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2508             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2509             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2510             seekNrList[nrOfSeekAds] = nr;
2511             zList[nrOfSeekAds] = 0;
2512             seekAdList[nrOfSeekAds++] = StrSave(buf);
2513             if(plot) PlotSeekAd(nrOfSeekAds-1);
2514         }
2515 }
2516
2517 void
2518 EraseSeekDot(int i)
2519 {
2520     int x = xList[i], y = yList[i], d=squareSize/4, k;
2521     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2522     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2523     // now replot every dot that overlapped
2524     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2525         int xx = xList[k], yy = yList[k];
2526         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2527             DrawSeekDot(xx, yy, colorList[k]);
2528     }
2529 }
2530
2531 void
2532 RemoveSeekAd(int nr)
2533 {
2534         int i;
2535         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2536             EraseSeekDot(i);
2537             if(seekAdList[i]) free(seekAdList[i]);
2538             seekAdList[i] = seekAdList[--nrOfSeekAds];
2539             seekNrList[i] = seekNrList[nrOfSeekAds];
2540             ratingList[i] = ratingList[nrOfSeekAds];
2541             colorList[i]  = colorList[nrOfSeekAds];
2542             tcList[i] = tcList[nrOfSeekAds];
2543             xList[i]  = xList[nrOfSeekAds];
2544             yList[i]  = yList[nrOfSeekAds];
2545             zList[i]  = zList[nrOfSeekAds];
2546             seekAdList[nrOfSeekAds] = NULL;
2547             break;
2548         }
2549 }
2550
2551 Boolean
2552 MatchSoughtLine(char *line)
2553 {
2554     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2555     int nr, base, inc, u=0; char dummy;
2556
2557     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2558        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2559        (u=1) &&
2560        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2561         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2562         // match: compact and save the line
2563         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2564         return TRUE;
2565     }
2566     return FALSE;
2567 }
2568
2569 int
2570 DrawSeekGraph()
2571 {
2572     int i;
2573     if(!seekGraphUp) return FALSE;
2574     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2575     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2576
2577     DrawSeekBackground(0, 0, w, h);
2578     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2579     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2580     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2581         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2582         yy = h-1-yy;
2583         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2584         if(i%500 == 0) {
2585             char buf[MSG_SIZ];
2586             snprintf(buf, MSG_SIZ, "%d", i);
2587             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2588         }
2589     }
2590     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2591     for(i=1; i<100; i+=(i<10?1:5)) {
2592         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2593         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2594         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2595             char buf[MSG_SIZ];
2596             snprintf(buf, MSG_SIZ, "%d", i);
2597             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2598         }
2599     }
2600     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2601     return TRUE;
2602 }
2603
2604 int SeekGraphClick(ClickType click, int x, int y, int moving)
2605 {
2606     static int lastDown = 0, displayed = 0, lastSecond;
2607     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2608         if(click == Release || moving) return FALSE;
2609         nrOfSeekAds = 0;
2610         soughtPending = TRUE;
2611         SendToICS(ics_prefix);
2612         SendToICS("sought\n"); // should this be "sought all"?
2613     } else { // issue challenge based on clicked ad
2614         int dist = 10000; int i, closest = 0, second = 0;
2615         for(i=0; i<nrOfSeekAds; i++) {
2616             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2617             if(d < dist) { dist = d; closest = i; }
2618             second += (d - zList[i] < 120); // count in-range ads
2619             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2620         }
2621         if(dist < 120) {
2622             char buf[MSG_SIZ];
2623             second = (second > 1);
2624             if(displayed != closest || second != lastSecond) {
2625                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2626                 lastSecond = second; displayed = closest;
2627             }
2628             if(click == Press) {
2629                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2630                 lastDown = closest;
2631                 return TRUE;
2632             } // on press 'hit', only show info
2633             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2634             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2635             SendToICS(ics_prefix);
2636             SendToICS(buf);
2637             return TRUE; // let incoming board of started game pop down the graph
2638         } else if(click == Release) { // release 'miss' is ignored
2639             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2640             if(moving == 2) { // right up-click
2641                 nrOfSeekAds = 0; // refresh graph
2642                 soughtPending = TRUE;
2643                 SendToICS(ics_prefix);
2644                 SendToICS("sought\n"); // should this be "sought all"?
2645             }
2646             return TRUE;
2647         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2648         // press miss or release hit 'pop down' seek graph
2649         seekGraphUp = FALSE;
2650         DrawPosition(TRUE, NULL);
2651     }
2652     return TRUE;
2653 }
2654
2655 void
2656 read_from_ics(isr, closure, data, count, error)
2657      InputSourceRef isr;
2658      VOIDSTAR closure;
2659      char *data;
2660      int count;
2661      int error;
2662 {
2663 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2664 #define STARTED_NONE 0
2665 #define STARTED_MOVES 1
2666 #define STARTED_BOARD 2
2667 #define STARTED_OBSERVE 3
2668 #define STARTED_HOLDINGS 4
2669 #define STARTED_CHATTER 5
2670 #define STARTED_COMMENT 6
2671 #define STARTED_MOVES_NOHIDE 7
2672
2673     static int started = STARTED_NONE;
2674     static char parse[20000];
2675     static int parse_pos = 0;
2676     static char buf[BUF_SIZE + 1];
2677     static int firstTime = TRUE, intfSet = FALSE;
2678     static ColorClass prevColor = ColorNormal;
2679     static int savingComment = FALSE;
2680     static int cmatch = 0; // continuation sequence match
2681     char *bp;
2682     char str[MSG_SIZ];
2683     int i, oldi;
2684     int buf_len;
2685     int next_out;
2686     int tkind;
2687     int backup;    /* [DM] For zippy color lines */
2688     char *p;
2689     char talker[MSG_SIZ]; // [HGM] chat
2690     int channel;
2691
2692     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2693
2694     if (appData.debugMode) {
2695       if (!error) {
2696         fprintf(debugFP, "<ICS: ");
2697         show_bytes(debugFP, data, count);
2698         fprintf(debugFP, "\n");
2699       }
2700     }
2701
2702     if (appData.debugMode) { int f = forwardMostMove;
2703         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2704                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2705                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2706     }
2707     if (count > 0) {
2708         /* If last read ended with a partial line that we couldn't parse,
2709            prepend it to the new read and try again. */
2710         if (leftover_len > 0) {
2711             for (i=0; i<leftover_len; i++)
2712               buf[i] = buf[leftover_start + i];
2713         }
2714
2715     /* copy new characters into the buffer */
2716     bp = buf + leftover_len;
2717     buf_len=leftover_len;
2718     for (i=0; i<count; i++)
2719     {
2720         // ignore these
2721         if (data[i] == '\r')
2722             continue;
2723
2724         // join lines split by ICS?
2725         if (!appData.noJoin)
2726         {
2727             /*
2728                 Joining just consists of finding matches against the
2729                 continuation sequence, and discarding that sequence
2730                 if found instead of copying it.  So, until a match
2731                 fails, there's nothing to do since it might be the
2732                 complete sequence, and thus, something we don't want
2733                 copied.
2734             */
2735             if (data[i] == cont_seq[cmatch])
2736             {
2737                 cmatch++;
2738                 if (cmatch == strlen(cont_seq))
2739                 {
2740                     cmatch = 0; // complete match.  just reset the counter
2741
2742                     /*
2743                         it's possible for the ICS to not include the space
2744                         at the end of the last word, making our [correct]
2745                         join operation fuse two separate words.  the server
2746                         does this when the space occurs at the width setting.
2747                     */
2748                     if (!buf_len || buf[buf_len-1] != ' ')
2749                     {
2750                         *bp++ = ' ';
2751                         buf_len++;
2752                     }
2753                 }
2754                 continue;
2755             }
2756             else if (cmatch)
2757             {
2758                 /*
2759                     match failed, so we have to copy what matched before
2760                     falling through and copying this character.  In reality,
2761                     this will only ever be just the newline character, but
2762                     it doesn't hurt to be precise.
2763                 */
2764                 strncpy(bp, cont_seq, cmatch);
2765                 bp += cmatch;
2766                 buf_len += cmatch;
2767                 cmatch = 0;
2768             }
2769         }
2770
2771         // copy this char
2772         *bp++ = data[i];
2773         buf_len++;
2774     }
2775
2776         buf[buf_len] = NULLCHAR;
2777 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2778         next_out = 0;
2779         leftover_start = 0;
2780
2781         i = 0;
2782         while (i < buf_len) {
2783             /* Deal with part of the TELNET option negotiation
2784                protocol.  We refuse to do anything beyond the
2785                defaults, except that we allow the WILL ECHO option,
2786                which ICS uses to turn off password echoing when we are
2787                directly connected to it.  We reject this option
2788                if localLineEditing mode is on (always on in xboard)
2789                and we are talking to port 23, which might be a real
2790                telnet server that will try to keep WILL ECHO on permanently.
2791              */
2792             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2793                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2794                 unsigned char option;
2795                 oldi = i;
2796                 switch ((unsigned char) buf[++i]) {
2797                   case TN_WILL:
2798                     if (appData.debugMode)
2799                       fprintf(debugFP, "\n<WILL ");
2800                     switch (option = (unsigned char) buf[++i]) {
2801                       case TN_ECHO:
2802                         if (appData.debugMode)
2803                           fprintf(debugFP, "ECHO ");
2804                         /* Reply only if this is a change, according
2805                            to the protocol rules. */
2806                         if (remoteEchoOption) break;
2807                         if (appData.localLineEditing &&
2808                             atoi(appData.icsPort) == TN_PORT) {
2809                             TelnetRequest(TN_DONT, TN_ECHO);
2810                         } else {
2811                             EchoOff();
2812                             TelnetRequest(TN_DO, TN_ECHO);
2813                             remoteEchoOption = TRUE;
2814                         }
2815                         break;
2816                       default:
2817                         if (appData.debugMode)
2818                           fprintf(debugFP, "%d ", option);
2819                         /* Whatever this is, we don't want it. */
2820                         TelnetRequest(TN_DONT, option);
2821                         break;
2822                     }
2823                     break;
2824                   case TN_WONT:
2825                     if (appData.debugMode)
2826                       fprintf(debugFP, "\n<WONT ");
2827                     switch (option = (unsigned char) buf[++i]) {
2828                       case TN_ECHO:
2829                         if (appData.debugMode)
2830                           fprintf(debugFP, "ECHO ");
2831                         /* Reply only if this is a change, according
2832                            to the protocol rules. */
2833                         if (!remoteEchoOption) break;
2834                         EchoOn();
2835                         TelnetRequest(TN_DONT, TN_ECHO);
2836                         remoteEchoOption = FALSE;
2837                         break;
2838                       default:
2839                         if (appData.debugMode)
2840                           fprintf(debugFP, "%d ", (unsigned char) option);
2841                         /* Whatever this is, it must already be turned
2842                            off, because we never agree to turn on
2843                            anything non-default, so according to the
2844                            protocol rules, we don't reply. */
2845                         break;
2846                     }
2847                     break;
2848                   case TN_DO:
2849                     if (appData.debugMode)
2850                       fprintf(debugFP, "\n<DO ");
2851                     switch (option = (unsigned char) buf[++i]) {
2852                       default:
2853                         /* Whatever this is, we refuse to do it. */
2854                         if (appData.debugMode)
2855                           fprintf(debugFP, "%d ", option);
2856                         TelnetRequest(TN_WONT, option);
2857                         break;
2858                     }
2859                     break;
2860                   case TN_DONT:
2861                     if (appData.debugMode)
2862                       fprintf(debugFP, "\n<DONT ");
2863                     switch (option = (unsigned char) buf[++i]) {
2864                       default:
2865                         if (appData.debugMode)
2866                           fprintf(debugFP, "%d ", option);
2867                         /* Whatever this is, we are already not doing
2868                            it, because we never agree to do anything
2869                            non-default, so according to the protocol
2870                            rules, we don't reply. */
2871                         break;
2872                     }
2873                     break;
2874                   case TN_IAC:
2875                     if (appData.debugMode)
2876                       fprintf(debugFP, "\n<IAC ");
2877                     /* Doubled IAC; pass it through */
2878                     i--;
2879                     break;
2880                   default:
2881                     if (appData.debugMode)
2882                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2883                     /* Drop all other telnet commands on the floor */
2884                     break;
2885                 }
2886                 if (oldi > next_out)
2887                   SendToPlayer(&buf[next_out], oldi - next_out);
2888                 if (++i > next_out)
2889                   next_out = i;
2890                 continue;
2891             }
2892
2893             /* OK, this at least will *usually* work */
2894             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2895                 loggedOn = TRUE;
2896             }
2897
2898             if (loggedOn && !intfSet) {
2899                 if (ics_type == ICS_ICC) {
2900                   snprintf(str, MSG_SIZ,
2901                           "/set-quietly interface %s\n/set-quietly style 12\n",
2902                           programVersion);
2903                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2904                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2905                 } else if (ics_type == ICS_CHESSNET) {
2906                   snprintf(str, MSG_SIZ, "/style 12\n");
2907                 } else {
2908                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2909                   strcat(str, programVersion);
2910                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2911                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2912                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2913 #ifdef WIN32
2914                   strcat(str, "$iset nohighlight 1\n");
2915 #endif
2916                   strcat(str, "$iset lock 1\n$style 12\n");
2917                 }
2918                 SendToICS(str);
2919                 NotifyFrontendLogin();
2920                 intfSet = TRUE;
2921             }
2922
2923             if (started == STARTED_COMMENT) {
2924                 /* Accumulate characters in comment */
2925                 parse[parse_pos++] = buf[i];
2926                 if (buf[i] == '\n') {
2927                     parse[parse_pos] = NULLCHAR;
2928                     if(chattingPartner>=0) {
2929                         char mess[MSG_SIZ];
2930                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2931                         OutputChatMessage(chattingPartner, mess);
2932                         chattingPartner = -1;
2933                         next_out = i+1; // [HGM] suppress printing in ICS window
2934                     } else
2935                     if(!suppressKibitz) // [HGM] kibitz
2936                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2937                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2938                         int nrDigit = 0, nrAlph = 0, j;
2939                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2940                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2941                         parse[parse_pos] = NULLCHAR;
2942                         // try to be smart: if it does not look like search info, it should go to
2943                         // ICS interaction window after all, not to engine-output window.
2944                         for(j=0; j<parse_pos; j++) { // count letters and digits
2945                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2946                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2947                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2948                         }
2949                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2950                             int depth=0; float score;
2951                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2952                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2953                                 pvInfoList[forwardMostMove-1].depth = depth;
2954                                 pvInfoList[forwardMostMove-1].score = 100*score;
2955                             }
2956                             OutputKibitz(suppressKibitz, parse);
2957                         } else {
2958                             char tmp[MSG_SIZ];
2959                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2960                             SendToPlayer(tmp, strlen(tmp));
2961                         }
2962                         next_out = i+1; // [HGM] suppress printing in ICS window
2963                     }
2964                     started = STARTED_NONE;
2965                 } else {
2966                     /* Don't match patterns against characters in comment */
2967                     i++;
2968                     continue;
2969                 }
2970             }
2971             if (started == STARTED_CHATTER) {
2972                 if (buf[i] != '\n') {
2973                     /* Don't match patterns against characters in chatter */
2974                     i++;
2975                     continue;
2976                 }
2977                 started = STARTED_NONE;
2978                 if(suppressKibitz) next_out = i+1;
2979             }
2980
2981             /* Kludge to deal with rcmd protocol */
2982             if (firstTime && looking_at(buf, &i, "\001*")) {
2983                 DisplayFatalError(&buf[1], 0, 1);
2984                 continue;
2985             } else {
2986                 firstTime = FALSE;
2987             }
2988
2989             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2990                 ics_type = ICS_ICC;
2991                 ics_prefix = "/";
2992                 if (appData.debugMode)
2993                   fprintf(debugFP, "ics_type %d\n", ics_type);
2994                 continue;
2995             }
2996             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2997                 ics_type = ICS_FICS;
2998                 ics_prefix = "$";
2999                 if (appData.debugMode)
3000                   fprintf(debugFP, "ics_type %d\n", ics_type);
3001                 continue;
3002             }
3003             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3004                 ics_type = ICS_CHESSNET;
3005                 ics_prefix = "/";
3006                 if (appData.debugMode)
3007                   fprintf(debugFP, "ics_type %d\n", ics_type);
3008                 continue;
3009             }
3010
3011             if (!loggedOn &&
3012                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3013                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3014                  looking_at(buf, &i, "will be \"*\""))) {
3015               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3016               continue;
3017             }
3018
3019             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3020               char buf[MSG_SIZ];
3021               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3022               DisplayIcsInteractionTitle(buf);
3023               have_set_title = TRUE;
3024             }
3025
3026             /* skip finger notes */
3027             if (started == STARTED_NONE &&
3028                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3029                  (buf[i] == '1' && buf[i+1] == '0')) &&
3030                 buf[i+2] == ':' && buf[i+3] == ' ') {
3031               started = STARTED_CHATTER;
3032               i += 3;
3033               continue;
3034             }
3035
3036             oldi = i;
3037             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3038             if(appData.seekGraph) {
3039                 if(soughtPending && MatchSoughtLine(buf+i)) {
3040                     i = strstr(buf+i, "rated") - buf;
3041                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3042                     next_out = leftover_start = i;
3043                     started = STARTED_CHATTER;
3044                     suppressKibitz = TRUE;
3045                     continue;
3046                 }
3047                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3048                         && looking_at(buf, &i, "* ads displayed")) {
3049                     soughtPending = FALSE;
3050                     seekGraphUp = TRUE;
3051                     DrawSeekGraph();
3052                     continue;
3053                 }
3054                 if(appData.autoRefresh) {
3055                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3056                         int s = (ics_type == ICS_ICC); // ICC format differs
3057                         if(seekGraphUp)
3058                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3059                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3060                         looking_at(buf, &i, "*% "); // eat prompt
3061                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3062                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3063                         next_out = i; // suppress
3064                         continue;
3065                     }
3066                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3067                         char *p = star_match[0];
3068                         while(*p) {
3069                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3070                             while(*p && *p++ != ' '); // next
3071                         }
3072                         looking_at(buf, &i, "*% "); // eat prompt
3073                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3074                         next_out = i;
3075                         continue;
3076                     }
3077                 }
3078             }
3079
3080             /* skip formula vars */
3081             if (started == STARTED_NONE &&
3082                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3083               started = STARTED_CHATTER;
3084               i += 3;
3085               continue;
3086             }
3087
3088             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3089             if (appData.autoKibitz && started == STARTED_NONE &&
3090                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3091                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3092                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3093                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3094                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3095                         suppressKibitz = TRUE;
3096                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3097                         next_out = i;
3098                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3099                                 && (gameMode == IcsPlayingWhite)) ||
3100                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3101                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3102                             started = STARTED_CHATTER; // own kibitz we simply discard
3103                         else {
3104                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3105                             parse_pos = 0; parse[0] = NULLCHAR;
3106                             savingComment = TRUE;
3107                             suppressKibitz = gameMode != IcsObserving ? 2 :
3108                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3109                         }
3110                         continue;
3111                 } else
3112                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3113                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3114                          && atoi(star_match[0])) {
3115                     // suppress the acknowledgements of our own autoKibitz
3116                     char *p;
3117                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3118                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3119                     SendToPlayer(star_match[0], strlen(star_match[0]));
3120                     if(looking_at(buf, &i, "*% ")) // eat prompt
3121                         suppressKibitz = FALSE;
3122                     next_out = i;
3123                     continue;
3124                 }
3125             } // [HGM] kibitz: end of patch
3126
3127             // [HGM] chat: intercept tells by users for which we have an open chat window
3128             channel = -1;
3129             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3130                                            looking_at(buf, &i, "* whispers:") ||
3131                                            looking_at(buf, &i, "* kibitzes:") ||
3132                                            looking_at(buf, &i, "* shouts:") ||
3133                                            looking_at(buf, &i, "* c-shouts:") ||
3134                                            looking_at(buf, &i, "--> * ") ||
3135                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3136                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3137                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3138                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3139                 int p;
3140                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3141                 chattingPartner = -1;
3142
3143                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3144                 for(p=0; p<MAX_CHAT; p++) {
3145                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3146                     talker[0] = '['; strcat(talker, "] ");
3147                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3148                     chattingPartner = p; break;
3149                     }
3150                 } else
3151                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3152                 for(p=0; p<MAX_CHAT; p++) {
3153                     if(!strcmp("kibitzes", chatPartner[p])) {
3154                         talker[0] = '['; strcat(talker, "] ");
3155                         chattingPartner = p; break;
3156                     }
3157                 } else
3158                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3159                 for(p=0; p<MAX_CHAT; p++) {
3160                     if(!strcmp("whispers", chatPartner[p])) {
3161                         talker[0] = '['; strcat(talker, "] ");
3162                         chattingPartner = p; break;
3163                     }
3164                 } else
3165                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3166                   if(buf[i-8] == '-' && buf[i-3] == 't')
3167                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3168                     if(!strcmp("c-shouts", chatPartner[p])) {
3169                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3170                         chattingPartner = p; break;
3171                     }
3172                   }
3173                   if(chattingPartner < 0)
3174                   for(p=0; p<MAX_CHAT; p++) {
3175                     if(!strcmp("shouts", chatPartner[p])) {
3176                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3177                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3178                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3179                         chattingPartner = p; break;
3180                     }
3181                   }
3182                 }
3183                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3184                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3185                     talker[0] = 0; Colorize(ColorTell, FALSE);
3186                     chattingPartner = p; break;
3187                 }
3188                 if(chattingPartner<0) i = oldi; else {
3189                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3190                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3191                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3192                     started = STARTED_COMMENT;
3193                     parse_pos = 0; parse[0] = NULLCHAR;
3194                     savingComment = 3 + chattingPartner; // counts as TRUE
3195                     suppressKibitz = TRUE;
3196                     continue;
3197                 }
3198             } // [HGM] chat: end of patch
3199
3200           backup = i;
3201             if (appData.zippyTalk || appData.zippyPlay) {
3202                 /* [DM] Backup address for color zippy lines */
3203 #if ZIPPY
3204                if (loggedOn == TRUE)
3205                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3206                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3207 #endif
3208             } // [DM] 'else { ' deleted
3209                 if (
3210                     /* Regular tells and says */
3211                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3212                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3213                     looking_at(buf, &i, "* says: ") ||
3214                     /* Don't color "message" or "messages" output */
3215                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3216                     looking_at(buf, &i, "*. * at *:*: ") ||
3217                     looking_at(buf, &i, "--* (*:*): ") ||
3218                     /* Message notifications (same color as tells) */
3219                     looking_at(buf, &i, "* has left a message ") ||
3220                     looking_at(buf, &i, "* just sent you a message:\n") ||
3221                     /* Whispers and kibitzes */
3222                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3223                     looking_at(buf, &i, "* kibitzes: ") ||
3224                     /* Channel tells */
3225                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3226
3227                   if (tkind == 1 && strchr(star_match[0], ':')) {
3228                       /* Avoid "tells you:" spoofs in channels */
3229                      tkind = 3;
3230                   }
3231                   if (star_match[0][0] == NULLCHAR ||
3232                       strchr(star_match[0], ' ') ||
3233                       (tkind == 3 && strchr(star_match[1], ' '))) {
3234                     /* Reject bogus matches */
3235                     i = oldi;
3236                   } else {
3237                     if (appData.colorize) {
3238                       if (oldi > next_out) {
3239                         SendToPlayer(&buf[next_out], oldi - next_out);
3240                         next_out = oldi;
3241                       }
3242                       switch (tkind) {
3243                       case 1:
3244                         Colorize(ColorTell, FALSE);
3245                         curColor = ColorTell;
3246                         break;
3247                       case 2:
3248                         Colorize(ColorKibitz, FALSE);
3249                         curColor = ColorKibitz;
3250                         break;
3251                       case 3:
3252                         p = strrchr(star_match[1], '(');
3253                         if (p == NULL) {
3254                           p = star_match[1];
3255                         } else {
3256                           p++;
3257                         }
3258                         if (atoi(p) == 1) {
3259                           Colorize(ColorChannel1, FALSE);
3260                           curColor = ColorChannel1;
3261                         } else {
3262                           Colorize(ColorChannel, FALSE);
3263                           curColor = ColorChannel;
3264                         }
3265                         break;
3266                       case 5:
3267                         curColor = ColorNormal;
3268                         break;
3269                       }
3270                     }
3271                     if (started == STARTED_NONE && appData.autoComment &&
3272                         (gameMode == IcsObserving ||
3273                          gameMode == IcsPlayingWhite ||
3274                          gameMode == IcsPlayingBlack)) {
3275                       parse_pos = i - oldi;
3276                       memcpy(parse, &buf[oldi], parse_pos);
3277                       parse[parse_pos] = NULLCHAR;
3278                       started = STARTED_COMMENT;
3279                       savingComment = TRUE;
3280                     } else {
3281                       started = STARTED_CHATTER;
3282                       savingComment = FALSE;
3283                     }
3284                     loggedOn = TRUE;
3285                     continue;
3286                   }
3287                 }
3288
3289                 if (looking_at(buf, &i, "* s-shouts: ") ||
3290                     looking_at(buf, &i, "* c-shouts: ")) {
3291                     if (appData.colorize) {
3292                         if (oldi > next_out) {
3293                             SendToPlayer(&buf[next_out], oldi - next_out);
3294                             next_out = oldi;
3295                         }
3296                         Colorize(ColorSShout, FALSE);
3297                         curColor = ColorSShout;
3298                     }
3299                     loggedOn = TRUE;
3300                     started = STARTED_CHATTER;
3301                     continue;
3302                 }
3303
3304                 if (looking_at(buf, &i, "--->")) {
3305                     loggedOn = TRUE;
3306                     continue;
3307                 }
3308
3309                 if (looking_at(buf, &i, "* shouts: ") ||
3310                     looking_at(buf, &i, "--> ")) {
3311                     if (appData.colorize) {
3312                         if (oldi > next_out) {
3313                             SendToPlayer(&buf[next_out], oldi - next_out);
3314                             next_out = oldi;
3315                         }
3316                         Colorize(ColorShout, FALSE);
3317                         curColor = ColorShout;
3318                     }
3319                     loggedOn = TRUE;
3320                     started = STARTED_CHATTER;
3321                     continue;
3322                 }
3323
3324                 if (looking_at( buf, &i, "Challenge:")) {
3325                     if (appData.colorize) {
3326                         if (oldi > next_out) {
3327                             SendToPlayer(&buf[next_out], oldi - next_out);
3328                             next_out = oldi;
3329                         }
3330                         Colorize(ColorChallenge, FALSE);
3331                         curColor = ColorChallenge;
3332                     }
3333                     loggedOn = TRUE;
3334                     continue;
3335                 }
3336
3337                 if (looking_at(buf, &i, "* offers you") ||
3338                     looking_at(buf, &i, "* offers to be") ||
3339                     looking_at(buf, &i, "* would like to") ||
3340                     looking_at(buf, &i, "* requests to") ||
3341                     looking_at(buf, &i, "Your opponent offers") ||
3342                     looking_at(buf, &i, "Your opponent requests")) {
3343
3344                     if (appData.colorize) {
3345                         if (oldi > next_out) {
3346                             SendToPlayer(&buf[next_out], oldi - next_out);
3347                             next_out = oldi;
3348                         }
3349                         Colorize(ColorRequest, FALSE);
3350                         curColor = ColorRequest;
3351                     }
3352                     continue;
3353                 }
3354
3355                 if (looking_at(buf, &i, "* (*) seeking")) {
3356                     if (appData.colorize) {
3357                         if (oldi > next_out) {
3358                             SendToPlayer(&buf[next_out], oldi - next_out);
3359                             next_out = oldi;
3360                         }
3361                         Colorize(ColorSeek, FALSE);
3362                         curColor = ColorSeek;
3363                     }
3364                     continue;
3365             }
3366
3367           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3368
3369             if (looking_at(buf, &i, "\\   ")) {
3370                 if (prevColor != ColorNormal) {
3371                     if (oldi > next_out) {
3372                         SendToPlayer(&buf[next_out], oldi - next_out);
3373                         next_out = oldi;
3374                     }
3375                     Colorize(prevColor, TRUE);
3376                     curColor = prevColor;
3377                 }
3378                 if (savingComment) {
3379                     parse_pos = i - oldi;
3380                     memcpy(parse, &buf[oldi], parse_pos);
3381                     parse[parse_pos] = NULLCHAR;
3382                     started = STARTED_COMMENT;
3383                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3384                         chattingPartner = savingComment - 3; // kludge to remember the box
3385                 } else {
3386                     started = STARTED_CHATTER;
3387                 }
3388                 continue;
3389             }
3390
3391             if (looking_at(buf, &i, "Black Strength :") ||
3392                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3393                 looking_at(buf, &i, "<10>") ||
3394                 looking_at(buf, &i, "#@#")) {
3395                 /* Wrong board style */
3396                 loggedOn = TRUE;
3397                 SendToICS(ics_prefix);
3398                 SendToICS("set style 12\n");
3399                 SendToICS(ics_prefix);
3400                 SendToICS("refresh\n");
3401                 continue;
3402             }
3403
3404             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3405                 ICSInitScript();
3406                 have_sent_ICS_logon = 1;
3407                 continue;
3408             }
3409
3410             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3411                 (looking_at(buf, &i, "\n<12> ") ||
3412                  looking_at(buf, &i, "<12> "))) {
3413                 loggedOn = TRUE;
3414                 if (oldi > next_out) {
3415                     SendToPlayer(&buf[next_out], oldi - next_out);
3416                 }
3417                 next_out = i;
3418                 started = STARTED_BOARD;
3419                 parse_pos = 0;
3420                 continue;
3421             }
3422
3423             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3424                 looking_at(buf, &i, "<b1> ")) {
3425                 if (oldi > next_out) {
3426                     SendToPlayer(&buf[next_out], oldi - next_out);
3427                 }
3428                 next_out = i;
3429                 started = STARTED_HOLDINGS;
3430                 parse_pos = 0;
3431                 continue;
3432             }
3433
3434             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3435                 loggedOn = TRUE;
3436                 /* Header for a move list -- first line */
3437
3438                 switch (ics_getting_history) {
3439                   case H_FALSE:
3440                     switch (gameMode) {
3441                       case IcsIdle:
3442                       case BeginningOfGame:
3443                         /* User typed "moves" or "oldmoves" while we
3444                            were idle.  Pretend we asked for these
3445                            moves and soak them up so user can step
3446                            through them and/or save them.
3447                            */
3448                         Reset(FALSE, TRUE);
3449                         gameMode = IcsObserving;
3450                         ModeHighlight();
3451                         ics_gamenum = -1;
3452                         ics_getting_history = H_GOT_UNREQ_HEADER;
3453                         break;
3454                       case EditGame: /*?*/
3455                       case EditPosition: /*?*/
3456                         /* Should above feature work in these modes too? */
3457                         /* For now it doesn't */
3458                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3459                         break;
3460                       default:
3461                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3462                         break;
3463                     }
3464                     break;
3465                   case H_REQUESTED:
3466                     /* Is this the right one? */
3467                     if (gameInfo.white && gameInfo.black &&
3468                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3469                         strcmp(gameInfo.black, star_match[2]) == 0) {
3470                         /* All is well */
3471                         ics_getting_history = H_GOT_REQ_HEADER;
3472                     }
3473                     break;
3474                   case H_GOT_REQ_HEADER:
3475                   case H_GOT_UNREQ_HEADER:
3476                   case H_GOT_UNWANTED_HEADER:
3477                   case H_GETTING_MOVES:
3478                     /* Should not happen */
3479                     DisplayError(_("Error gathering move list: two headers"), 0);
3480                     ics_getting_history = H_FALSE;
3481                     break;
3482                 }
3483
3484                 /* Save player ratings into gameInfo if needed */
3485                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3486                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3487                     (gameInfo.whiteRating == -1 ||
3488                      gameInfo.blackRating == -1)) {
3489
3490                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3491                     gameInfo.blackRating = string_to_rating(star_match[3]);
3492                     if (appData.debugMode)
3493                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3494                               gameInfo.whiteRating, gameInfo.blackRating);
3495                 }
3496                 continue;
3497             }
3498
3499             if (looking_at(buf, &i,
3500               "* * match, initial time: * minute*, increment: * second")) {
3501                 /* Header for a move list -- second line */
3502                 /* Initial board will follow if this is a wild game */
3503                 if (gameInfo.event != NULL) free(gameInfo.event);
3504                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3505                 gameInfo.event = StrSave(str);
3506                 /* [HGM] we switched variant. Translate boards if needed. */
3507                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3508                 continue;
3509             }
3510
3511             if (looking_at(buf, &i, "Move  ")) {
3512                 /* Beginning of a move list */
3513                 switch (ics_getting_history) {
3514                   case H_FALSE:
3515                     /* Normally should not happen */
3516                     /* Maybe user hit reset while we were parsing */
3517                     break;
3518                   case H_REQUESTED:
3519                     /* Happens if we are ignoring a move list that is not
3520                      * the one we just requested.  Common if the user
3521                      * tries to observe two games without turning off
3522                      * getMoveList */
3523                     break;
3524                   case H_GETTING_MOVES:
3525                     /* Should not happen */
3526                     DisplayError(_("Error gathering move list: nested"), 0);
3527                     ics_getting_history = H_FALSE;
3528                     break;
3529                   case H_GOT_REQ_HEADER:
3530                     ics_getting_history = H_GETTING_MOVES;
3531                     started = STARTED_MOVES;
3532                     parse_pos = 0;
3533                     if (oldi > next_out) {
3534                         SendToPlayer(&buf[next_out], oldi - next_out);
3535                     }
3536                     break;
3537                   case H_GOT_UNREQ_HEADER:
3538                     ics_getting_history = H_GETTING_MOVES;
3539                     started = STARTED_MOVES_NOHIDE;
3540                     parse_pos = 0;
3541                     break;
3542                   case H_GOT_UNWANTED_HEADER:
3543                     ics_getting_history = H_FALSE;
3544                     break;
3545                 }
3546                 continue;
3547             }
3548
3549             if (looking_at(buf, &i, "% ") ||
3550                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3551                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3552                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3553                     soughtPending = FALSE;
3554                     seekGraphUp = TRUE;
3555                     DrawSeekGraph();
3556                 }
3557                 if(suppressKibitz) next_out = i;
3558                 savingComment = FALSE;
3559                 suppressKibitz = 0;
3560                 switch (started) {
3561                   case STARTED_MOVES:
3562                   case STARTED_MOVES_NOHIDE:
3563                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3564                     parse[parse_pos + i - oldi] = NULLCHAR;
3565                     ParseGameHistory(parse);
3566 #if ZIPPY
3567                     if (appData.zippyPlay && first.initDone) {
3568                         FeedMovesToProgram(&first, forwardMostMove);
3569                         if (gameMode == IcsPlayingWhite) {
3570                             if (WhiteOnMove(forwardMostMove)) {
3571                                 if (first.sendTime) {
3572                                   if (first.useColors) {
3573                                     SendToProgram("black\n", &first);
3574                                   }
3575                                   SendTimeRemaining(&first, TRUE);
3576                                 }
3577                                 if (first.useColors) {
3578                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3579                                 }
3580                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3581                                 first.maybeThinking = TRUE;
3582                             } else {
3583                                 if (first.usePlayother) {
3584                                   if (first.sendTime) {
3585                                     SendTimeRemaining(&first, TRUE);
3586                                   }
3587                                   SendToProgram("playother\n", &first);
3588                                   firstMove = FALSE;
3589                                 } else {
3590                                   firstMove = TRUE;
3591                                 }
3592                             }
3593                         } else if (gameMode == IcsPlayingBlack) {
3594                             if (!WhiteOnMove(forwardMostMove)) {
3595                                 if (first.sendTime) {
3596                                   if (first.useColors) {
3597                                     SendToProgram("white\n", &first);
3598                                   }
3599                                   SendTimeRemaining(&first, FALSE);
3600                                 }
3601                                 if (first.useColors) {
3602                                   SendToProgram("black\n", &first);
3603                                 }
3604                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3605                                 first.maybeThinking = TRUE;
3606                             } else {
3607                                 if (first.usePlayother) {
3608                                   if (first.sendTime) {
3609                                     SendTimeRemaining(&first, FALSE);
3610                                   }
3611                                   SendToProgram("playother\n", &first);
3612                                   firstMove = FALSE;
3613                                 } else {
3614                                   firstMove = TRUE;
3615                                 }
3616                             }
3617                         }
3618                     }
3619 #endif
3620                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3621                         /* Moves came from oldmoves or moves command
3622                            while we weren't doing anything else.
3623                            */
3624                         currentMove = forwardMostMove;
3625                         ClearHighlights();/*!!could figure this out*/
3626                         flipView = appData.flipView;
3627                         DrawPosition(TRUE, boards[currentMove]);
3628                         DisplayBothClocks();
3629                         snprintf(str, MSG_SIZ, "%s vs. %s",
3630                                 gameInfo.white, gameInfo.black);
3631                         DisplayTitle(str);
3632                         gameMode = IcsIdle;
3633                     } else {
3634                         /* Moves were history of an active game */
3635                         if (gameInfo.resultDetails != NULL) {
3636                             free(gameInfo.resultDetails);
3637                             gameInfo.resultDetails = NULL;
3638                         }
3639                     }
3640                     HistorySet(parseList, backwardMostMove,
3641                                forwardMostMove, currentMove-1);
3642                     DisplayMove(currentMove - 1);
3643                     if (started == STARTED_MOVES) next_out = i;
3644                     started = STARTED_NONE;
3645                     ics_getting_history = H_FALSE;
3646                     break;
3647
3648                   case STARTED_OBSERVE:
3649                     started = STARTED_NONE;
3650                     SendToICS(ics_prefix);
3651                     SendToICS("refresh\n");
3652                     break;
3653
3654                   default:
3655                     break;
3656                 }
3657                 if(bookHit) { // [HGM] book: simulate book reply
3658                     static char bookMove[MSG_SIZ]; // a bit generous?
3659
3660                     programStats.nodes = programStats.depth = programStats.time =
3661                     programStats.score = programStats.got_only_move = 0;
3662                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3663
3664                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3665                     strcat(bookMove, bookHit);
3666                     HandleMachineMove(bookMove, &first);
3667                 }
3668                 continue;
3669             }
3670
3671             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3672                  started == STARTED_HOLDINGS ||
3673                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3674                 /* Accumulate characters in move list or board */
3675                 parse[parse_pos++] = buf[i];
3676             }
3677
3678             /* Start of game messages.  Mostly we detect start of game
3679                when the first board image arrives.  On some versions
3680                of the ICS, though, we need to do a "refresh" after starting
3681                to observe in order to get the current board right away. */
3682             if (looking_at(buf, &i, "Adding game * to observation list")) {
3683                 started = STARTED_OBSERVE;
3684                 continue;
3685             }
3686
3687             /* Handle auto-observe */
3688             if (appData.autoObserve &&
3689                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3690                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3691                 char *player;
3692                 /* Choose the player that was highlighted, if any. */
3693                 if (star_match[0][0] == '\033' ||
3694                     star_match[1][0] != '\033') {
3695                     player = star_match[0];
3696                 } else {
3697                     player = star_match[2];
3698                 }
3699                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3700                         ics_prefix, StripHighlightAndTitle(player));
3701                 SendToICS(str);
3702
3703                 /* Save ratings from notify string */
3704                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3705                 player1Rating = string_to_rating(star_match[1]);
3706                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3707                 player2Rating = string_to_rating(star_match[3]);
3708
3709                 if (appData.debugMode)
3710                   fprintf(debugFP,
3711                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3712                           player1Name, player1Rating,
3713                           player2Name, player2Rating);
3714
3715                 continue;
3716             }
3717
3718             /* Deal with automatic examine mode after a game,
3719                and with IcsObserving -> IcsExamining transition */
3720             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3721                 looking_at(buf, &i, "has made you an examiner of game *")) {
3722
3723                 int gamenum = atoi(star_match[0]);
3724                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3725                     gamenum == ics_gamenum) {
3726                     /* We were already playing or observing this game;
3727                        no need to refetch history */
3728                     gameMode = IcsExamining;
3729                     if (pausing) {
3730                         pauseExamForwardMostMove = forwardMostMove;
3731                     } else if (currentMove < forwardMostMove) {
3732                         ForwardInner(forwardMostMove);
3733                     }
3734                 } else {
3735                     /* I don't think this case really can happen */
3736                     SendToICS(ics_prefix);
3737                     SendToICS("refresh\n");
3738                 }
3739                 continue;
3740             }
3741
3742             /* Error messages */
3743 //          if (ics_user_moved) {
3744             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3745                 if (looking_at(buf, &i, "Illegal move") ||
3746                     looking_at(buf, &i, "Not a legal move") ||
3747                     looking_at(buf, &i, "Your king is in check") ||
3748                     looking_at(buf, &i, "It isn't your turn") ||
3749                     looking_at(buf, &i, "It is not your move")) {
3750                     /* Illegal move */
3751                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3752                         currentMove = forwardMostMove-1;
3753                         DisplayMove(currentMove - 1); /* before DMError */
3754                         DrawPosition(FALSE, boards[currentMove]);
3755                         SwitchClocks(forwardMostMove-1); // [HGM] race
3756                         DisplayBothClocks();
3757                     }
3758                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3759                     ics_user_moved = 0;
3760                     continue;
3761                 }
3762             }
3763
3764             if (looking_at(buf, &i, "still have time") ||
3765                 looking_at(buf, &i, "not out of time") ||
3766                 looking_at(buf, &i, "either player is out of time") ||
3767                 looking_at(buf, &i, "has timeseal; checking")) {
3768                 /* We must have called his flag a little too soon */
3769                 whiteFlag = blackFlag = FALSE;
3770                 continue;
3771             }
3772
3773             if (looking_at(buf, &i, "added * seconds to") ||
3774                 looking_at(buf, &i, "seconds were added to")) {
3775                 /* Update the clocks */
3776                 SendToICS(ics_prefix);
3777                 SendToICS("refresh\n");
3778                 continue;
3779             }
3780
3781             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3782                 ics_clock_paused = TRUE;
3783                 StopClocks();
3784                 continue;
3785             }
3786
3787             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3788                 ics_clock_paused = FALSE;
3789                 StartClocks();
3790                 continue;
3791             }
3792
3793             /* Grab player ratings from the Creating: message.
3794                Note we have to check for the special case when
3795                the ICS inserts things like [white] or [black]. */
3796             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3797                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3798                 /* star_matches:
3799                    0    player 1 name (not necessarily white)
3800                    1    player 1 rating
3801                    2    empty, white, or black (IGNORED)
3802                    3    player 2 name (not necessarily black)
3803                    4    player 2 rating
3804
3805                    The names/ratings are sorted out when the game
3806                    actually starts (below).
3807                 */
3808                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3809                 player1Rating = string_to_rating(star_match[1]);
3810                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3811                 player2Rating = string_to_rating(star_match[4]);
3812
3813                 if (appData.debugMode)
3814                   fprintf(debugFP,
3815                           "Ratings from 'Creating:' %s %d, %s %d\n",
3816                           player1Name, player1Rating,
3817                           player2Name, player2Rating);
3818
3819                 continue;
3820             }
3821
3822             /* Improved generic start/end-of-game messages */
3823             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3824                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3825                 /* If tkind == 0: */
3826                 /* star_match[0] is the game number */
3827                 /*           [1] is the white player's name */
3828                 /*           [2] is the black player's name */
3829                 /* For end-of-game: */
3830                 /*           [3] is the reason for the game end */
3831                 /*           [4] is a PGN end game-token, preceded by " " */
3832                 /* For start-of-game: */
3833                 /*           [3] begins with "Creating" or "Continuing" */
3834                 /*           [4] is " *" or empty (don't care). */
3835                 int gamenum = atoi(star_match[0]);
3836                 char *whitename, *blackname, *why, *endtoken;
3837                 ChessMove endtype = EndOfFile;
3838
3839                 if (tkind == 0) {
3840                   whitename = star_match[1];
3841                   blackname = star_match[2];
3842                   why = star_match[3];
3843                   endtoken = star_match[4];
3844                 } else {
3845                   whitename = star_match[1];
3846                   blackname = star_match[3];
3847                   why = star_match[5];
3848                   endtoken = star_match[6];
3849                 }
3850
3851                 /* Game start messages */
3852                 if (strncmp(why, "Creating ", 9) == 0 ||
3853                     strncmp(why, "Continuing ", 11) == 0) {
3854                     gs_gamenum = gamenum;
3855                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3856                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3857 #if ZIPPY
3858                     if (appData.zippyPlay) {
3859                         ZippyGameStart(whitename, blackname);
3860                     }
3861 #endif /*ZIPPY*/
3862                     partnerBoardValid = FALSE; // [HGM] bughouse
3863                     continue;
3864                 }
3865
3866                 /* Game end messages */
3867                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3868                     ics_gamenum != gamenum) {
3869                     continue;
3870                 }
3871                 while (endtoken[0] == ' ') endtoken++;
3872                 switch (endtoken[0]) {
3873                   case '*':
3874                   default:
3875                     endtype = GameUnfinished;
3876                     break;
3877                   case '0':
3878                     endtype = BlackWins;
3879                     break;
3880                   case '1':
3881                     if (endtoken[1] == '/')
3882                       endtype = GameIsDrawn;
3883                     else
3884                       endtype = WhiteWins;
3885                     break;
3886                 }
3887                 GameEnds(endtype, why, GE_ICS);
3888 #if ZIPPY
3889                 if (appData.zippyPlay && first.initDone) {
3890                     ZippyGameEnd(endtype, why);
3891                     if (first.pr == NULL) {
3892                       /* Start the next process early so that we'll
3893                          be ready for the next challenge */
3894                       StartChessProgram(&first);
3895                     }
3896                     /* Send "new" early, in case this command takes
3897                        a long time to finish, so that we'll be ready
3898                        for the next challenge. */
3899                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3900                     Reset(TRUE, TRUE);
3901                 }
3902 #endif /*ZIPPY*/
3903                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3904                 continue;
3905             }
3906
3907             if (looking_at(buf, &i, "Removing game * from observation") ||
3908                 looking_at(buf, &i, "no longer observing game *") ||
3909                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3910                 if (gameMode == IcsObserving &&
3911                     atoi(star_match[0]) == ics_gamenum)
3912                   {
3913                       /* icsEngineAnalyze */
3914                       if (appData.icsEngineAnalyze) {
3915                             ExitAnalyzeMode();
3916                             ModeHighlight();
3917                       }
3918                       StopClocks();
3919                       gameMode = IcsIdle;
3920                       ics_gamenum = -1;
3921                       ics_user_moved = FALSE;
3922                   }
3923                 continue;
3924             }
3925
3926             if (looking_at(buf, &i, "no longer examining game *")) {
3927                 if (gameMode == IcsExamining &&
3928                     atoi(star_match[0]) == ics_gamenum)
3929                   {
3930                       gameMode = IcsIdle;
3931                       ics_gamenum = -1;
3932                       ics_user_moved = FALSE;
3933                   }
3934                 continue;
3935             }
3936
3937             /* Advance leftover_start past any newlines we find,
3938                so only partial lines can get reparsed */
3939             if (looking_at(buf, &i, "\n")) {
3940                 prevColor = curColor;
3941                 if (curColor != ColorNormal) {
3942                     if (oldi > next_out) {
3943                         SendToPlayer(&buf[next_out], oldi - next_out);
3944                         next_out = oldi;
3945                     }
3946                     Colorize(ColorNormal, FALSE);
3947                     curColor = ColorNormal;
3948                 }
3949                 if (started == STARTED_BOARD) {
3950                     started = STARTED_NONE;
3951                     parse[parse_pos] = NULLCHAR;
3952                     ParseBoard12(parse);
3953                     ics_user_moved = 0;
3954
3955                     /* Send premove here */
3956                     if (appData.premove) {
3957                       char str[MSG_SIZ];
3958                       if (currentMove == 0 &&
3959                           gameMode == IcsPlayingWhite &&
3960                           appData.premoveWhite) {
3961                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3962                         if (appData.debugMode)
3963                           fprintf(debugFP, "Sending premove:\n");
3964                         SendToICS(str);
3965                       } else if (currentMove == 1 &&
3966                                  gameMode == IcsPlayingBlack &&
3967                                  appData.premoveBlack) {
3968                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3969                         if (appData.debugMode)
3970                           fprintf(debugFP, "Sending premove:\n");
3971                         SendToICS(str);
3972                       } else if (gotPremove) {
3973                         gotPremove = 0;
3974                         ClearPremoveHighlights();
3975                         if (appData.debugMode)
3976                           fprintf(debugFP, "Sending premove:\n");
3977                           UserMoveEvent(premoveFromX, premoveFromY,
3978                                         premoveToX, premoveToY,
3979                                         premovePromoChar);
3980                       }
3981                     }
3982
3983                     /* Usually suppress following prompt */
3984                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3985                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3986                         if (looking_at(buf, &i, "*% ")) {
3987                             savingComment = FALSE;
3988                             suppressKibitz = 0;
3989                         }
3990                     }
3991                     next_out = i;
3992                 } else if (started == STARTED_HOLDINGS) {
3993                     int gamenum;
3994                     char new_piece[MSG_SIZ];
3995                     started = STARTED_NONE;
3996                     parse[parse_pos] = NULLCHAR;
3997                     if (appData.debugMode)
3998                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3999                                                         parse, currentMove);
4000                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4001                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4002                         if (gameInfo.variant == VariantNormal) {
4003                           /* [HGM] We seem to switch variant during a game!
4004                            * Presumably no holdings were displayed, so we have
4005                            * to move the position two files to the right to
4006                            * create room for them!
4007                            */
4008                           VariantClass newVariant;
4009                           switch(gameInfo.boardWidth) { // base guess on board width
4010                                 case 9:  newVariant = VariantShogi; break;
4011                                 case 10: newVariant = VariantGreat; break;
4012                                 default: newVariant = VariantCrazyhouse; break;
4013                           }
4014                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4015                           /* Get a move list just to see the header, which
4016                              will tell us whether this is really bug or zh */
4017                           if (ics_getting_history == H_FALSE) {
4018                             ics_getting_history = H_REQUESTED;
4019                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4020                             SendToICS(str);
4021                           }
4022                         }
4023                         new_piece[0] = NULLCHAR;
4024                         sscanf(parse, "game %d white [%s black [%s <- %s",
4025                                &gamenum, white_holding, black_holding,
4026                                new_piece);
4027                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4028                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4029                         /* [HGM] copy holdings to board holdings area */
4030                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4031                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4032                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4033 #if ZIPPY
4034                         if (appData.zippyPlay && first.initDone) {
4035                             ZippyHoldings(white_holding, black_holding,
4036                                           new_piece);
4037                         }
4038 #endif /*ZIPPY*/
4039                         if (tinyLayout || smallLayout) {
4040                             char wh[16], bh[16];
4041                             PackHolding(wh, white_holding);
4042                             PackHolding(bh, black_holding);
4043                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4044                                     gameInfo.white, gameInfo.black);
4045                         } else {
4046                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4047                                     gameInfo.white, white_holding,
4048                                     gameInfo.black, black_holding);
4049                         }
4050                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4051                         DrawPosition(FALSE, boards[currentMove]);
4052                         DisplayTitle(str);
4053                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4054                         sscanf(parse, "game %d white [%s black [%s <- %s",
4055                                &gamenum, white_holding, black_holding,
4056                                new_piece);
4057                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4058                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4059                         /* [HGM] copy holdings to partner-board holdings area */
4060                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4061                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4062                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4063                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4064                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4065                       }
4066                     }
4067                     /* Suppress following prompt */
4068                     if (looking_at(buf, &i, "*% ")) {
4069                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4070                         savingComment = FALSE;
4071                         suppressKibitz = 0;
4072                     }
4073                     next_out = i;
4074                 }
4075                 continue;
4076             }
4077
4078             i++;                /* skip unparsed character and loop back */
4079         }
4080
4081         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4082 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4083 //          SendToPlayer(&buf[next_out], i - next_out);
4084             started != STARTED_HOLDINGS && leftover_start > next_out) {
4085             SendToPlayer(&buf[next_out], leftover_start - next_out);
4086             next_out = i;
4087         }
4088
4089         leftover_len = buf_len - leftover_start;
4090         /* if buffer ends with something we couldn't parse,
4091            reparse it after appending the next read */
4092
4093     } else if (count == 0) {
4094         RemoveInputSource(isr);
4095         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4096     } else {
4097         DisplayFatalError(_("Error reading from ICS"), error, 1);
4098     }
4099 }
4100
4101
4102 /* Board style 12 looks like this:
4103
4104    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4105
4106  * The "<12> " is stripped before it gets to this routine.  The two
4107  * trailing 0's (flip state and clock ticking) are later addition, and
4108  * some chess servers may not have them, or may have only the first.
4109  * Additional trailing fields may be added in the future.
4110  */
4111
4112 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4113
4114 #define RELATION_OBSERVING_PLAYED    0
4115 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4116 #define RELATION_PLAYING_MYMOVE      1
4117 #define RELATION_PLAYING_NOTMYMOVE  -1
4118 #define RELATION_EXAMINING           2
4119 #define RELATION_ISOLATED_BOARD     -3
4120 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4121
4122 void
4123 ParseBoard12(string)
4124      char *string;
4125 {
4126     GameMode newGameMode;
4127     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4128     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4129     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4130     char to_play, board_chars[200];
4131     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4132     char black[32], white[32];
4133     Board board;
4134     int prevMove = currentMove;
4135     int ticking = 2;
4136     ChessMove moveType;
4137     int fromX, fromY, toX, toY;
4138     char promoChar;
4139     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4140     char *bookHit = NULL; // [HGM] book
4141     Boolean weird = FALSE, reqFlag = FALSE;
4142
4143     fromX = fromY = toX = toY = -1;
4144
4145     newGame = FALSE;
4146
4147     if (appData.debugMode)
4148       fprintf(debugFP, _("Parsing board: %s\n"), string);
4149
4150     move_str[0] = NULLCHAR;
4151     elapsed_time[0] = NULLCHAR;
4152     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4153         int  i = 0, j;
4154         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4155             if(string[i] == ' ') { ranks++; files = 0; }
4156             else files++;
4157             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4158             i++;
4159         }
4160         for(j = 0; j <i; j++) board_chars[j] = string[j];
4161         board_chars[i] = '\0';
4162         string += i + 1;
4163     }
4164     n = sscanf(string, PATTERN, &to_play, &double_push,
4165                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4166                &gamenum, white, black, &relation, &basetime, &increment,
4167                &white_stren, &black_stren, &white_time, &black_time,
4168                &moveNum, str, elapsed_time, move_str, &ics_flip,
4169                &ticking);
4170
4171     if (n < 21) {
4172         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4173         DisplayError(str, 0);
4174         return;
4175     }
4176
4177     /* Convert the move number to internal form */
4178     moveNum = (moveNum - 1) * 2;
4179     if (to_play == 'B') moveNum++;
4180     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4181       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4182                         0, 1);
4183       return;
4184     }
4185
4186     switch (relation) {
4187       case RELATION_OBSERVING_PLAYED:
4188       case RELATION_OBSERVING_STATIC:
4189         if (gamenum == -1) {
4190             /* Old ICC buglet */
4191             relation = RELATION_OBSERVING_STATIC;
4192         }
4193         newGameMode = IcsObserving;
4194         break;
4195       case RELATION_PLAYING_MYMOVE:
4196       case RELATION_PLAYING_NOTMYMOVE:
4197         newGameMode =
4198           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4199             IcsPlayingWhite : IcsPlayingBlack;
4200         break;
4201       case RELATION_EXAMINING:
4202         newGameMode = IcsExamining;
4203         break;
4204       case RELATION_ISOLATED_BOARD:
4205       default:
4206         /* Just display this board.  If user was doing something else,
4207            we will forget about it until the next board comes. */
4208         newGameMode = IcsIdle;
4209         break;
4210       case RELATION_STARTING_POSITION:
4211         newGameMode = gameMode;
4212         break;
4213     }
4214
4215     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4216          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4217       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4218       char *toSqr;
4219       for (k = 0; k < ranks; k++) {
4220         for (j = 0; j < files; j++)
4221           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4222         if(gameInfo.holdingsWidth > 1) {
4223              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4224              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4225         }
4226       }
4227       CopyBoard(partnerBoard, board);
4228       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4229         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4230         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4231       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4232       if(toSqr = strchr(str, '-')) {
4233         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4234         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4235       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4236       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4237       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4238       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4239       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4240       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4241                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4242       DisplayMessage(partnerStatus, "");
4243         partnerBoardValid = TRUE;
4244       return;
4245     }
4246
4247     /* Modify behavior for initial board display on move listing
4248        of wild games.
4249        */
4250     switch (ics_getting_history) {
4251       case H_FALSE:
4252       case H_REQUESTED:
4253         break;
4254       case H_GOT_REQ_HEADER:
4255       case H_GOT_UNREQ_HEADER:
4256         /* This is the initial position of the current game */
4257         gamenum = ics_gamenum;
4258         moveNum = 0;            /* old ICS bug workaround */
4259         if (to_play == 'B') {
4260           startedFromSetupPosition = TRUE;
4261           blackPlaysFirst = TRUE;
4262           moveNum = 1;
4263           if (forwardMostMove == 0) forwardMostMove = 1;
4264           if (backwardMostMove == 0) backwardMostMove = 1;
4265           if (currentMove == 0) currentMove = 1;
4266         }
4267         newGameMode = gameMode;
4268         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4269         break;
4270       case H_GOT_UNWANTED_HEADER:
4271         /* This is an initial board that we don't want */
4272         return;
4273       case H_GETTING_MOVES:
4274         /* Should not happen */
4275         DisplayError(_("Error gathering move list: extra board"), 0);
4276         ics_getting_history = H_FALSE;
4277         return;
4278     }
4279
4280    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4281                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4282      /* [HGM] We seem to have switched variant unexpectedly
4283       * Try to guess new variant from board size
4284       */
4285           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4286           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4287           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4288           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4289           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4290           if(!weird) newVariant = VariantNormal;
4291           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4292           /* Get a move list just to see the header, which
4293              will tell us whether this is really bug or zh */
4294           if (ics_getting_history == H_FALSE) {
4295             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4296             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4297             SendToICS(str);
4298           }
4299     }
4300
4301     /* Take action if this is the first board of a new game, or of a
4302        different game than is currently being displayed.  */
4303     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4304         relation == RELATION_ISOLATED_BOARD) {
4305
4306         /* Forget the old game and get the history (if any) of the new one */
4307         if (gameMode != BeginningOfGame) {
4308           Reset(TRUE, TRUE);
4309         }
4310         newGame = TRUE;
4311         if (appData.autoRaiseBoard) BoardToTop();
4312         prevMove = -3;
4313         if (gamenum == -1) {
4314             newGameMode = IcsIdle;
4315         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4316                    appData.getMoveList && !reqFlag) {
4317             /* Need to get game history */
4318             ics_getting_history = H_REQUESTED;
4319             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4320             SendToICS(str);
4321         }
4322
4323         /* Initially flip the board to have black on the bottom if playing
4324            black or if the ICS flip flag is set, but let the user change
4325            it with the Flip View button. */
4326         flipView = appData.autoFlipView ?
4327           (newGameMode == IcsPlayingBlack) || ics_flip :
4328           appData.flipView;
4329
4330         /* Done with values from previous mode; copy in new ones */
4331         gameMode = newGameMode;
4332         ModeHighlight();
4333         ics_gamenum = gamenum;
4334         if (gamenum == gs_gamenum) {
4335             int klen = strlen(gs_kind);
4336             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4337             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4338             gameInfo.event = StrSave(str);
4339         } else {
4340             gameInfo.event = StrSave("ICS game");
4341         }
4342         gameInfo.site = StrSave(appData.icsHost);
4343         gameInfo.date = PGNDate();
4344         gameInfo.round = StrSave("-");
4345         gameInfo.white = StrSave(white);
4346         gameInfo.black = StrSave(black);
4347         timeControl = basetime * 60 * 1000;
4348         timeControl_2 = 0;
4349         timeIncrement = increment * 1000;
4350         movesPerSession = 0;
4351         gameInfo.timeControl = TimeControlTagValue();
4352         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4353   if (appData.debugMode) {
4354     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4355     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4356     setbuf(debugFP, NULL);
4357   }
4358
4359         gameInfo.outOfBook = NULL;
4360
4361         /* Do we have the ratings? */
4362         if (strcmp(player1Name, white) == 0 &&
4363             strcmp(player2Name, black) == 0) {
4364             if (appData.debugMode)
4365               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4366                       player1Rating, player2Rating);
4367             gameInfo.whiteRating = player1Rating;
4368             gameInfo.blackRating = player2Rating;
4369         } else if (strcmp(player2Name, white) == 0 &&
4370                    strcmp(player1Name, black) == 0) {
4371             if (appData.debugMode)
4372               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4373                       player2Rating, player1Rating);
4374             gameInfo.whiteRating = player2Rating;
4375             gameInfo.blackRating = player1Rating;
4376         }
4377         player1Name[0] = player2Name[0] = NULLCHAR;
4378
4379         /* Silence shouts if requested */
4380         if (appData.quietPlay &&
4381             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4382             SendToICS(ics_prefix);
4383             SendToICS("set shout 0\n");
4384         }
4385     }
4386
4387     /* Deal with midgame name changes */
4388     if (!newGame) {
4389         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4390             if (gameInfo.white) free(gameInfo.white);
4391             gameInfo.white = StrSave(white);
4392         }
4393         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4394             if (gameInfo.black) free(gameInfo.black);
4395             gameInfo.black = StrSave(black);
4396         }
4397     }
4398
4399     /* Throw away game result if anything actually changes in examine mode */
4400     if (gameMode == IcsExamining && !newGame) {
4401         gameInfo.result = GameUnfinished;
4402         if (gameInfo.resultDetails != NULL) {
4403             free(gameInfo.resultDetails);
4404             gameInfo.resultDetails = NULL;
4405         }
4406     }
4407
4408     /* In pausing && IcsExamining mode, we ignore boards coming
4409        in if they are in a different variation than we are. */
4410     if (pauseExamInvalid) return;
4411     if (pausing && gameMode == IcsExamining) {
4412         if (moveNum <= pauseExamForwardMostMove) {
4413             pauseExamInvalid = TRUE;
4414             forwardMostMove = pauseExamForwardMostMove;
4415             return;
4416         }
4417     }
4418
4419   if (appData.debugMode) {
4420     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4421   }
4422     /* Parse the board */
4423     for (k = 0; k < ranks; k++) {
4424       for (j = 0; j < files; j++)
4425         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4426       if(gameInfo.holdingsWidth > 1) {
4427            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4428            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4429       }
4430     }
4431     CopyBoard(boards[moveNum], board);
4432     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4433     if (moveNum == 0) {
4434         startedFromSetupPosition =
4435           !CompareBoards(board, initialPosition);
4436         if(startedFromSetupPosition)
4437             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4438     }
4439
4440     /* [HGM] Set castling rights. Take the outermost Rooks,
4441        to make it also work for FRC opening positions. Note that board12
4442        is really defective for later FRC positions, as it has no way to
4443        indicate which Rook can castle if they are on the same side of King.
4444        For the initial position we grant rights to the outermost Rooks,
4445        and remember thos rights, and we then copy them on positions
4446        later in an FRC game. This means WB might not recognize castlings with
4447        Rooks that have moved back to their original position as illegal,
4448        but in ICS mode that is not its job anyway.
4449     */
4450     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4451     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4452
4453         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4454             if(board[0][i] == WhiteRook) j = i;
4455         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4456         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4457             if(board[0][i] == WhiteRook) j = i;
4458         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4459         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4460             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4461         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4462         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4463             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4464         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4465
4466         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4467         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4468             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4469         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4470             if(board[BOARD_HEIGHT-1][k] == bKing)
4471                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4472         if(gameInfo.variant == VariantTwoKings) {
4473             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4474             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4475             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4476         }
4477     } else { int r;
4478         r = boards[moveNum][CASTLING][0] = initialRights[0];
4479         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4480         r = boards[moveNum][CASTLING][1] = initialRights[1];
4481         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4482         r = boards[moveNum][CASTLING][3] = initialRights[3];
4483         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4484         r = boards[moveNum][CASTLING][4] = initialRights[4];
4485         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4486         /* wildcastle kludge: always assume King has rights */
4487         r = boards[moveNum][CASTLING][2] = initialRights[2];
4488         r = boards[moveNum][CASTLING][5] = initialRights[5];
4489     }
4490     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4491     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4492
4493
4494     if (ics_getting_history == H_GOT_REQ_HEADER ||
4495         ics_getting_history == H_GOT_UNREQ_HEADER) {
4496         /* This was an initial position from a move list, not
4497            the current position */
4498         return;
4499     }
4500
4501     /* Update currentMove and known move number limits */
4502     newMove = newGame || moveNum > forwardMostMove;
4503
4504     if (newGame) {
4505         forwardMostMove = backwardMostMove = currentMove = moveNum;
4506         if (gameMode == IcsExamining && moveNum == 0) {
4507           /* Workaround for ICS limitation: we are not told the wild
4508              type when starting to examine a game.  But if we ask for
4509              the move list, the move list header will tell us */
4510             ics_getting_history = H_REQUESTED;
4511             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4512             SendToICS(str);
4513         }
4514     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4515                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4516 #if ZIPPY
4517         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4518         /* [HGM] applied this also to an engine that is silently watching        */
4519         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4520             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4521             gameInfo.variant == currentlyInitializedVariant) {
4522           takeback = forwardMostMove - moveNum;
4523           for (i = 0; i < takeback; i++) {
4524             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4525             SendToProgram("undo\n", &first);
4526           }
4527         }
4528 #endif
4529
4530         forwardMostMove = moveNum;
4531         if (!pausing || currentMove > forwardMostMove)
4532           currentMove = forwardMostMove;
4533     } else {
4534         /* New part of history that is not contiguous with old part */
4535         if (pausing && gameMode == IcsExamining) {
4536             pauseExamInvalid = TRUE;
4537             forwardMostMove = pauseExamForwardMostMove;
4538             return;
4539         }
4540         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4541 #if ZIPPY
4542             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4543                 // [HGM] when we will receive the move list we now request, it will be
4544                 // fed to the engine from the first move on. So if the engine is not
4545                 // in the initial position now, bring it there.
4546                 InitChessProgram(&first, 0);
4547             }
4548 #endif
4549             ics_getting_history = H_REQUESTED;
4550             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4551             SendToICS(str);
4552         }
4553         forwardMostMove = backwardMostMove = currentMove = moveNum;
4554     }
4555
4556     /* Update the clocks */
4557     if (strchr(elapsed_time, '.')) {
4558       /* Time is in ms */
4559       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4560       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4561     } else {
4562       /* Time is in seconds */
4563       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4564       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4565     }
4566
4567
4568 #if ZIPPY
4569     if (appData.zippyPlay && newGame &&
4570         gameMode != IcsObserving && gameMode != IcsIdle &&
4571         gameMode != IcsExamining)
4572       ZippyFirstBoard(moveNum, basetime, increment);
4573 #endif
4574
4575     /* Put the move on the move list, first converting
4576        to canonical algebraic form. */
4577     if (moveNum > 0) {
4578   if (appData.debugMode) {
4579     if (appData.debugMode) { int f = forwardMostMove;
4580         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4581                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4582                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4583     }
4584     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4585     fprintf(debugFP, "moveNum = %d\n", moveNum);
4586     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4587     setbuf(debugFP, NULL);
4588   }
4589         if (moveNum <= backwardMostMove) {
4590             /* We don't know what the board looked like before
4591                this move.  Punt. */
4592           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4593             strcat(parseList[moveNum - 1], " ");
4594             strcat(parseList[moveNum - 1], elapsed_time);
4595             moveList[moveNum - 1][0] = NULLCHAR;
4596         } else if (strcmp(move_str, "none") == 0) {
4597             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4598             /* Again, we don't know what the board looked like;
4599                this is really the start of the game. */
4600             parseList[moveNum - 1][0] = NULLCHAR;
4601             moveList[moveNum - 1][0] = NULLCHAR;
4602             backwardMostMove = moveNum;
4603             startedFromSetupPosition = TRUE;
4604             fromX = fromY = toX = toY = -1;
4605         } else {
4606           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4607           //                 So we parse the long-algebraic move string in stead of the SAN move
4608           int valid; char buf[MSG_SIZ], *prom;
4609
4610           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4611                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4612           // str looks something like "Q/a1-a2"; kill the slash
4613           if(str[1] == '/')
4614             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4615           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4616           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4617                 strcat(buf, prom); // long move lacks promo specification!
4618           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4619                 if(appData.debugMode)
4620                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4621                 safeStrCpy(move_str, buf, MSG_SIZ);
4622           }
4623           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4624                                 &fromX, &fromY, &toX, &toY, &promoChar)
4625                || ParseOneMove(buf, moveNum - 1, &moveType,
4626                                 &fromX, &fromY, &toX, &toY, &promoChar);
4627           // end of long SAN patch
4628           if (valid) {
4629             (void) CoordsToAlgebraic(boards[moveNum - 1],
4630                                      PosFlags(moveNum - 1),
4631                                      fromY, fromX, toY, toX, promoChar,
4632                                      parseList[moveNum-1]);
4633             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4634               case MT_NONE:
4635               case MT_STALEMATE:
4636               default:
4637                 break;
4638               case MT_CHECK:
4639                 if(gameInfo.variant != VariantShogi)
4640                     strcat(parseList[moveNum - 1], "+");
4641                 break;
4642               case MT_CHECKMATE:
4643               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4644                 strcat(parseList[moveNum - 1], "#");
4645                 break;
4646             }
4647             strcat(parseList[moveNum - 1], " ");
4648             strcat(parseList[moveNum - 1], elapsed_time);
4649             /* currentMoveString is set as a side-effect of ParseOneMove */
4650             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4651             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4652             strcat(moveList[moveNum - 1], "\n");
4653
4654             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4655                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4656               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4657                 ChessSquare old, new = boards[moveNum][k][j];
4658                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4659                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4660                   if(old == new) continue;
4661                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4662                   else if(new == WhiteWazir || new == BlackWazir) {
4663                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4664                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4665                       else boards[moveNum][k][j] = old; // preserve type of Gold
4666                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4667                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4668               }
4669           } else {
4670             /* Move from ICS was illegal!?  Punt. */
4671             if (appData.debugMode) {
4672               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4673               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4674             }
4675             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4676             strcat(parseList[moveNum - 1], " ");
4677             strcat(parseList[moveNum - 1], elapsed_time);
4678             moveList[moveNum - 1][0] = NULLCHAR;
4679             fromX = fromY = toX = toY = -1;
4680           }
4681         }
4682   if (appData.debugMode) {
4683     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4684     setbuf(debugFP, NULL);
4685   }
4686
4687 #if ZIPPY
4688         /* Send move to chess program (BEFORE animating it). */
4689         if (appData.zippyPlay && !newGame && newMove &&
4690            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4691
4692             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4693                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4694                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4695                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4696                             move_str);
4697                     DisplayError(str, 0);
4698                 } else {
4699                     if (first.sendTime) {
4700                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4701                     }
4702                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4703                     if (firstMove && !bookHit) {
4704                         firstMove = FALSE;
4705                         if (first.useColors) {
4706                           SendToProgram(gameMode == IcsPlayingWhite ?
4707                                         "white\ngo\n" :
4708                                         "black\ngo\n", &first);
4709                         } else {
4710                           SendToProgram("go\n", &first);
4711                         }
4712                         first.maybeThinking = TRUE;
4713                     }
4714                 }
4715             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4716               if (moveList[moveNum - 1][0] == NULLCHAR) {
4717                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4718                 DisplayError(str, 0);
4719               } else {
4720                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4721                 SendMoveToProgram(moveNum - 1, &first);
4722               }
4723             }
4724         }
4725 #endif
4726     }
4727
4728     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4729         /* If move comes from a remote source, animate it.  If it
4730            isn't remote, it will have already been animated. */
4731         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4732             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4733         }
4734         if (!pausing && appData.highlightLastMove) {
4735             SetHighlights(fromX, fromY, toX, toY);
4736         }
4737     }
4738
4739     /* Start the clocks */
4740     whiteFlag = blackFlag = FALSE;
4741     appData.clockMode = !(basetime == 0 && increment == 0);
4742     if (ticking == 0) {
4743       ics_clock_paused = TRUE;
4744       StopClocks();
4745     } else if (ticking == 1) {
4746       ics_clock_paused = FALSE;
4747     }
4748     if (gameMode == IcsIdle ||
4749         relation == RELATION_OBSERVING_STATIC ||
4750         relation == RELATION_EXAMINING ||
4751         ics_clock_paused)
4752       DisplayBothClocks();
4753     else
4754       StartClocks();
4755
4756     /* Display opponents and material strengths */
4757     if (gameInfo.variant != VariantBughouse &&
4758         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4759         if (tinyLayout || smallLayout) {
4760             if(gameInfo.variant == VariantNormal)
4761               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4762                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4763                     basetime, increment);
4764             else
4765               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4766                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4767                     basetime, increment, (int) gameInfo.variant);
4768         } else {
4769             if(gameInfo.variant == VariantNormal)
4770               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4771                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4772                     basetime, increment);
4773             else
4774               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4775                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4776                     basetime, increment, VariantName(gameInfo.variant));
4777         }
4778         DisplayTitle(str);
4779   if (appData.debugMode) {
4780     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4781   }
4782     }
4783
4784
4785     /* Display the board */
4786     if (!pausing && !appData.noGUI) {
4787
4788       if (appData.premove)
4789           if (!gotPremove ||
4790              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4791              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4792               ClearPremoveHighlights();
4793
4794       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4795         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4796       DrawPosition(j, boards[currentMove]);
4797
4798       DisplayMove(moveNum - 1);
4799       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4800             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4801               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4802         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4803       }
4804     }
4805
4806     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4807 #if ZIPPY
4808     if(bookHit) { // [HGM] book: simulate book reply
4809         static char bookMove[MSG_SIZ]; // a bit generous?
4810
4811         programStats.nodes = programStats.depth = programStats.time =
4812         programStats.score = programStats.got_only_move = 0;
4813         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4814
4815         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4816         strcat(bookMove, bookHit);
4817         HandleMachineMove(bookMove, &first);
4818     }
4819 #endif
4820 }
4821
4822 void
4823 GetMoveListEvent()
4824 {
4825     char buf[MSG_SIZ];
4826     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4827         ics_getting_history = H_REQUESTED;
4828         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4829         SendToICS(buf);
4830     }
4831 }
4832
4833 void
4834 AnalysisPeriodicEvent(force)
4835      int force;
4836 {
4837     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4838          && !force) || !appData.periodicUpdates)
4839       return;
4840
4841     /* Send . command to Crafty to collect stats */
4842     SendToProgram(".\n", &first);
4843
4844     /* Don't send another until we get a response (this makes
4845        us stop sending to old Crafty's which don't understand
4846        the "." command (sending illegal cmds resets node count & time,
4847        which looks bad)) */
4848     programStats.ok_to_send = 0;
4849 }
4850
4851 void ics_update_width(new_width)
4852         int new_width;
4853 {
4854         ics_printf("set width %d\n", new_width);
4855 }
4856
4857 void
4858 SendMoveToProgram(moveNum, cps)
4859      int moveNum;
4860      ChessProgramState *cps;
4861 {
4862     char buf[MSG_SIZ];
4863
4864     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4865         // null move in variant where engine does not understand it (for analysis purposes)
4866         SendBoard(cps, moveNum + 1); // send position after move in stead.
4867         return;
4868     }
4869     if (cps->useUsermove) {
4870       SendToProgram("usermove ", cps);
4871     }
4872     if (cps->useSAN) {
4873       char *space;
4874       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4875         int len = space - parseList[moveNum];
4876         memcpy(buf, parseList[moveNum], len);
4877         buf[len++] = '\n';
4878         buf[len] = NULLCHAR;
4879       } else {
4880         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4881       }
4882       SendToProgram(buf, cps);
4883     } else {
4884       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4885         AlphaRank(moveList[moveNum], 4);
4886         SendToProgram(moveList[moveNum], cps);
4887         AlphaRank(moveList[moveNum], 4); // and back
4888       } else
4889       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4890        * the engine. It would be nice to have a better way to identify castle
4891        * moves here. */
4892       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4893                                                                          && cps->useOOCastle) {
4894         int fromX = moveList[moveNum][0] - AAA;
4895         int fromY = moveList[moveNum][1] - ONE;
4896         int toX = moveList[moveNum][2] - AAA;
4897         int toY = moveList[moveNum][3] - ONE;
4898         if((boards[moveNum][fromY][fromX] == WhiteKing
4899             && boards[moveNum][toY][toX] == WhiteRook)
4900            || (boards[moveNum][fromY][fromX] == BlackKing
4901                && boards[moveNum][toY][toX] == BlackRook)) {
4902           if(toX > fromX) SendToProgram("O-O\n", cps);
4903           else SendToProgram("O-O-O\n", cps);
4904         }
4905         else SendToProgram(moveList[moveNum], cps);
4906       } else
4907       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4908         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4909           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4910           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4911                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4912         } else
4913           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4914                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4915         SendToProgram(buf, cps);
4916       }
4917       else SendToProgram(moveList[moveNum], cps);
4918       /* End of additions by Tord */
4919     }
4920
4921     /* [HGM] setting up the opening has brought engine in force mode! */
4922     /*       Send 'go' if we are in a mode where machine should play. */
4923     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4924         (gameMode == TwoMachinesPlay   ||
4925 #if ZIPPY
4926          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4927 #endif
4928          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4929         SendToProgram("go\n", cps);
4930   if (appData.debugMode) {
4931     fprintf(debugFP, "(extra)\n");
4932   }
4933     }
4934     setboardSpoiledMachineBlack = 0;
4935 }
4936
4937 void
4938 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4939      ChessMove moveType;
4940      int fromX, fromY, toX, toY;
4941      char promoChar;
4942 {
4943     char user_move[MSG_SIZ];
4944
4945     switch (moveType) {
4946       default:
4947         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4948                 (int)moveType, fromX, fromY, toX, toY);
4949         DisplayError(user_move + strlen("say "), 0);
4950         break;
4951       case WhiteKingSideCastle:
4952       case BlackKingSideCastle:
4953       case WhiteQueenSideCastleWild:
4954       case BlackQueenSideCastleWild:
4955       /* PUSH Fabien */
4956       case WhiteHSideCastleFR:
4957       case BlackHSideCastleFR:
4958       /* POP Fabien */
4959         snprintf(user_move, MSG_SIZ, "o-o\n");
4960         break;
4961       case WhiteQueenSideCastle:
4962       case BlackQueenSideCastle:
4963       case WhiteKingSideCastleWild:
4964       case BlackKingSideCastleWild:
4965       /* PUSH Fabien */
4966       case WhiteASideCastleFR:
4967       case BlackASideCastleFR:
4968       /* POP Fabien */
4969         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4970         break;
4971       case WhiteNonPromotion:
4972       case BlackNonPromotion:
4973         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4974         break;
4975       case WhitePromotion:
4976       case BlackPromotion:
4977         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4978           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4979                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4980                 PieceToChar(WhiteFerz));
4981         else if(gameInfo.variant == VariantGreat)
4982           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4983                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4984                 PieceToChar(WhiteMan));
4985         else
4986           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4987                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4988                 promoChar);
4989         break;
4990       case WhiteDrop:
4991       case BlackDrop:
4992       drop:
4993         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4994                  ToUpper(PieceToChar((ChessSquare) fromX)),
4995                  AAA + toX, ONE + toY);
4996         break;
4997       case IllegalMove:  /* could be a variant we don't quite understand */
4998         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4999       case NormalMove:
5000       case WhiteCapturesEnPassant:
5001       case BlackCapturesEnPassant:
5002         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5003                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5004         break;
5005     }
5006     SendToICS(user_move);
5007     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5008         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5009 }
5010
5011 void
5012 UploadGameEvent()
5013 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5014     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5015     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5016     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5017         DisplayError("You cannot do this while you are playing or observing", 0);
5018         return;
5019     }
5020     if(gameMode != IcsExamining) { // is this ever not the case?
5021         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5022
5023         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5024           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5025         } else { // on FICS we must first go to general examine mode
5026           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5027         }
5028         if(gameInfo.variant != VariantNormal) {
5029             // try figure out wild number, as xboard names are not always valid on ICS
5030             for(i=1; i<=36; i++) {
5031               snprintf(buf, MSG_SIZ, "wild/%d", i);
5032                 if(StringToVariant(buf) == gameInfo.variant) break;
5033             }
5034             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5035             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5036             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5037         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5038         SendToICS(ics_prefix);
5039         SendToICS(buf);
5040         if(startedFromSetupPosition || backwardMostMove != 0) {
5041           fen = PositionToFEN(backwardMostMove, NULL);
5042           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5043             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5044             SendToICS(buf);
5045           } else { // FICS: everything has to set by separate bsetup commands
5046             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5047             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5048             SendToICS(buf);
5049             if(!WhiteOnMove(backwardMostMove)) {
5050                 SendToICS("bsetup tomove black\n");
5051             }
5052             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5053             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5054             SendToICS(buf);
5055             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5056             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5057             SendToICS(buf);
5058             i = boards[backwardMostMove][EP_STATUS];
5059             if(i >= 0) { // set e.p.
5060               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5061                 SendToICS(buf);
5062             }
5063             bsetup++;
5064           }
5065         }
5066       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5067             SendToICS("bsetup done\n"); // switch to normal examining.
5068     }
5069     for(i = backwardMostMove; i<last; i++) {
5070         char buf[20];
5071         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5072         SendToICS(buf);
5073     }
5074     SendToICS(ics_prefix);
5075     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5076 }
5077
5078 void
5079 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5080      int rf, ff, rt, ft;
5081      char promoChar;
5082      char move[7];
5083 {
5084     if (rf == DROP_RANK) {
5085       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5086       sprintf(move, "%c@%c%c\n",
5087                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5088     } else {
5089         if (promoChar == 'x' || promoChar == NULLCHAR) {
5090           sprintf(move, "%c%c%c%c\n",
5091                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5092         } else {
5093             sprintf(move, "%c%c%c%c%c\n",
5094                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5095         }
5096     }
5097 }
5098
5099 void
5100 ProcessICSInitScript(f)
5101      FILE *f;
5102 {
5103     char buf[MSG_SIZ];
5104
5105     while (fgets(buf, MSG_SIZ, f)) {
5106         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5107     }
5108
5109     fclose(f);
5110 }
5111
5112
5113 static int lastX, lastY, selectFlag, dragging;
5114
5115 void
5116 Sweep(int step)
5117 {
5118     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5119     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5120     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5121     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5122     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5123     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5124     do {
5125         promoSweep -= step;
5126         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5127         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5128         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5129         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5130         if(!step) step = 1;
5131     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5132             appData.testLegality && (promoSweep == king ||
5133             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5134     ChangeDragPiece(promoSweep);
5135 }
5136
5137 int PromoScroll(int x, int y)
5138 {
5139   int step = 0;
5140
5141   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5142   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5143   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5144   if(!step) return FALSE;
5145   lastX = x; lastY = y;
5146   if((promoSweep < BlackPawn) == flipView) step = -step;
5147   if(step > 0) selectFlag = 1;
5148   if(!selectFlag) Sweep(step);
5149   return FALSE;
5150 }
5151
5152 void
5153 NextPiece(int step)
5154 {
5155     ChessSquare piece = boards[currentMove][toY][toX];
5156     do {
5157         pieceSweep -= step;
5158         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5159         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5160         if(!step) step = -1;
5161     } while(PieceToChar(pieceSweep) == '.');
5162     boards[currentMove][toY][toX] = pieceSweep;
5163     DrawPosition(FALSE, boards[currentMove]);
5164     boards[currentMove][toY][toX] = piece;
5165 }
5166 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5167 void
5168 AlphaRank(char *move, int n)
5169 {
5170 //    char *p = move, c; int x, y;
5171
5172     if (appData.debugMode) {
5173         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5174     }
5175
5176     if(move[1]=='*' &&
5177        move[2]>='0' && move[2]<='9' &&
5178        move[3]>='a' && move[3]<='x'    ) {
5179         move[1] = '@';
5180         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5181         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5182     } else
5183     if(move[0]>='0' && move[0]<='9' &&
5184        move[1]>='a' && move[1]<='x' &&
5185        move[2]>='0' && move[2]<='9' &&
5186        move[3]>='a' && move[3]<='x'    ) {
5187         /* input move, Shogi -> normal */
5188         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5189         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5190         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5191         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5192     } else
5193     if(move[1]=='@' &&
5194        move[3]>='0' && move[3]<='9' &&
5195        move[2]>='a' && move[2]<='x'    ) {
5196         move[1] = '*';
5197         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5198         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5199     } else
5200     if(
5201        move[0]>='a' && move[0]<='x' &&
5202        move[3]>='0' && move[3]<='9' &&
5203        move[2]>='a' && move[2]<='x'    ) {
5204          /* output move, normal -> Shogi */
5205         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5206         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5207         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5208         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5209         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5210     }
5211     if (appData.debugMode) {
5212         fprintf(debugFP, "   out = '%s'\n", move);
5213     }
5214 }
5215
5216 char yy_textstr[8000];
5217
5218 /* Parser for moves from gnuchess, ICS, or user typein box */
5219 Boolean
5220 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5221      char *move;
5222      int moveNum;
5223      ChessMove *moveType;
5224      int *fromX, *fromY, *toX, *toY;
5225      char *promoChar;
5226 {
5227     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5228
5229     switch (*moveType) {
5230       case WhitePromotion:
5231       case BlackPromotion:
5232       case WhiteNonPromotion:
5233       case BlackNonPromotion:
5234       case NormalMove:
5235       case WhiteCapturesEnPassant:
5236       case BlackCapturesEnPassant:
5237       case WhiteKingSideCastle:
5238       case WhiteQueenSideCastle:
5239       case BlackKingSideCastle:
5240       case BlackQueenSideCastle:
5241       case WhiteKingSideCastleWild:
5242       case WhiteQueenSideCastleWild:
5243       case BlackKingSideCastleWild:
5244       case BlackQueenSideCastleWild:
5245       /* Code added by Tord: */
5246       case WhiteHSideCastleFR:
5247       case WhiteASideCastleFR:
5248       case BlackHSideCastleFR:
5249       case BlackASideCastleFR:
5250       /* End of code added by Tord */
5251       case IllegalMove:         /* bug or odd chess variant */
5252         *fromX = currentMoveString[0] - AAA;
5253         *fromY = currentMoveString[1] - ONE;
5254         *toX = currentMoveString[2] - AAA;
5255         *toY = currentMoveString[3] - ONE;
5256         *promoChar = currentMoveString[4];
5257         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5258             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5259     if (appData.debugMode) {
5260         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5261     }
5262             *fromX = *fromY = *toX = *toY = 0;
5263             return FALSE;
5264         }
5265         if (appData.testLegality) {
5266           return (*moveType != IllegalMove);
5267         } else {
5268           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5269                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5270         }
5271
5272       case WhiteDrop:
5273       case BlackDrop:
5274         *fromX = *moveType == WhiteDrop ?
5275           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5276           (int) CharToPiece(ToLower(currentMoveString[0]));
5277         *fromY = DROP_RANK;
5278         *toX = currentMoveString[2] - AAA;
5279         *toY = currentMoveString[3] - ONE;
5280         *promoChar = NULLCHAR;
5281         return TRUE;
5282
5283       case AmbiguousMove:
5284       case ImpossibleMove:
5285       case EndOfFile:
5286       case ElapsedTime:
5287       case Comment:
5288       case PGNTag:
5289       case NAG:
5290       case WhiteWins:
5291       case BlackWins:
5292       case GameIsDrawn:
5293       default:
5294     if (appData.debugMode) {
5295         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5296     }
5297         /* bug? */
5298         *fromX = *fromY = *toX = *toY = 0;
5299         *promoChar = NULLCHAR;
5300         return FALSE;
5301     }
5302 }
5303
5304 Boolean pushed = FALSE;
5305 char *lastParseAttempt;
5306
5307 void
5308 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5309 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5310   int fromX, fromY, toX, toY; char promoChar;
5311   ChessMove moveType;
5312   Boolean valid;
5313   int nr = 0;
5314
5315   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5316     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5317     pushed = TRUE;
5318   }
5319   endPV = forwardMostMove;
5320   do {
5321     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5322     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5323     lastParseAttempt = pv;
5324     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5325 if(appData.debugMode){
5326 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5327 }
5328     if(!valid && nr == 0 &&
5329        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5330         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5331         // Hande case where played move is different from leading PV move
5332         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5333         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5334         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5335         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5336           endPV += 2; // if position different, keep this
5337           moveList[endPV-1][0] = fromX + AAA;
5338           moveList[endPV-1][1] = fromY + ONE;
5339           moveList[endPV-1][2] = toX + AAA;
5340           moveList[endPV-1][3] = toY + ONE;
5341           parseList[endPV-1][0] = NULLCHAR;
5342           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5343         }
5344       }
5345     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5346     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5347     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5348     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5349         valid++; // allow comments in PV
5350         continue;
5351     }
5352     nr++;
5353     if(endPV+1 > framePtr) break; // no space, truncate
5354     if(!valid) break;
5355     endPV++;
5356     CopyBoard(boards[endPV], boards[endPV-1]);
5357     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5358     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5359     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5360     CoordsToAlgebraic(boards[endPV - 1],
5361                              PosFlags(endPV - 1),
5362                              fromY, fromX, toY, toX, promoChar,
5363                              parseList[endPV - 1]);
5364   } while(valid);
5365   if(atEnd == 2) return; // used hidden, for PV conversion
5366   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5367   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5368   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5369                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5370   DrawPosition(TRUE, boards[currentMove]);
5371 }
5372
5373 int
5374 MultiPV(ChessProgramState *cps)
5375 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5376         int i;
5377         for(i=0; i<cps->nrOptions; i++)
5378             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5379                 return i;
5380         return -1;
5381 }
5382
5383 Boolean
5384 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5385 {
5386         int startPV, multi, lineStart, origIndex = index;
5387         char *p, buf2[MSG_SIZ];
5388
5389         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5390         lastX = x; lastY = y;
5391         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5392         lineStart = startPV = index;
5393         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5394         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5395         index = startPV;
5396         do{ while(buf[index] && buf[index] != '\n') index++;
5397         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5398         buf[index] = 0;
5399         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5400                 int n = first.option[multi].value;
5401                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5402                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5403                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5404                 first.option[multi].value = n;
5405                 *start = *end = 0;
5406                 return FALSE;
5407         }
5408         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5409         *start = startPV; *end = index-1;
5410         return TRUE;
5411 }
5412
5413 char *
5414 PvToSAN(char *pv)
5415 {
5416         static char buf[10*MSG_SIZ];
5417         int i, k=0, savedEnd=endPV;
5418         *buf = NULLCHAR;
5419         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5420         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5421         for(i = forwardMostMove; i<endPV; i++){
5422             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5423             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5424             k += strlen(buf+k);
5425         }
5426         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5427         if(forwardMostMove < savedEnd) PopInner(0);
5428         endPV = savedEnd;
5429         return buf;
5430 }
5431
5432 Boolean
5433 LoadPV(int x, int y)
5434 { // called on right mouse click to load PV
5435   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5436   lastX = x; lastY = y;
5437   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5438   return TRUE;
5439 }
5440
5441 void
5442 UnLoadPV()
5443 {
5444   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5445   if(endPV < 0) return;
5446   endPV = -1;
5447   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5448         Boolean saveAnimate = appData.animate;
5449         if(pushed) {
5450             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5451                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5452             } else storedGames--; // abandon shelved tail of original game
5453         }
5454         pushed = FALSE;
5455         forwardMostMove = currentMove;
5456         currentMove = oldFMM;
5457         appData.animate = FALSE;
5458         ToNrEvent(forwardMostMove);
5459         appData.animate = saveAnimate;
5460   }
5461   currentMove = forwardMostMove;
5462   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5463   ClearPremoveHighlights();
5464   DrawPosition(TRUE, boards[currentMove]);
5465 }
5466
5467 void
5468 MovePV(int x, int y, int h)
5469 { // step through PV based on mouse coordinates (called on mouse move)
5470   int margin = h>>3, step = 0;
5471
5472   // we must somehow check if right button is still down (might be released off board!)
5473   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5474   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5475   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5476   if(!step) return;
5477   lastX = x; lastY = y;
5478
5479   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5480   if(endPV < 0) return;
5481   if(y < margin) step = 1; else
5482   if(y > h - margin) step = -1;
5483   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5484   currentMove += step;
5485   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5486   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5487                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5488   DrawPosition(FALSE, boards[currentMove]);
5489 }
5490
5491
5492 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5493 // All positions will have equal probability, but the current method will not provide a unique
5494 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5495 #define DARK 1
5496 #define LITE 2
5497 #define ANY 3
5498
5499 int squaresLeft[4];
5500 int piecesLeft[(int)BlackPawn];
5501 int seed, nrOfShuffles;
5502
5503 void GetPositionNumber()
5504 {       // sets global variable seed
5505         int i;
5506
5507         seed = appData.defaultFrcPosition;
5508         if(seed < 0) { // randomize based on time for negative FRC position numbers
5509                 for(i=0; i<50; i++) seed += random();
5510                 seed = random() ^ random() >> 8 ^ random() << 8;
5511                 if(seed<0) seed = -seed;
5512         }
5513 }
5514
5515 int put(Board board, int pieceType, int rank, int n, int shade)
5516 // put the piece on the (n-1)-th empty squares of the given shade
5517 {
5518         int i;
5519
5520         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5521                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5522                         board[rank][i] = (ChessSquare) pieceType;
5523                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5524                         squaresLeft[ANY]--;
5525                         piecesLeft[pieceType]--;
5526                         return i;
5527                 }
5528         }
5529         return -1;
5530 }
5531
5532
5533 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5534 // calculate where the next piece goes, (any empty square), and put it there
5535 {
5536         int i;
5537
5538         i = seed % squaresLeft[shade];
5539         nrOfShuffles *= squaresLeft[shade];
5540         seed /= squaresLeft[shade];
5541         put(board, pieceType, rank, i, shade);
5542 }
5543
5544 void AddTwoPieces(Board board, int pieceType, int rank)
5545 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5546 {
5547         int i, n=squaresLeft[ANY], j=n-1, k;
5548
5549         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5550         i = seed % k;  // pick one
5551         nrOfShuffles *= k;
5552         seed /= k;
5553         while(i >= j) i -= j--;
5554         j = n - 1 - j; i += j;
5555         put(board, pieceType, rank, j, ANY);
5556         put(board, pieceType, rank, i, ANY);
5557 }
5558
5559 void SetUpShuffle(Board board, int number)
5560 {
5561         int i, p, first=1;
5562
5563         GetPositionNumber(); nrOfShuffles = 1;
5564
5565         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5566         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5567         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5568
5569         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5570
5571         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5572             p = (int) board[0][i];
5573             if(p < (int) BlackPawn) piecesLeft[p] ++;
5574             board[0][i] = EmptySquare;
5575         }
5576
5577         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5578             // shuffles restricted to allow normal castling put KRR first
5579             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5580                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5581             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5582                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5583             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5584                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5585             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5586                 put(board, WhiteRook, 0, 0, ANY);
5587             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5588         }
5589
5590         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5591             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5592             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5593                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5594                 while(piecesLeft[p] >= 2) {
5595                     AddOnePiece(board, p, 0, LITE);
5596                     AddOnePiece(board, p, 0, DARK);
5597                 }
5598                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5599             }
5600
5601         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5602             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5603             // but we leave King and Rooks for last, to possibly obey FRC restriction
5604             if(p == (int)WhiteRook) continue;
5605             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5606             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5607         }
5608
5609         // now everything is placed, except perhaps King (Unicorn) and Rooks
5610
5611         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5612             // Last King gets castling rights
5613             while(piecesLeft[(int)WhiteUnicorn]) {
5614                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5615                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5616             }
5617
5618             while(piecesLeft[(int)WhiteKing]) {
5619                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5620                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5621             }
5622
5623
5624         } else {
5625             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5626             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5627         }
5628
5629         // Only Rooks can be left; simply place them all
5630         while(piecesLeft[(int)WhiteRook]) {
5631                 i = put(board, WhiteRook, 0, 0, ANY);
5632                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5633                         if(first) {
5634                                 first=0;
5635                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5636                         }
5637                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5638                 }
5639         }
5640         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5641             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5642         }
5643
5644         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5645 }
5646
5647 int SetCharTable( char *table, const char * map )
5648 /* [HGM] moved here from winboard.c because of its general usefulness */
5649 /*       Basically a safe strcpy that uses the last character as King */
5650 {
5651     int result = FALSE; int NrPieces;
5652
5653     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5654                     && NrPieces >= 12 && !(NrPieces&1)) {
5655         int i; /* [HGM] Accept even length from 12 to 34 */
5656
5657         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5658         for( i=0; i<NrPieces/2-1; i++ ) {
5659             table[i] = map[i];
5660             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5661         }
5662         table[(int) WhiteKing]  = map[NrPieces/2-1];
5663         table[(int) BlackKing]  = map[NrPieces-1];
5664
5665         result = TRUE;
5666     }
5667
5668     return result;
5669 }
5670
5671 void Prelude(Board board)
5672 {       // [HGM] superchess: random selection of exo-pieces
5673         int i, j, k; ChessSquare p;
5674         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5675
5676         GetPositionNumber(); // use FRC position number
5677
5678         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5679             SetCharTable(pieceToChar, appData.pieceToCharTable);
5680             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5681                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5682         }
5683
5684         j = seed%4;                 seed /= 4;
5685         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5686         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5687         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5688         j = seed%3 + (seed%3 >= j); seed /= 3;
5689         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5690         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5691         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5692         j = seed%3;                 seed /= 3;
5693         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5694         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5695         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5696         j = seed%2 + (seed%2 >= j); seed /= 2;
5697         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5698         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5699         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5700         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5701         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5702         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5703         put(board, exoPieces[0],    0, 0, ANY);
5704         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5705 }
5706
5707 void
5708 InitPosition(redraw)
5709      int redraw;
5710 {
5711     ChessSquare (* pieces)[BOARD_FILES];
5712     int i, j, pawnRow, overrule,
5713     oldx = gameInfo.boardWidth,
5714     oldy = gameInfo.boardHeight,
5715     oldh = gameInfo.holdingsWidth;
5716     static int oldv;
5717
5718     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5719
5720     /* [AS] Initialize pv info list [HGM] and game status */
5721     {
5722         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5723             pvInfoList[i].depth = 0;
5724             boards[i][EP_STATUS] = EP_NONE;
5725             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5726         }
5727
5728         initialRulePlies = 0; /* 50-move counter start */
5729
5730         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5731         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5732     }
5733
5734
5735     /* [HGM] logic here is completely changed. In stead of full positions */
5736     /* the initialized data only consist of the two backranks. The switch */
5737     /* selects which one we will use, which is than copied to the Board   */
5738     /* initialPosition, which for the rest is initialized by Pawns and    */
5739     /* empty squares. This initial position is then copied to boards[0],  */
5740     /* possibly after shuffling, so that it remains available.            */
5741
5742     gameInfo.holdingsWidth = 0; /* default board sizes */
5743     gameInfo.boardWidth    = 8;
5744     gameInfo.boardHeight   = 8;
5745     gameInfo.holdingsSize  = 0;
5746     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5747     for(i=0; i<BOARD_FILES-2; i++)
5748       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5749     initialPosition[EP_STATUS] = EP_NONE;
5750     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5751     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5752          SetCharTable(pieceNickName, appData.pieceNickNames);
5753     else SetCharTable(pieceNickName, "............");
5754     pieces = FIDEArray;
5755
5756     switch (gameInfo.variant) {
5757     case VariantFischeRandom:
5758       shuffleOpenings = TRUE;
5759     default:
5760       break;
5761     case VariantShatranj:
5762       pieces = ShatranjArray;
5763       nrCastlingRights = 0;
5764       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5765       break;
5766     case VariantMakruk:
5767       pieces = makrukArray;
5768       nrCastlingRights = 0;
5769       startedFromSetupPosition = TRUE;
5770       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5771       break;
5772     case VariantTwoKings:
5773       pieces = twoKingsArray;
5774       break;
5775     case VariantGrand:
5776       pieces = GrandArray;
5777       nrCastlingRights = 0;
5778       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5779       gameInfo.boardWidth = 10;
5780       gameInfo.boardHeight = 10;
5781       gameInfo.holdingsSize = 7;
5782       break;
5783     case VariantCapaRandom:
5784       shuffleOpenings = TRUE;
5785     case VariantCapablanca:
5786       pieces = CapablancaArray;
5787       gameInfo.boardWidth = 10;
5788       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5789       break;
5790     case VariantGothic:
5791       pieces = GothicArray;
5792       gameInfo.boardWidth = 10;
5793       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5794       break;
5795     case VariantSChess:
5796       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5797       gameInfo.holdingsSize = 7;
5798       break;
5799     case VariantJanus:
5800       pieces = JanusArray;
5801       gameInfo.boardWidth = 10;
5802       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5803       nrCastlingRights = 6;
5804         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5805         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5806         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5807         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5808         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5809         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5810       break;
5811     case VariantFalcon:
5812       pieces = FalconArray;
5813       gameInfo.boardWidth = 10;
5814       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5815       break;
5816     case VariantXiangqi:
5817       pieces = XiangqiArray;
5818       gameInfo.boardWidth  = 9;
5819       gameInfo.boardHeight = 10;
5820       nrCastlingRights = 0;
5821       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5822       break;
5823     case VariantShogi:
5824       pieces = ShogiArray;
5825       gameInfo.boardWidth  = 9;
5826       gameInfo.boardHeight = 9;
5827       gameInfo.holdingsSize = 7;
5828       nrCastlingRights = 0;
5829       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5830       break;
5831     case VariantCourier:
5832       pieces = CourierArray;
5833       gameInfo.boardWidth  = 12;
5834       nrCastlingRights = 0;
5835       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5836       break;
5837     case VariantKnightmate:
5838       pieces = KnightmateArray;
5839       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5840       break;
5841     case VariantSpartan:
5842       pieces = SpartanArray;
5843       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5844       break;
5845     case VariantFairy:
5846       pieces = fairyArray;
5847       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5848       break;
5849     case VariantGreat:
5850       pieces = GreatArray;
5851       gameInfo.boardWidth = 10;
5852       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5853       gameInfo.holdingsSize = 8;
5854       break;
5855     case VariantSuper:
5856       pieces = FIDEArray;
5857       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5858       gameInfo.holdingsSize = 8;
5859       startedFromSetupPosition = TRUE;
5860       break;
5861     case VariantCrazyhouse:
5862     case VariantBughouse:
5863       pieces = FIDEArray;
5864       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5865       gameInfo.holdingsSize = 5;
5866       break;
5867     case VariantWildCastle:
5868       pieces = FIDEArray;
5869       /* !!?shuffle with kings guaranteed to be on d or e file */
5870       shuffleOpenings = 1;
5871       break;
5872     case VariantNoCastle:
5873       pieces = FIDEArray;
5874       nrCastlingRights = 0;
5875       /* !!?unconstrained back-rank shuffle */
5876       shuffleOpenings = 1;
5877       break;
5878     }
5879
5880     overrule = 0;
5881     if(appData.NrFiles >= 0) {
5882         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5883         gameInfo.boardWidth = appData.NrFiles;
5884     }
5885     if(appData.NrRanks >= 0) {
5886         gameInfo.boardHeight = appData.NrRanks;
5887     }
5888     if(appData.holdingsSize >= 0) {
5889         i = appData.holdingsSize;
5890         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5891         gameInfo.holdingsSize = i;
5892     }
5893     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5894     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5895         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5896
5897     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5898     if(pawnRow < 1) pawnRow = 1;
5899     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5900
5901     /* User pieceToChar list overrules defaults */
5902     if(appData.pieceToCharTable != NULL)
5903         SetCharTable(pieceToChar, appData.pieceToCharTable);
5904
5905     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5906
5907         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5908             s = (ChessSquare) 0; /* account holding counts in guard band */
5909         for( i=0; i<BOARD_HEIGHT; i++ )
5910             initialPosition[i][j] = s;
5911
5912         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5913         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5914         initialPosition[pawnRow][j] = WhitePawn;
5915         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5916         if(gameInfo.variant == VariantXiangqi) {
5917             if(j&1) {
5918                 initialPosition[pawnRow][j] =
5919                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5920                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5921                    initialPosition[2][j] = WhiteCannon;
5922                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5923                 }
5924             }
5925         }
5926         if(gameInfo.variant == VariantGrand) {
5927             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5928                initialPosition[0][j] = WhiteRook;
5929                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5930             }
5931         }
5932         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5933     }
5934     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5935
5936             j=BOARD_LEFT+1;
5937             initialPosition[1][j] = WhiteBishop;
5938             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5939             j=BOARD_RGHT-2;
5940             initialPosition[1][j] = WhiteRook;
5941             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5942     }
5943
5944     if( nrCastlingRights == -1) {
5945         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5946         /*       This sets default castling rights from none to normal corners   */
5947         /* Variants with other castling rights must set them themselves above    */
5948         nrCastlingRights = 6;
5949
5950         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5951         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5952         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5953         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5954         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5955         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5956      }
5957
5958      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5959      if(gameInfo.variant == VariantGreat) { // promotion commoners
5960         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5961         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5962         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5963         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5964      }
5965      if( gameInfo.variant == VariantSChess ) {
5966       initialPosition[1][0] = BlackMarshall;
5967       initialPosition[2][0] = BlackAngel;
5968       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5969       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5970       initialPosition[1][1] = initialPosition[2][1] = 
5971       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5972      }
5973   if (appData.debugMode) {
5974     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5975   }
5976     if(shuffleOpenings) {
5977         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5978         startedFromSetupPosition = TRUE;
5979     }
5980     if(startedFromPositionFile) {
5981       /* [HGM] loadPos: use PositionFile for every new game */
5982       CopyBoard(initialPosition, filePosition);
5983       for(i=0; i<nrCastlingRights; i++)
5984           initialRights[i] = filePosition[CASTLING][i];
5985       startedFromSetupPosition = TRUE;
5986     }
5987
5988     CopyBoard(boards[0], initialPosition);
5989
5990     if(oldx != gameInfo.boardWidth ||
5991        oldy != gameInfo.boardHeight ||
5992        oldv != gameInfo.variant ||
5993        oldh != gameInfo.holdingsWidth
5994                                          )
5995             InitDrawingSizes(-2 ,0);
5996
5997     oldv = gameInfo.variant;
5998     if (redraw)
5999       DrawPosition(TRUE, boards[currentMove]);
6000 }
6001
6002 void
6003 SendBoard(cps, moveNum)
6004      ChessProgramState *cps;
6005      int moveNum;
6006 {
6007     char message[MSG_SIZ];
6008
6009     if (cps->useSetboard) {
6010       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6011       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6012       SendToProgram(message, cps);
6013       free(fen);
6014
6015     } else {
6016       ChessSquare *bp;
6017       int i, j;
6018       /* Kludge to set black to move, avoiding the troublesome and now
6019        * deprecated "black" command.
6020        */
6021       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6022         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6023
6024       SendToProgram("edit\n", cps);
6025       SendToProgram("#\n", cps);
6026       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6027         bp = &boards[moveNum][i][BOARD_LEFT];
6028         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6029           if ((int) *bp < (int) BlackPawn) {
6030             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6031                     AAA + j, ONE + i);
6032             if(message[0] == '+' || message[0] == '~') {
6033               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6034                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6035                         AAA + j, ONE + i);
6036             }
6037             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6038                 message[1] = BOARD_RGHT   - 1 - j + '1';
6039                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6040             }
6041             SendToProgram(message, cps);
6042           }
6043         }
6044       }
6045
6046       SendToProgram("c\n", cps);
6047       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6048         bp = &boards[moveNum][i][BOARD_LEFT];
6049         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6050           if (((int) *bp != (int) EmptySquare)
6051               && ((int) *bp >= (int) BlackPawn)) {
6052             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6053                     AAA + j, ONE + i);
6054             if(message[0] == '+' || message[0] == '~') {
6055               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6056                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6057                         AAA + j, ONE + i);
6058             }
6059             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6060                 message[1] = BOARD_RGHT   - 1 - j + '1';
6061                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6062             }
6063             SendToProgram(message, cps);
6064           }
6065         }
6066       }
6067
6068       SendToProgram(".\n", cps);
6069     }
6070     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6071 }
6072
6073 ChessSquare
6074 DefaultPromoChoice(int white)
6075 {
6076     ChessSquare result;
6077     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6078         result = WhiteFerz; // no choice
6079     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6080         result= WhiteKing; // in Suicide Q is the last thing we want
6081     else if(gameInfo.variant == VariantSpartan)
6082         result = white ? WhiteQueen : WhiteAngel;
6083     else result = WhiteQueen;
6084     if(!white) result = WHITE_TO_BLACK result;
6085     return result;
6086 }
6087
6088 static int autoQueen; // [HGM] oneclick
6089
6090 int
6091 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6092 {
6093     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6094     /* [HGM] add Shogi promotions */
6095     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6096     ChessSquare piece;
6097     ChessMove moveType;
6098     Boolean premove;
6099
6100     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6101     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6102
6103     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6104       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6105         return FALSE;
6106
6107     piece = boards[currentMove][fromY][fromX];
6108     if(gameInfo.variant == VariantShogi) {
6109         promotionZoneSize = BOARD_HEIGHT/3;
6110         highestPromotingPiece = (int)WhiteFerz;
6111     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6112         promotionZoneSize = 3;
6113     }
6114
6115     // Treat Lance as Pawn when it is not representing Amazon
6116     if(gameInfo.variant != VariantSuper) {
6117         if(piece == WhiteLance) piece = WhitePawn; else
6118         if(piece == BlackLance) piece = BlackPawn;
6119     }
6120
6121     // next weed out all moves that do not touch the promotion zone at all
6122     if((int)piece >= BlackPawn) {
6123         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6124              return FALSE;
6125         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6126     } else {
6127         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6128            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6129     }
6130
6131     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6132
6133     // weed out mandatory Shogi promotions
6134     if(gameInfo.variant == VariantShogi) {
6135         if(piece >= BlackPawn) {
6136             if(toY == 0 && piece == BlackPawn ||
6137                toY == 0 && piece == BlackQueen ||
6138                toY <= 1 && piece == BlackKnight) {
6139                 *promoChoice = '+';
6140                 return FALSE;
6141             }
6142         } else {
6143             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6144                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6145                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6146                 *promoChoice = '+';
6147                 return FALSE;
6148             }
6149         }
6150     }
6151
6152     // weed out obviously illegal Pawn moves
6153     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6154         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6155         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6156         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6157         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6158         // note we are not allowed to test for valid (non-)capture, due to premove
6159     }
6160
6161     // we either have a choice what to promote to, or (in Shogi) whether to promote
6162     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6163         *promoChoice = PieceToChar(BlackFerz);  // no choice
6164         return FALSE;
6165     }
6166     // no sense asking what we must promote to if it is going to explode...
6167     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6168         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6169         return FALSE;
6170     }
6171     // give caller the default choice even if we will not make it
6172     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6173     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6174     if(        sweepSelect && gameInfo.variant != VariantGreat
6175                            && gameInfo.variant != VariantGrand
6176                            && gameInfo.variant != VariantSuper) return FALSE;
6177     if(autoQueen) return FALSE; // predetermined
6178
6179     // suppress promotion popup on illegal moves that are not premoves
6180     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6181               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6182     if(appData.testLegality && !premove) {
6183         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6184                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6185         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6186             return FALSE;
6187     }
6188
6189     return TRUE;
6190 }
6191
6192 int
6193 InPalace(row, column)
6194      int row, column;
6195 {   /* [HGM] for Xiangqi */
6196     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6197          column < (BOARD_WIDTH + 4)/2 &&
6198          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6199     return FALSE;
6200 }
6201
6202 int
6203 PieceForSquare (x, y)
6204      int x;
6205      int y;
6206 {
6207   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6208      return -1;
6209   else
6210      return boards[currentMove][y][x];
6211 }
6212
6213 int
6214 OKToStartUserMove(x, y)
6215      int x, y;
6216 {
6217     ChessSquare from_piece;
6218     int white_piece;
6219
6220     if (matchMode) return FALSE;
6221     if (gameMode == EditPosition) return TRUE;
6222
6223     if (x >= 0 && y >= 0)
6224       from_piece = boards[currentMove][y][x];
6225     else
6226       from_piece = EmptySquare;
6227
6228     if (from_piece == EmptySquare) return FALSE;
6229
6230     white_piece = (int)from_piece >= (int)WhitePawn &&
6231       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6232
6233     switch (gameMode) {
6234       case AnalyzeFile:
6235       case TwoMachinesPlay:
6236       case EndOfGame:
6237         return FALSE;
6238
6239       case IcsObserving:
6240       case IcsIdle:
6241         return FALSE;
6242
6243       case MachinePlaysWhite:
6244       case IcsPlayingBlack:
6245         if (appData.zippyPlay) return FALSE;
6246         if (white_piece) {
6247             DisplayMoveError(_("You are playing Black"));
6248             return FALSE;
6249         }
6250         break;
6251
6252       case MachinePlaysBlack:
6253       case IcsPlayingWhite:
6254         if (appData.zippyPlay) return FALSE;
6255         if (!white_piece) {
6256             DisplayMoveError(_("You are playing White"));
6257             return FALSE;
6258         }
6259         break;
6260
6261       case PlayFromGameFile:
6262             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6263       case EditGame:
6264         if (!white_piece && WhiteOnMove(currentMove)) {
6265             DisplayMoveError(_("It is White's turn"));
6266             return FALSE;
6267         }
6268         if (white_piece && !WhiteOnMove(currentMove)) {
6269             DisplayMoveError(_("It is Black's turn"));
6270             return FALSE;
6271         }
6272         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6273             /* Editing correspondence game history */
6274             /* Could disallow this or prompt for confirmation */
6275             cmailOldMove = -1;
6276         }
6277         break;
6278
6279       case BeginningOfGame:
6280         if (appData.icsActive) return FALSE;
6281         if (!appData.noChessProgram) {
6282             if (!white_piece) {
6283                 DisplayMoveError(_("You are playing White"));
6284                 return FALSE;
6285             }
6286         }
6287         break;
6288
6289       case Training:
6290         if (!white_piece && WhiteOnMove(currentMove)) {
6291             DisplayMoveError(_("It is White's turn"));
6292             return FALSE;
6293         }
6294         if (white_piece && !WhiteOnMove(currentMove)) {
6295             DisplayMoveError(_("It is Black's turn"));
6296             return FALSE;
6297         }
6298         break;
6299
6300       default:
6301       case IcsExamining:
6302         break;
6303     }
6304     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6305         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6306         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6307         && gameMode != AnalyzeFile && gameMode != Training) {
6308         DisplayMoveError(_("Displayed position is not current"));
6309         return FALSE;
6310     }
6311     return TRUE;
6312 }
6313
6314 Boolean
6315 OnlyMove(int *x, int *y, Boolean captures) {
6316     DisambiguateClosure cl;
6317     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6318     switch(gameMode) {
6319       case MachinePlaysBlack:
6320       case IcsPlayingWhite:
6321       case BeginningOfGame:
6322         if(!WhiteOnMove(currentMove)) return FALSE;
6323         break;
6324       case MachinePlaysWhite:
6325       case IcsPlayingBlack:
6326         if(WhiteOnMove(currentMove)) return FALSE;
6327         break;
6328       case EditGame:
6329         break;
6330       default:
6331         return FALSE;
6332     }
6333     cl.pieceIn = EmptySquare;
6334     cl.rfIn = *y;
6335     cl.ffIn = *x;
6336     cl.rtIn = -1;
6337     cl.ftIn = -1;
6338     cl.promoCharIn = NULLCHAR;
6339     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6340     if( cl.kind == NormalMove ||
6341         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6342         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6343         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6344       fromX = cl.ff;
6345       fromY = cl.rf;
6346       *x = cl.ft;
6347       *y = cl.rt;
6348       return TRUE;
6349     }
6350     if(cl.kind != ImpossibleMove) return FALSE;
6351     cl.pieceIn = EmptySquare;
6352     cl.rfIn = -1;
6353     cl.ffIn = -1;
6354     cl.rtIn = *y;
6355     cl.ftIn = *x;
6356     cl.promoCharIn = NULLCHAR;
6357     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6358     if( cl.kind == NormalMove ||
6359         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6360         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6361         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6362       fromX = cl.ff;
6363       fromY = cl.rf;
6364       *x = cl.ft;
6365       *y = cl.rt;
6366       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6367       return TRUE;
6368     }
6369     return FALSE;
6370 }
6371
6372 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6373 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6374 int lastLoadGameUseList = FALSE;
6375 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6376 ChessMove lastLoadGameStart = EndOfFile;
6377
6378 void
6379 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6380      int fromX, fromY, toX, toY;
6381      int promoChar;
6382 {
6383     ChessMove moveType;
6384     ChessSquare pdown, pup;
6385
6386     /* Check if the user is playing in turn.  This is complicated because we
6387        let the user "pick up" a piece before it is his turn.  So the piece he
6388        tried to pick up may have been captured by the time he puts it down!
6389        Therefore we use the color the user is supposed to be playing in this
6390        test, not the color of the piece that is currently on the starting
6391        square---except in EditGame mode, where the user is playing both
6392        sides; fortunately there the capture race can't happen.  (It can
6393        now happen in IcsExamining mode, but that's just too bad.  The user
6394        will get a somewhat confusing message in that case.)
6395        */
6396
6397     switch (gameMode) {
6398       case AnalyzeFile:
6399       case TwoMachinesPlay:
6400       case EndOfGame:
6401       case IcsObserving:
6402       case IcsIdle:
6403         /* We switched into a game mode where moves are not accepted,
6404            perhaps while the mouse button was down. */
6405         return;
6406
6407       case MachinePlaysWhite:
6408         /* User is moving for Black */
6409         if (WhiteOnMove(currentMove)) {
6410             DisplayMoveError(_("It is White's turn"));
6411             return;
6412         }
6413         break;
6414
6415       case MachinePlaysBlack:
6416         /* User is moving for White */
6417         if (!WhiteOnMove(currentMove)) {
6418             DisplayMoveError(_("It is Black's turn"));
6419             return;
6420         }
6421         break;
6422
6423       case PlayFromGameFile:
6424             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6425       case EditGame:
6426       case IcsExamining:
6427       case BeginningOfGame:
6428       case AnalyzeMode:
6429       case Training:
6430         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6431         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6432             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6433             /* User is moving for Black */
6434             if (WhiteOnMove(currentMove)) {
6435                 DisplayMoveError(_("It is White's turn"));
6436                 return;
6437             }
6438         } else {
6439             /* User is moving for White */
6440             if (!WhiteOnMove(currentMove)) {
6441                 DisplayMoveError(_("It is Black's turn"));
6442                 return;
6443             }
6444         }
6445         break;
6446
6447       case IcsPlayingBlack:
6448         /* User is moving for Black */
6449         if (WhiteOnMove(currentMove)) {
6450             if (!appData.premove) {
6451                 DisplayMoveError(_("It is White's turn"));
6452             } else if (toX >= 0 && toY >= 0) {
6453                 premoveToX = toX;
6454                 premoveToY = toY;
6455                 premoveFromX = fromX;
6456                 premoveFromY = fromY;
6457                 premovePromoChar = promoChar;
6458                 gotPremove = 1;
6459                 if (appData.debugMode)
6460                     fprintf(debugFP, "Got premove: fromX %d,"
6461                             "fromY %d, toX %d, toY %d\n",
6462                             fromX, fromY, toX, toY);
6463             }
6464             return;
6465         }
6466         break;
6467
6468       case IcsPlayingWhite:
6469         /* User is moving for White */
6470         if (!WhiteOnMove(currentMove)) {
6471             if (!appData.premove) {
6472                 DisplayMoveError(_("It is Black's turn"));
6473             } else if (toX >= 0 && toY >= 0) {
6474                 premoveToX = toX;
6475                 premoveToY = toY;
6476                 premoveFromX = fromX;
6477                 premoveFromY = fromY;
6478                 premovePromoChar = promoChar;
6479                 gotPremove = 1;
6480                 if (appData.debugMode)
6481                     fprintf(debugFP, "Got premove: fromX %d,"
6482                             "fromY %d, toX %d, toY %d\n",
6483                             fromX, fromY, toX, toY);
6484             }
6485             return;
6486         }
6487         break;
6488
6489       default:
6490         break;
6491
6492       case EditPosition:
6493         /* EditPosition, empty square, or different color piece;
6494            click-click move is possible */
6495         if (toX == -2 || toY == -2) {
6496             boards[0][fromY][fromX] = EmptySquare;
6497             DrawPosition(FALSE, boards[currentMove]);
6498             return;
6499         } else if (toX >= 0 && toY >= 0) {
6500             boards[0][toY][toX] = boards[0][fromY][fromX];
6501             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6502                 if(boards[0][fromY][0] != EmptySquare) {
6503                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6504                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6505                 }
6506             } else
6507             if(fromX == BOARD_RGHT+1) {
6508                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6509                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6510                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6511                 }
6512             } else
6513             boards[0][fromY][fromX] = EmptySquare;
6514             DrawPosition(FALSE, boards[currentMove]);
6515             return;
6516         }
6517         return;
6518     }
6519
6520     if(toX < 0 || toY < 0) return;
6521     pdown = boards[currentMove][fromY][fromX];
6522     pup = boards[currentMove][toY][toX];
6523
6524     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6525     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6526          if( pup != EmptySquare ) return;
6527          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6528            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6529                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6530            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6531            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6532            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6533            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6534          fromY = DROP_RANK;
6535     }
6536
6537     /* [HGM] always test for legality, to get promotion info */
6538     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6539                                          fromY, fromX, toY, toX, promoChar);
6540
6541     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6542
6543     /* [HGM] but possibly ignore an IllegalMove result */
6544     if (appData.testLegality) {
6545         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6546             DisplayMoveError(_("Illegal move"));
6547             return;
6548         }
6549     }
6550
6551     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6552 }
6553
6554 /* Common tail of UserMoveEvent and DropMenuEvent */
6555 int
6556 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6557      ChessMove moveType;
6558      int fromX, fromY, toX, toY;
6559      /*char*/int promoChar;
6560 {
6561     char *bookHit = 0;
6562
6563     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6564         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6565         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6566         if(WhiteOnMove(currentMove)) {
6567             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6568         } else {
6569             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6570         }
6571     }
6572
6573     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6574        move type in caller when we know the move is a legal promotion */
6575     if(moveType == NormalMove && promoChar)
6576         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6577
6578     /* [HGM] <popupFix> The following if has been moved here from
6579        UserMoveEvent(). Because it seemed to belong here (why not allow
6580        piece drops in training games?), and because it can only be
6581        performed after it is known to what we promote. */
6582     if (gameMode == Training) {
6583       /* compare the move played on the board to the next move in the
6584        * game. If they match, display the move and the opponent's response.
6585        * If they don't match, display an error message.
6586        */
6587       int saveAnimate;
6588       Board testBoard;
6589       CopyBoard(testBoard, boards[currentMove]);
6590       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6591
6592       if (CompareBoards(testBoard, boards[currentMove+1])) {
6593         ForwardInner(currentMove+1);
6594
6595         /* Autoplay the opponent's response.
6596          * if appData.animate was TRUE when Training mode was entered,
6597          * the response will be animated.
6598          */
6599         saveAnimate = appData.animate;
6600         appData.animate = animateTraining;
6601         ForwardInner(currentMove+1);
6602         appData.animate = saveAnimate;
6603
6604         /* check for the end of the game */
6605         if (currentMove >= forwardMostMove) {
6606           gameMode = PlayFromGameFile;
6607           ModeHighlight();
6608           SetTrainingModeOff();
6609           DisplayInformation(_("End of game"));
6610         }
6611       } else {
6612         DisplayError(_("Incorrect move"), 0);
6613       }
6614       return 1;
6615     }
6616
6617   /* Ok, now we know that the move is good, so we can kill
6618      the previous line in Analysis Mode */
6619   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6620                                 && currentMove < forwardMostMove) {
6621     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6622     else forwardMostMove = currentMove;
6623   }
6624
6625   /* If we need the chess program but it's dead, restart it */
6626   ResurrectChessProgram();
6627
6628   /* A user move restarts a paused game*/
6629   if (pausing)
6630     PauseEvent();
6631
6632   thinkOutput[0] = NULLCHAR;
6633
6634   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6635
6636   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6637     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6638     return 1;
6639   }
6640
6641   if (gameMode == BeginningOfGame) {
6642     if (appData.noChessProgram) {
6643       gameMode = EditGame;
6644       SetGameInfo();
6645     } else {
6646       char buf[MSG_SIZ];
6647       gameMode = MachinePlaysBlack;
6648       StartClocks();
6649       SetGameInfo();
6650       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6651       DisplayTitle(buf);
6652       if (first.sendName) {
6653         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6654         SendToProgram(buf, &first);
6655       }
6656       StartClocks();
6657     }
6658     ModeHighlight();
6659   }
6660
6661   /* Relay move to ICS or chess engine */
6662   if (appData.icsActive) {
6663     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6664         gameMode == IcsExamining) {
6665       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6666         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6667         SendToICS("draw ");
6668         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6669       }
6670       // also send plain move, in case ICS does not understand atomic claims
6671       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6672       ics_user_moved = 1;
6673     }
6674   } else {
6675     if (first.sendTime && (gameMode == BeginningOfGame ||
6676                            gameMode == MachinePlaysWhite ||
6677                            gameMode == MachinePlaysBlack)) {
6678       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6679     }
6680     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6681          // [HGM] book: if program might be playing, let it use book
6682         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6683         first.maybeThinking = TRUE;
6684     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6685         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6686         SendBoard(&first, currentMove+1);
6687     } else SendMoveToProgram(forwardMostMove-1, &first);
6688     if (currentMove == cmailOldMove + 1) {
6689       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6690     }
6691   }
6692
6693   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6694
6695   switch (gameMode) {
6696   case EditGame:
6697     if(appData.testLegality)
6698     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6699     case MT_NONE:
6700     case MT_CHECK:
6701       break;
6702     case MT_CHECKMATE:
6703     case MT_STAINMATE:
6704       if (WhiteOnMove(currentMove)) {
6705         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6706       } else {
6707         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6708       }
6709       break;
6710     case MT_STALEMATE:
6711       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6712       break;
6713     }
6714     break;
6715
6716   case MachinePlaysBlack:
6717   case MachinePlaysWhite:
6718     /* disable certain menu options while machine is thinking */
6719     SetMachineThinkingEnables();
6720     break;
6721
6722   default:
6723     break;
6724   }
6725
6726   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6727   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6728
6729   if(bookHit) { // [HGM] book: simulate book reply
6730         static char bookMove[MSG_SIZ]; // a bit generous?
6731
6732         programStats.nodes = programStats.depth = programStats.time =
6733         programStats.score = programStats.got_only_move = 0;
6734         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6735
6736         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6737         strcat(bookMove, bookHit);
6738         HandleMachineMove(bookMove, &first);
6739   }
6740   return 1;
6741 }
6742
6743 void
6744 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6745      Board board;
6746      int flags;
6747      ChessMove kind;
6748      int rf, ff, rt, ft;
6749      VOIDSTAR closure;
6750 {
6751     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6752     Markers *m = (Markers *) closure;
6753     if(rf == fromY && ff == fromX)
6754         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6755                          || kind == WhiteCapturesEnPassant
6756                          || kind == BlackCapturesEnPassant);
6757     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6758 }
6759
6760 void
6761 MarkTargetSquares(int clear)
6762 {
6763   int x, y;
6764   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6765      !appData.testLegality || gameMode == EditPosition) return;
6766   if(clear) {
6767     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6768   } else {
6769     int capt = 0;
6770     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6771     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6772       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6773       if(capt)
6774       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6775     }
6776   }
6777   DrawPosition(TRUE, NULL);
6778 }
6779
6780 int
6781 Explode(Board board, int fromX, int fromY, int toX, int toY)
6782 {
6783     if(gameInfo.variant == VariantAtomic &&
6784        (board[toY][toX] != EmptySquare ||                     // capture?
6785         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6786                          board[fromY][fromX] == BlackPawn   )
6787       )) {
6788         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6789         return TRUE;
6790     }
6791     return FALSE;
6792 }
6793
6794 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6795
6796 int CanPromote(ChessSquare piece, int y)
6797 {
6798         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6799         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6800         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6801            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6802            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6803                                                   gameInfo.variant == VariantMakruk) return FALSE;
6804         return (piece == BlackPawn && y == 1 ||
6805                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6806                 piece == BlackLance && y == 1 ||
6807                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6808 }
6809
6810 void LeftClick(ClickType clickType, int xPix, int yPix)
6811 {
6812     int x, y;
6813     Boolean saveAnimate;
6814     static int second = 0, promotionChoice = 0, clearFlag = 0;
6815     char promoChoice = NULLCHAR;
6816     ChessSquare piece;
6817
6818     if(appData.seekGraph && appData.icsActive && loggedOn &&
6819         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6820         SeekGraphClick(clickType, xPix, yPix, 0);
6821         return;
6822     }
6823
6824     if (clickType == Press) ErrorPopDown();
6825
6826     x = EventToSquare(xPix, BOARD_WIDTH);
6827     y = EventToSquare(yPix, BOARD_HEIGHT);
6828     if (!flipView && y >= 0) {
6829         y = BOARD_HEIGHT - 1 - y;
6830     }
6831     if (flipView && x >= 0) {
6832         x = BOARD_WIDTH - 1 - x;
6833     }
6834
6835     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6836         defaultPromoChoice = promoSweep;
6837         promoSweep = EmptySquare;   // terminate sweep
6838         promoDefaultAltered = TRUE;
6839         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6840     }
6841
6842     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6843         if(clickType == Release) return; // ignore upclick of click-click destination
6844         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6845         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6846         if(gameInfo.holdingsWidth &&
6847                 (WhiteOnMove(currentMove)
6848                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6849                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6850             // click in right holdings, for determining promotion piece
6851             ChessSquare p = boards[currentMove][y][x];
6852             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6853             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6854             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6855                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6856                 fromX = fromY = -1;
6857                 return;
6858             }
6859         }
6860         DrawPosition(FALSE, boards[currentMove]);
6861         return;
6862     }
6863
6864     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6865     if(clickType == Press
6866             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6867               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6868               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6869         return;
6870
6871     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6872         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6873
6874     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6875         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6876                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6877         defaultPromoChoice = DefaultPromoChoice(side);
6878     }
6879
6880     autoQueen = appData.alwaysPromoteToQueen;
6881
6882     if (fromX == -1) {
6883       int originalY = y;
6884       gatingPiece = EmptySquare;
6885       if (clickType != Press) {
6886         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6887             DragPieceEnd(xPix, yPix); dragging = 0;
6888             DrawPosition(FALSE, NULL);
6889         }
6890         return;
6891       }
6892       fromX = x; fromY = y; toX = toY = -1;
6893       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6894          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6895          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6896             /* First square */
6897             if (OKToStartUserMove(fromX, fromY)) {
6898                 second = 0;
6899                 MarkTargetSquares(0);
6900                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6901                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6902                     promoSweep = defaultPromoChoice;
6903                     selectFlag = 0; lastX = xPix; lastY = yPix;
6904                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6905                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6906                 }
6907                 if (appData.highlightDragging) {
6908                     SetHighlights(fromX, fromY, -1, -1);
6909                 }
6910             } else fromX = fromY = -1;
6911             return;
6912         }
6913     }
6914
6915     /* fromX != -1 */
6916     if (clickType == Press && gameMode != EditPosition) {
6917         ChessSquare fromP;
6918         ChessSquare toP;
6919         int frc;
6920
6921         // ignore off-board to clicks
6922         if(y < 0 || x < 0) return;
6923
6924         /* Check if clicking again on the same color piece */
6925         fromP = boards[currentMove][fromY][fromX];
6926         toP = boards[currentMove][y][x];
6927         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6928         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6929              WhitePawn <= toP && toP <= WhiteKing &&
6930              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6931              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6932             (BlackPawn <= fromP && fromP <= BlackKing &&
6933              BlackPawn <= toP && toP <= BlackKing &&
6934              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6935              !(fromP == BlackKing && toP == BlackRook && frc))) {
6936             /* Clicked again on same color piece -- changed his mind */
6937             second = (x == fromX && y == fromY);
6938             promoDefaultAltered = FALSE;
6939             MarkTargetSquares(1);
6940            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6941             if (appData.highlightDragging) {
6942                 SetHighlights(x, y, -1, -1);
6943             } else {
6944                 ClearHighlights();
6945             }
6946             if (OKToStartUserMove(x, y)) {
6947                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6948                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6949                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6950                  gatingPiece = boards[currentMove][fromY][fromX];
6951                 else gatingPiece = EmptySquare;
6952                 fromX = x;
6953                 fromY = y; dragging = 1;
6954                 MarkTargetSquares(0);
6955                 DragPieceBegin(xPix, yPix, FALSE);
6956                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6957                     promoSweep = defaultPromoChoice;
6958                     selectFlag = 0; lastX = xPix; lastY = yPix;
6959                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6960                 }
6961             }
6962            }
6963            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6964            second = FALSE; 
6965         }
6966         // ignore clicks on holdings
6967         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6968     }
6969
6970     if (clickType == Release && x == fromX && y == fromY) {
6971         DragPieceEnd(xPix, yPix); dragging = 0;
6972         if(clearFlag) {
6973             // a deferred attempt to click-click move an empty square on top of a piece
6974             boards[currentMove][y][x] = EmptySquare;
6975             ClearHighlights();
6976             DrawPosition(FALSE, boards[currentMove]);
6977             fromX = fromY = -1; clearFlag = 0;
6978             return;
6979         }
6980         if (appData.animateDragging) {
6981             /* Undo animation damage if any */
6982             DrawPosition(FALSE, NULL);
6983         }
6984         if (second) {
6985             /* Second up/down in same square; just abort move */
6986             second = 0;
6987             fromX = fromY = -1;
6988             gatingPiece = EmptySquare;
6989             ClearHighlights();
6990             gotPremove = 0;
6991             ClearPremoveHighlights();
6992         } else {
6993             /* First upclick in same square; start click-click mode */
6994             SetHighlights(x, y, -1, -1);
6995         }
6996         return;
6997     }
6998
6999     clearFlag = 0;
7000
7001     /* we now have a different from- and (possibly off-board) to-square */
7002     /* Completed move */
7003     toX = x;
7004     toY = y;
7005     saveAnimate = appData.animate;
7006     MarkTargetSquares(1);
7007     if (clickType == Press) {
7008         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7009             // must be Edit Position mode with empty-square selected
7010             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7011             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7012             return;
7013         }
7014         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7015             ChessSquare piece = boards[currentMove][fromY][fromX];
7016             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7017             promoSweep = defaultPromoChoice;
7018             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7019             selectFlag = 0; lastX = xPix; lastY = yPix;
7020             Sweep(0); // Pawn that is going to promote: preview promotion piece
7021             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7022             DrawPosition(FALSE, boards[currentMove]);
7023             return;
7024         }
7025         /* Finish clickclick move */
7026         if (appData.animate || appData.highlightLastMove) {
7027             SetHighlights(fromX, fromY, toX, toY);
7028         } else {
7029             ClearHighlights();
7030         }
7031     } else {
7032         /* Finish drag move */
7033         if (appData.highlightLastMove) {
7034             SetHighlights(fromX, fromY, toX, toY);
7035         } else {
7036             ClearHighlights();
7037         }
7038         DragPieceEnd(xPix, yPix); dragging = 0;
7039         /* Don't animate move and drag both */
7040         appData.animate = FALSE;
7041     }
7042
7043     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7044     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7045         ChessSquare piece = boards[currentMove][fromY][fromX];
7046         if(gameMode == EditPosition && piece != EmptySquare &&
7047            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7048             int n;
7049
7050             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7051                 n = PieceToNumber(piece - (int)BlackPawn);
7052                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7053                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7054                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7055             } else
7056             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7057                 n = PieceToNumber(piece);
7058                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7059                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7060                 boards[currentMove][n][BOARD_WIDTH-2]++;
7061             }
7062             boards[currentMove][fromY][fromX] = EmptySquare;
7063         }
7064         ClearHighlights();
7065         fromX = fromY = -1;
7066         DrawPosition(TRUE, boards[currentMove]);
7067         return;
7068     }
7069
7070     // off-board moves should not be highlighted
7071     if(x < 0 || y < 0) ClearHighlights();
7072
7073     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7074
7075     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7076         SetHighlights(fromX, fromY, toX, toY);
7077         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7078             // [HGM] super: promotion to captured piece selected from holdings
7079             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7080             promotionChoice = TRUE;
7081             // kludge follows to temporarily execute move on display, without promoting yet
7082             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7083             boards[currentMove][toY][toX] = p;
7084             DrawPosition(FALSE, boards[currentMove]);
7085             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7086             boards[currentMove][toY][toX] = q;
7087             DisplayMessage("Click in holdings to choose piece", "");
7088             return;
7089         }
7090         PromotionPopUp();
7091     } else {
7092         int oldMove = currentMove;
7093         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7094         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7095         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7096         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7097            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7098             DrawPosition(TRUE, boards[currentMove]);
7099         fromX = fromY = -1;
7100     }
7101     appData.animate = saveAnimate;
7102     if (appData.animate || appData.animateDragging) {
7103         /* Undo animation damage if needed */
7104         DrawPosition(FALSE, NULL);
7105     }
7106 }
7107
7108 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7109 {   // front-end-free part taken out of PieceMenuPopup
7110     int whichMenu; int xSqr, ySqr;
7111
7112     if(seekGraphUp) { // [HGM] seekgraph
7113         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7114         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7115         return -2;
7116     }
7117
7118     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7119          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7120         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7121         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7122         if(action == Press)   {
7123             originalFlip = flipView;
7124             flipView = !flipView; // temporarily flip board to see game from partners perspective
7125             DrawPosition(TRUE, partnerBoard);
7126             DisplayMessage(partnerStatus, "");
7127             partnerUp = TRUE;
7128         } else if(action == Release) {
7129             flipView = originalFlip;
7130             DrawPosition(TRUE, boards[currentMove]);
7131             partnerUp = FALSE;
7132         }
7133         return -2;
7134     }
7135
7136     xSqr = EventToSquare(x, BOARD_WIDTH);
7137     ySqr = EventToSquare(y, BOARD_HEIGHT);
7138     if (action == Release) {
7139         if(pieceSweep != EmptySquare) {
7140             EditPositionMenuEvent(pieceSweep, toX, toY);
7141             pieceSweep = EmptySquare;
7142         } else UnLoadPV(); // [HGM] pv
7143     }
7144     if (action != Press) return -2; // return code to be ignored
7145     switch (gameMode) {
7146       case IcsExamining:
7147         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7148       case EditPosition:
7149         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7150         if (xSqr < 0 || ySqr < 0) return -1;
7151         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7152         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7153         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7154         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7155         NextPiece(0);
7156         return 2; // grab
7157       case IcsObserving:
7158         if(!appData.icsEngineAnalyze) return -1;
7159       case IcsPlayingWhite:
7160       case IcsPlayingBlack:
7161         if(!appData.zippyPlay) goto noZip;
7162       case AnalyzeMode:
7163       case AnalyzeFile:
7164       case MachinePlaysWhite:
7165       case MachinePlaysBlack:
7166       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7167         if (!appData.dropMenu) {
7168           LoadPV(x, y);
7169           return 2; // flag front-end to grab mouse events
7170         }
7171         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7172            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7173       case EditGame:
7174       noZip:
7175         if (xSqr < 0 || ySqr < 0) return -1;
7176         if (!appData.dropMenu || appData.testLegality &&
7177             gameInfo.variant != VariantBughouse &&
7178             gameInfo.variant != VariantCrazyhouse) return -1;
7179         whichMenu = 1; // drop menu
7180         break;
7181       default:
7182         return -1;
7183     }
7184
7185     if (((*fromX = xSqr) < 0) ||
7186         ((*fromY = ySqr) < 0)) {
7187         *fromX = *fromY = -1;
7188         return -1;
7189     }
7190     if (flipView)
7191       *fromX = BOARD_WIDTH - 1 - *fromX;
7192     else
7193       *fromY = BOARD_HEIGHT - 1 - *fromY;
7194
7195     return whichMenu;
7196 }
7197
7198 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7199 {
7200 //    char * hint = lastHint;
7201     FrontEndProgramStats stats;
7202
7203     stats.which = cps == &first ? 0 : 1;
7204     stats.depth = cpstats->depth;
7205     stats.nodes = cpstats->nodes;
7206     stats.score = cpstats->score;
7207     stats.time = cpstats->time;
7208     stats.pv = cpstats->movelist;
7209     stats.hint = lastHint;
7210     stats.an_move_index = 0;
7211     stats.an_move_count = 0;
7212
7213     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7214         stats.hint = cpstats->move_name;
7215         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7216         stats.an_move_count = cpstats->nr_moves;
7217     }
7218
7219     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
7220
7221     SetProgramStats( &stats );
7222 }
7223
7224 void
7225 ClearEngineOutputPane(int which)
7226 {
7227     static FrontEndProgramStats dummyStats;
7228     dummyStats.which = which;
7229     dummyStats.pv = "#";
7230     SetProgramStats( &dummyStats );
7231 }
7232
7233 #define MAXPLAYERS 500
7234
7235 char *
7236 TourneyStandings(int display)
7237 {
7238     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7239     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7240     char result, *p, *names[MAXPLAYERS];
7241
7242     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7243         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7244     names[0] = p = strdup(appData.participants);
7245     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7246
7247     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7248
7249     while(result = appData.results[nr]) {
7250         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7251         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7252         wScore = bScore = 0;
7253         switch(result) {
7254           case '+': wScore = 2; break;
7255           case '-': bScore = 2; break;
7256           case '=': wScore = bScore = 1; break;
7257           case ' ':
7258           case '*': return strdup("busy"); // tourney not finished
7259         }
7260         score[w] += wScore;
7261         score[b] += bScore;
7262         games[w]++;
7263         games[b]++;
7264         nr++;
7265     }
7266     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7267     for(w=0; w<nPlayers; w++) {
7268         bScore = -1;
7269         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7270         ranking[w] = b; points[w] = bScore; score[b] = -2;
7271     }
7272     p = malloc(nPlayers*34+1);
7273     for(w=0; w<nPlayers && w<display; w++)
7274         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7275     free(names[0]);
7276     return p;
7277 }
7278
7279 void
7280 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7281 {       // count all piece types
7282         int p, f, r;
7283         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7284         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7285         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7286                 p = board[r][f];
7287                 pCnt[p]++;
7288                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7289                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7290                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7291                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7292                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7293                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7294         }
7295 }
7296
7297 int
7298 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7299 {
7300         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7301         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7302
7303         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7304         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7305         if(myPawns == 2 && nMine == 3) // KPP
7306             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7307         if(myPawns == 1 && nMine == 2) // KP
7308             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7309         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7310             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7311         if(myPawns) return FALSE;
7312         if(pCnt[WhiteRook+side])
7313             return pCnt[BlackRook-side] ||
7314                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7315                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7316                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7317         if(pCnt[WhiteCannon+side]) {
7318             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7319             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7320         }
7321         if(pCnt[WhiteKnight+side])
7322             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7323         return FALSE;
7324 }
7325
7326 int
7327 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7328 {
7329         VariantClass v = gameInfo.variant;
7330
7331         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7332         if(v == VariantShatranj) return TRUE; // always winnable through baring
7333         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7334         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7335
7336         if(v == VariantXiangqi) {
7337                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7338
7339                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7340                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7341                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7342                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7343                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7344                 if(stale) // we have at least one last-rank P plus perhaps C
7345                     return majors // KPKX
7346                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7347                 else // KCA*E*
7348                     return pCnt[WhiteFerz+side] // KCAK
7349                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7350                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7351                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7352
7353         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7354                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7355
7356                 if(nMine == 1) return FALSE; // bare King
7357                 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
7358                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7359                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7360                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7361                 if(pCnt[WhiteKnight+side])
7362                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7363                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7364                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7365                 if(nBishops)
7366                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7367                 if(pCnt[WhiteAlfil+side])
7368                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7369                 if(pCnt[WhiteWazir+side])
7370                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7371         }
7372
7373         return TRUE;
7374 }
7375
7376 int
7377 CompareWithRights(Board b1, Board b2)
7378 {
7379     int rights = 0;
7380     if(!CompareBoards(b1, b2)) return FALSE;
7381     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7382     /* compare castling rights */
7383     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7384            rights++; /* King lost rights, while rook still had them */
7385     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7386         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7387            rights++; /* but at least one rook lost them */
7388     }
7389     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7390            rights++;
7391     if( b1[CASTLING][5] != NoRights ) {
7392         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7393            rights++;
7394     }
7395     return rights == 0;
7396 }
7397
7398 int
7399 Adjudicate(ChessProgramState *cps)
7400 {       // [HGM] some adjudications useful with buggy engines
7401         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7402         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7403         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7404         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7405         int k, count = 0; static int bare = 1;
7406         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7407         Boolean canAdjudicate = !appData.icsActive;
7408
7409         // most tests only when we understand the game, i.e. legality-checking on
7410             if( appData.testLegality )
7411             {   /* [HGM] Some more adjudications for obstinate engines */
7412                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7413                 static int moveCount = 6;
7414                 ChessMove result;
7415                 char *reason = NULL;
7416
7417                 /* Count what is on board. */
7418                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7419
7420                 /* Some material-based adjudications that have to be made before stalemate test */
7421                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7422                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7423                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7424                      if(canAdjudicate && appData.checkMates) {
7425                          if(engineOpponent)
7426                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7427                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7428                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7429                          return 1;
7430                      }
7431                 }
7432
7433                 /* Bare King in Shatranj (loses) or Losers (wins) */
7434                 if( nrW == 1 || nrB == 1) {
7435                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7436                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7437                      if(canAdjudicate && appData.checkMates) {
7438                          if(engineOpponent)
7439                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7440                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7441                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7442                          return 1;
7443                      }
7444                   } else
7445                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7446                   {    /* bare King */
7447                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7448                         if(canAdjudicate && appData.checkMates) {
7449                             /* but only adjudicate if adjudication enabled */
7450                             if(engineOpponent)
7451                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7452                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7453                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7454                             return 1;
7455                         }
7456                   }
7457                 } else bare = 1;
7458
7459
7460             // don't wait for engine to announce game end if we can judge ourselves
7461             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7462               case MT_CHECK:
7463                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7464                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7465                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7466                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7467                             checkCnt++;
7468                         if(checkCnt >= 2) {
7469                             reason = "Xboard adjudication: 3rd check";
7470                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7471                             break;
7472                         }
7473                     }
7474                 }
7475               case MT_NONE:
7476               default:
7477                 break;
7478               case MT_STALEMATE:
7479               case MT_STAINMATE:
7480                 reason = "Xboard adjudication: Stalemate";
7481                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7482                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7483                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7484                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7485                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7486                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7487                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7488                                                                         EP_CHECKMATE : EP_WINS);
7489                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7490                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7491                 }
7492                 break;
7493               case MT_CHECKMATE:
7494                 reason = "Xboard adjudication: Checkmate";
7495                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7496                 break;
7497             }
7498
7499                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7500                     case EP_STALEMATE:
7501                         result = GameIsDrawn; break;
7502                     case EP_CHECKMATE:
7503                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7504                     case EP_WINS:
7505                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7506                     default:
7507                         result = EndOfFile;
7508                 }
7509                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7510                     if(engineOpponent)
7511                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7512                     GameEnds( result, reason, GE_XBOARD );
7513                     return 1;
7514                 }
7515
7516                 /* Next absolutely insufficient mating material. */
7517                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7518                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7519                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7520
7521                      /* always flag draws, for judging claims */
7522                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7523
7524                      if(canAdjudicate && appData.materialDraws) {
7525                          /* but only adjudicate them if adjudication enabled */
7526                          if(engineOpponent) {
7527                            SendToProgram("force\n", engineOpponent); // suppress reply
7528                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7529                          }
7530                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7531                          return 1;
7532                      }
7533                 }
7534
7535                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7536                 if(gameInfo.variant == VariantXiangqi ?
7537                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7538                  : nrW + nrB == 4 &&
7539                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7540                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7541                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7542                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7543                    ) ) {
7544                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7545                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7546                           if(engineOpponent) {
7547                             SendToProgram("force\n", engineOpponent); // suppress reply
7548                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7549                           }
7550                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7551                           return 1;
7552                      }
7553                 } else moveCount = 6;
7554             }
7555         if (appData.debugMode) { int i;
7556             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7557                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7558                     appData.drawRepeats);
7559             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7560               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7561
7562         }
7563
7564         // Repetition draws and 50-move rule can be applied independently of legality testing
7565
7566                 /* Check for rep-draws */
7567                 count = 0;
7568                 for(k = forwardMostMove-2;
7569                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7570                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7571                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7572                     k-=2)
7573                 {   int rights=0;
7574                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7575                         /* compare castling rights */
7576                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7577                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7578                                 rights++; /* King lost rights, while rook still had them */
7579                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7580                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7581                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7582                                    rights++; /* but at least one rook lost them */
7583                         }
7584                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7585                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7586                                 rights++;
7587                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7588                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7589                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7590                                    rights++;
7591                         }
7592                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7593                             && appData.drawRepeats > 1) {
7594                              /* adjudicate after user-specified nr of repeats */
7595                              int result = GameIsDrawn;
7596                              char *details = "XBoard adjudication: repetition draw";
7597                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7598                                 // [HGM] xiangqi: check for forbidden perpetuals
7599                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7600                                 for(m=forwardMostMove; m>k; m-=2) {
7601                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7602                                         ourPerpetual = 0; // the current mover did not always check
7603                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7604                                         hisPerpetual = 0; // the opponent did not always check
7605                                 }
7606                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7607                                                                         ourPerpetual, hisPerpetual);
7608                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7609                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7610                                     details = "Xboard adjudication: perpetual checking";
7611                                 } else
7612                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7613                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7614                                 } else
7615                                 // Now check for perpetual chases
7616                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7617                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7618                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7619                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7620                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7621                                         details = "Xboard adjudication: perpetual chasing";
7622                                     } else
7623                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7624                                         break; // Abort repetition-checking loop.
7625                                 }
7626                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7627                              }
7628                              if(engineOpponent) {
7629                                SendToProgram("force\n", engineOpponent); // suppress reply
7630                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7631                              }
7632                              GameEnds( result, details, GE_XBOARD );
7633                              return 1;
7634                         }
7635                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7636                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7637                     }
7638                 }
7639
7640                 /* Now we test for 50-move draws. Determine ply count */
7641                 count = forwardMostMove;
7642                 /* look for last irreversble move */
7643                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7644                     count--;
7645                 /* if we hit starting position, add initial plies */
7646                 if( count == backwardMostMove )
7647                     count -= initialRulePlies;
7648                 count = forwardMostMove - count;
7649                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7650                         // adjust reversible move counter for checks in Xiangqi
7651                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7652                         if(i < backwardMostMove) i = backwardMostMove;
7653                         while(i <= forwardMostMove) {
7654                                 lastCheck = inCheck; // check evasion does not count
7655                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7656                                 if(inCheck || lastCheck) count--; // check does not count
7657                                 i++;
7658                         }
7659                 }
7660                 if( count >= 100)
7661                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7662                          /* this is used to judge if draw claims are legal */
7663                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7664                          if(engineOpponent) {
7665                            SendToProgram("force\n", engineOpponent); // suppress reply
7666                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7667                          }
7668                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7669                          return 1;
7670                 }
7671
7672                 /* if draw offer is pending, treat it as a draw claim
7673                  * when draw condition present, to allow engines a way to
7674                  * claim draws before making their move to avoid a race
7675                  * condition occurring after their move
7676                  */
7677                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7678                          char *p = NULL;
7679                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7680                              p = "Draw claim: 50-move rule";
7681                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7682                              p = "Draw claim: 3-fold repetition";
7683                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7684                              p = "Draw claim: insufficient mating material";
7685                          if( p != NULL && canAdjudicate) {
7686                              if(engineOpponent) {
7687                                SendToProgram("force\n", engineOpponent); // suppress reply
7688                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7689                              }
7690                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7691                              return 1;
7692                          }
7693                 }
7694
7695                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7696                     if(engineOpponent) {
7697                       SendToProgram("force\n", engineOpponent); // suppress reply
7698                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7699                     }
7700                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7701                     return 1;
7702                 }
7703         return 0;
7704 }
7705
7706 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7707 {   // [HGM] book: this routine intercepts moves to simulate book replies
7708     char *bookHit = NULL;
7709
7710     //first determine if the incoming move brings opponent into his book
7711     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7712         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7713     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7714     if(bookHit != NULL && !cps->bookSuspend) {
7715         // make sure opponent is not going to reply after receiving move to book position
7716         SendToProgram("force\n", cps);
7717         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7718     }
7719     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7720     // now arrange restart after book miss
7721     if(bookHit) {
7722         // after a book hit we never send 'go', and the code after the call to this routine
7723         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7724         char buf[MSG_SIZ], *move = bookHit;
7725         if(cps->useSAN) {
7726             int fromX, fromY, toX, toY;
7727             char promoChar;
7728             ChessMove moveType;
7729             move = buf + 30;
7730             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7731                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7732                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7733                                     PosFlags(forwardMostMove),
7734                                     fromY, fromX, toY, toX, promoChar, move);
7735             } else {
7736                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7737                 bookHit = NULL;
7738             }
7739         }
7740         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7741         SendToProgram(buf, cps);
7742         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7743     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7744         SendToProgram("go\n", cps);
7745         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7746     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7747         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7748             SendToProgram("go\n", cps);
7749         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7750     }
7751     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7752 }
7753
7754 char *savedMessage;
7755 ChessProgramState *savedState;
7756 void DeferredBookMove(void)
7757 {
7758         if(savedState->lastPing != savedState->lastPong)
7759                     ScheduleDelayedEvent(DeferredBookMove, 10);
7760         else
7761         HandleMachineMove(savedMessage, savedState);
7762 }
7763
7764 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7765
7766 void
7767 HandleMachineMove(message, cps)
7768      char *message;
7769      ChessProgramState *cps;
7770 {
7771     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7772     char realname[MSG_SIZ];
7773     int fromX, fromY, toX, toY;
7774     ChessMove moveType;
7775     char promoChar;
7776     char *p, *pv=buf1;
7777     int machineWhite;
7778     char *bookHit;
7779
7780     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7781         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7782         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7783             DisplayError(_("Invalid pairing from pairing engine"), 0);
7784             return;
7785         }
7786         pairingReceived = 1;
7787         NextMatchGame();
7788         return; // Skim the pairing messages here.
7789     }
7790
7791     cps->userError = 0;
7792
7793 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7794     /*
7795      * Kludge to ignore BEL characters
7796      */
7797     while (*message == '\007') message++;
7798
7799     /*
7800      * [HGM] engine debug message: ignore lines starting with '#' character
7801      */
7802     if(cps->debug && *message == '#') return;
7803
7804     /*
7805      * Look for book output
7806      */
7807     if (cps == &first && bookRequested) {
7808         if (message[0] == '\t' || message[0] == ' ') {
7809             /* Part of the book output is here; append it */
7810             strcat(bookOutput, message);
7811             strcat(bookOutput, "  \n");
7812             return;
7813         } else if (bookOutput[0] != NULLCHAR) {
7814             /* All of book output has arrived; display it */
7815             char *p = bookOutput;
7816             while (*p != NULLCHAR) {
7817                 if (*p == '\t') *p = ' ';
7818                 p++;
7819             }
7820             DisplayInformation(bookOutput);
7821             bookRequested = FALSE;
7822             /* Fall through to parse the current output */
7823         }
7824     }
7825
7826     /*
7827      * Look for machine move.
7828      */
7829     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7830         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7831     {
7832         /* This method is only useful on engines that support ping */
7833         if (cps->lastPing != cps->lastPong) {
7834           if (gameMode == BeginningOfGame) {
7835             /* Extra move from before last new; ignore */
7836             if (appData.debugMode) {
7837                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7838             }
7839           } else {
7840             if (appData.debugMode) {
7841                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7842                         cps->which, gameMode);
7843             }
7844
7845             SendToProgram("undo\n", cps);
7846           }
7847           return;
7848         }
7849
7850         switch (gameMode) {
7851           case BeginningOfGame:
7852             /* Extra move from before last reset; ignore */
7853             if (appData.debugMode) {
7854                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7855             }
7856             return;
7857
7858           case EndOfGame:
7859           case IcsIdle:
7860           default:
7861             /* Extra move after we tried to stop.  The mode test is
7862                not a reliable way of detecting this problem, but it's
7863                the best we can do on engines that don't support ping.
7864             */
7865             if (appData.debugMode) {
7866                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7867                         cps->which, gameMode);
7868             }
7869             SendToProgram("undo\n", cps);
7870             return;
7871
7872           case MachinePlaysWhite:
7873           case IcsPlayingWhite:
7874             machineWhite = TRUE;
7875             break;
7876
7877           case MachinePlaysBlack:
7878           case IcsPlayingBlack:
7879             machineWhite = FALSE;
7880             break;
7881
7882           case TwoMachinesPlay:
7883             machineWhite = (cps->twoMachinesColor[0] == 'w');
7884             break;
7885         }
7886         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7887             if (appData.debugMode) {
7888                 fprintf(debugFP,
7889                         "Ignoring move out of turn by %s, gameMode %d"
7890                         ", forwardMost %d\n",
7891                         cps->which, gameMode, forwardMostMove);
7892             }
7893             return;
7894         }
7895
7896     if (appData.debugMode) { int f = forwardMostMove;
7897         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7898                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7899                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7900     }
7901         if(cps->alphaRank) AlphaRank(machineMove, 4);
7902         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7903                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7904             /* Machine move could not be parsed; ignore it. */
7905           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7906                     machineMove, _(cps->which));
7907             DisplayError(buf1, 0);
7908             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7909                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7910             if (gameMode == TwoMachinesPlay) {
7911               GameEnds(machineWhite ? BlackWins : WhiteWins,
7912                        buf1, GE_XBOARD);
7913             }
7914             return;
7915         }
7916
7917         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7918         /* So we have to redo legality test with true e.p. status here,  */
7919         /* to make sure an illegal e.p. capture does not slip through,   */
7920         /* to cause a forfeit on a justified illegal-move complaint      */
7921         /* of the opponent.                                              */
7922         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7923            ChessMove moveType;
7924            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7925                              fromY, fromX, toY, toX, promoChar);
7926             if (appData.debugMode) {
7927                 int i;
7928                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7929                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7930                 fprintf(debugFP, "castling rights\n");
7931             }
7932             if(moveType == IllegalMove) {
7933               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7934                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7935                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7936                            buf1, GE_XBOARD);
7937                 return;
7938            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7939            /* [HGM] Kludge to handle engines that send FRC-style castling
7940               when they shouldn't (like TSCP-Gothic) */
7941            switch(moveType) {
7942              case WhiteASideCastleFR:
7943              case BlackASideCastleFR:
7944                toX+=2;
7945                currentMoveString[2]++;
7946                break;
7947              case WhiteHSideCastleFR:
7948              case BlackHSideCastleFR:
7949                toX--;
7950                currentMoveString[2]--;
7951                break;
7952              default: ; // nothing to do, but suppresses warning of pedantic compilers
7953            }
7954         }
7955         hintRequested = FALSE;
7956         lastHint[0] = NULLCHAR;
7957         bookRequested = FALSE;
7958         /* Program may be pondering now */
7959         cps->maybeThinking = TRUE;
7960         if (cps->sendTime == 2) cps->sendTime = 1;
7961         if (cps->offeredDraw) cps->offeredDraw--;
7962
7963         /* [AS] Save move info*/
7964         pvInfoList[ forwardMostMove ].score = programStats.score;
7965         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7966         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7967
7968         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7969
7970         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7971         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7972             int count = 0;
7973
7974             while( count < adjudicateLossPlies ) {
7975                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7976
7977                 if( count & 1 ) {
7978                     score = -score; /* Flip score for winning side */
7979                 }
7980
7981                 if( score > adjudicateLossThreshold ) {
7982                     break;
7983                 }
7984
7985                 count++;
7986             }
7987
7988             if( count >= adjudicateLossPlies ) {
7989                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7990
7991                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7992                     "Xboard adjudication",
7993                     GE_XBOARD );
7994
7995                 return;
7996             }
7997         }
7998
7999         if(Adjudicate(cps)) {
8000             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8001             return; // [HGM] adjudicate: for all automatic game ends
8002         }
8003
8004 #if ZIPPY
8005         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8006             first.initDone) {
8007           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8008                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8009                 SendToICS("draw ");
8010                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8011           }
8012           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8013           ics_user_moved = 1;
8014           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8015                 char buf[3*MSG_SIZ];
8016
8017                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8018                         programStats.score / 100.,
8019                         programStats.depth,
8020                         programStats.time / 100.,
8021                         (unsigned int)programStats.nodes,
8022                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8023                         programStats.movelist);
8024                 SendToICS(buf);
8025 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8026           }
8027         }
8028 #endif
8029
8030         /* [AS] Clear stats for next move */
8031         ClearProgramStats();
8032         thinkOutput[0] = NULLCHAR;
8033         hiddenThinkOutputState = 0;
8034
8035         bookHit = NULL;
8036         if (gameMode == TwoMachinesPlay) {
8037             /* [HGM] relaying draw offers moved to after reception of move */
8038             /* and interpreting offer as claim if it brings draw condition */
8039             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8040                 SendToProgram("draw\n", cps->other);
8041             }
8042             if (cps->other->sendTime) {
8043                 SendTimeRemaining(cps->other,
8044                                   cps->other->twoMachinesColor[0] == 'w');
8045             }
8046             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8047             if (firstMove && !bookHit) {
8048                 firstMove = FALSE;
8049                 if (cps->other->useColors) {
8050                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8051                 }
8052                 SendToProgram("go\n", cps->other);
8053             }
8054             cps->other->maybeThinking = TRUE;
8055         }
8056
8057         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8058
8059         if (!pausing && appData.ringBellAfterMoves) {
8060             RingBell();
8061         }
8062
8063         /*
8064          * Reenable menu items that were disabled while
8065          * machine was thinking
8066          */
8067         if (gameMode != TwoMachinesPlay)
8068             SetUserThinkingEnables();
8069
8070         // [HGM] book: after book hit opponent has received move and is now in force mode
8071         // force the book reply into it, and then fake that it outputted this move by jumping
8072         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8073         if(bookHit) {
8074                 static char bookMove[MSG_SIZ]; // a bit generous?
8075
8076                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8077                 strcat(bookMove, bookHit);
8078                 message = bookMove;
8079                 cps = cps->other;
8080                 programStats.nodes = programStats.depth = programStats.time =
8081                 programStats.score = programStats.got_only_move = 0;
8082                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8083
8084                 if(cps->lastPing != cps->lastPong) {
8085                     savedMessage = message; // args for deferred call
8086                     savedState = cps;
8087                     ScheduleDelayedEvent(DeferredBookMove, 10);
8088                     return;
8089                 }
8090                 goto FakeBookMove;
8091         }
8092
8093         return;
8094     }
8095
8096     /* Set special modes for chess engines.  Later something general
8097      *  could be added here; for now there is just one kludge feature,
8098      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8099      *  when "xboard" is given as an interactive command.
8100      */
8101     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8102         cps->useSigint = FALSE;
8103         cps->useSigterm = FALSE;
8104     }
8105     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8106       ParseFeatures(message+8, cps);
8107       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8108     }
8109
8110     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8111       int dummy, s=6; char buf[MSG_SIZ];
8112       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8113       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8114       ParseFEN(boards[0], &dummy, message+s);
8115       DrawPosition(TRUE, boards[0]);
8116       startedFromSetupPosition = TRUE;
8117       return;
8118     }
8119     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8120      * want this, I was asked to put it in, and obliged.
8121      */
8122     if (!strncmp(message, "setboard ", 9)) {
8123         Board initial_position;
8124
8125         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8126
8127         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8128             DisplayError(_("Bad FEN received from engine"), 0);
8129             return ;
8130         } else {
8131            Reset(TRUE, FALSE);
8132            CopyBoard(boards[0], initial_position);
8133            initialRulePlies = FENrulePlies;
8134            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8135            else gameMode = MachinePlaysBlack;
8136            DrawPosition(FALSE, boards[currentMove]);
8137         }
8138         return;
8139     }
8140
8141     /*
8142      * Look for communication commands
8143      */
8144     if (!strncmp(message, "telluser ", 9)) {
8145         if(message[9] == '\\' && message[10] == '\\')
8146             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8147         PlayTellSound();
8148         DisplayNote(message + 9);
8149         return;
8150     }
8151     if (!strncmp(message, "tellusererror ", 14)) {
8152         cps->userError = 1;
8153         if(message[14] == '\\' && message[15] == '\\')
8154             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8155         PlayTellSound();
8156         DisplayError(message + 14, 0);
8157         return;
8158     }
8159     if (!strncmp(message, "tellopponent ", 13)) {
8160       if (appData.icsActive) {
8161         if (loggedOn) {
8162           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8163           SendToICS(buf1);
8164         }
8165       } else {
8166         DisplayNote(message + 13);
8167       }
8168       return;
8169     }
8170     if (!strncmp(message, "tellothers ", 11)) {
8171       if (appData.icsActive) {
8172         if (loggedOn) {
8173           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8174           SendToICS(buf1);
8175         }
8176       }
8177       return;
8178     }
8179     if (!strncmp(message, "tellall ", 8)) {
8180       if (appData.icsActive) {
8181         if (loggedOn) {
8182           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8183           SendToICS(buf1);
8184         }
8185       } else {
8186         DisplayNote(message + 8);
8187       }
8188       return;
8189     }
8190     if (strncmp(message, "warning", 7) == 0) {
8191         /* Undocumented feature, use tellusererror in new code */
8192         DisplayError(message, 0);
8193         return;
8194     }
8195     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8196         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8197         strcat(realname, " query");
8198         AskQuestion(realname, buf2, buf1, cps->pr);
8199         return;
8200     }
8201     /* Commands from the engine directly to ICS.  We don't allow these to be
8202      *  sent until we are logged on. Crafty kibitzes have been known to
8203      *  interfere with the login process.
8204      */
8205     if (loggedOn) {
8206         if (!strncmp(message, "tellics ", 8)) {
8207             SendToICS(message + 8);
8208             SendToICS("\n");
8209             return;
8210         }
8211         if (!strncmp(message, "tellicsnoalias ", 15)) {
8212             SendToICS(ics_prefix);
8213             SendToICS(message + 15);
8214             SendToICS("\n");
8215             return;
8216         }
8217         /* The following are for backward compatibility only */
8218         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8219             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8220             SendToICS(ics_prefix);
8221             SendToICS(message);
8222             SendToICS("\n");
8223             return;
8224         }
8225     }
8226     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8227         return;
8228     }
8229     /*
8230      * If the move is illegal, cancel it and redraw the board.
8231      * Also deal with other error cases.  Matching is rather loose
8232      * here to accommodate engines written before the spec.
8233      */
8234     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8235         strncmp(message, "Error", 5) == 0) {
8236         if (StrStr(message, "name") ||
8237             StrStr(message, "rating") || StrStr(message, "?") ||
8238             StrStr(message, "result") || StrStr(message, "board") ||
8239             StrStr(message, "bk") || StrStr(message, "computer") ||
8240             StrStr(message, "variant") || StrStr(message, "hint") ||
8241             StrStr(message, "random") || StrStr(message, "depth") ||
8242             StrStr(message, "accepted")) {
8243             return;
8244         }
8245         if (StrStr(message, "protover")) {
8246           /* Program is responding to input, so it's apparently done
8247              initializing, and this error message indicates it is
8248              protocol version 1.  So we don't need to wait any longer
8249              for it to initialize and send feature commands. */
8250           FeatureDone(cps, 1);
8251           cps->protocolVersion = 1;
8252           return;
8253         }
8254         cps->maybeThinking = FALSE;
8255
8256         if (StrStr(message, "draw")) {
8257             /* Program doesn't have "draw" command */
8258             cps->sendDrawOffers = 0;
8259             return;
8260         }
8261         if (cps->sendTime != 1 &&
8262             (StrStr(message, "time") || StrStr(message, "otim"))) {
8263           /* Program apparently doesn't have "time" or "otim" command */
8264           cps->sendTime = 0;
8265           return;
8266         }
8267         if (StrStr(message, "analyze")) {
8268             cps->analysisSupport = FALSE;
8269             cps->analyzing = FALSE;
8270             Reset(FALSE, TRUE);
8271             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8272             DisplayError(buf2, 0);
8273             return;
8274         }
8275         if (StrStr(message, "(no matching move)st")) {
8276           /* Special kludge for GNU Chess 4 only */
8277           cps->stKludge = TRUE;
8278           SendTimeControl(cps, movesPerSession, timeControl,
8279                           timeIncrement, appData.searchDepth,
8280                           searchTime);
8281           return;
8282         }
8283         if (StrStr(message, "(no matching move)sd")) {
8284           /* Special kludge for GNU Chess 4 only */
8285           cps->sdKludge = TRUE;
8286           SendTimeControl(cps, movesPerSession, timeControl,
8287                           timeIncrement, appData.searchDepth,
8288                           searchTime);
8289           return;
8290         }
8291         if (!StrStr(message, "llegal")) {
8292             return;
8293         }
8294         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8295             gameMode == IcsIdle) return;
8296         if (forwardMostMove <= backwardMostMove) return;
8297         if (pausing) PauseEvent();
8298       if(appData.forceIllegal) {
8299             // [HGM] illegal: machine refused move; force position after move into it
8300           SendToProgram("force\n", cps);
8301           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8302                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8303                 // when black is to move, while there might be nothing on a2 or black
8304                 // might already have the move. So send the board as if white has the move.
8305                 // But first we must change the stm of the engine, as it refused the last move
8306                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8307                 if(WhiteOnMove(forwardMostMove)) {
8308                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8309                     SendBoard(cps, forwardMostMove); // kludgeless board
8310                 } else {
8311                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8312                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8313                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8314                 }
8315           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8316             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8317                  gameMode == TwoMachinesPlay)
8318               SendToProgram("go\n", cps);
8319             return;
8320       } else
8321         if (gameMode == PlayFromGameFile) {
8322             /* Stop reading this game file */
8323             gameMode = EditGame;
8324             ModeHighlight();
8325         }
8326         /* [HGM] illegal-move claim should forfeit game when Xboard */
8327         /* only passes fully legal moves                            */
8328         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8329             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8330                                 "False illegal-move claim", GE_XBOARD );
8331             return; // do not take back move we tested as valid
8332         }
8333         currentMove = forwardMostMove-1;
8334         DisplayMove(currentMove-1); /* before DisplayMoveError */
8335         SwitchClocks(forwardMostMove-1); // [HGM] race
8336         DisplayBothClocks();
8337         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8338                 parseList[currentMove], _(cps->which));
8339         DisplayMoveError(buf1);
8340         DrawPosition(FALSE, boards[currentMove]);
8341         return;
8342     }
8343     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8344         /* Program has a broken "time" command that
8345            outputs a string not ending in newline.
8346            Don't use it. */
8347         cps->sendTime = 0;
8348     }
8349
8350     /*
8351      * If chess program startup fails, exit with an error message.
8352      * Attempts to recover here are futile.
8353      */
8354     if ((StrStr(message, "unknown host") != NULL)
8355         || (StrStr(message, "No remote directory") != NULL)
8356         || (StrStr(message, "not found") != NULL)
8357         || (StrStr(message, "No such file") != NULL)
8358         || (StrStr(message, "can't alloc") != NULL)
8359         || (StrStr(message, "Permission denied") != NULL)) {
8360
8361         cps->maybeThinking = FALSE;
8362         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8363                 _(cps->which), cps->program, cps->host, message);
8364         RemoveInputSource(cps->isr);
8365         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8366             if(cps == &first) appData.noChessProgram = TRUE;
8367             DisplayError(buf1, 0);
8368         }
8369         return;
8370     }
8371
8372     /*
8373      * Look for hint output
8374      */
8375     if (sscanf(message, "Hint: %s", buf1) == 1) {
8376         if (cps == &first && hintRequested) {
8377             hintRequested = FALSE;
8378             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8379                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8380                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8381                                     PosFlags(forwardMostMove),
8382                                     fromY, fromX, toY, toX, promoChar, buf1);
8383                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8384                 DisplayInformation(buf2);
8385             } else {
8386                 /* Hint move could not be parsed!? */
8387               snprintf(buf2, sizeof(buf2),
8388                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8389                         buf1, _(cps->which));
8390                 DisplayError(buf2, 0);
8391             }
8392         } else {
8393           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8394         }
8395         return;
8396     }
8397
8398     /*
8399      * Ignore other messages if game is not in progress
8400      */
8401     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8402         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8403
8404     /*
8405      * look for win, lose, draw, or draw offer
8406      */
8407     if (strncmp(message, "1-0", 3) == 0) {
8408         char *p, *q, *r = "";
8409         p = strchr(message, '{');
8410         if (p) {
8411             q = strchr(p, '}');
8412             if (q) {
8413                 *q = NULLCHAR;
8414                 r = p + 1;
8415             }
8416         }
8417         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8418         return;
8419     } else if (strncmp(message, "0-1", 3) == 0) {
8420         char *p, *q, *r = "";
8421         p = strchr(message, '{');
8422         if (p) {
8423             q = strchr(p, '}');
8424             if (q) {
8425                 *q = NULLCHAR;
8426                 r = p + 1;
8427             }
8428         }
8429         /* Kludge for Arasan 4.1 bug */
8430         if (strcmp(r, "Black resigns") == 0) {
8431             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8432             return;
8433         }
8434         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8435         return;
8436     } else if (strncmp(message, "1/2", 3) == 0) {
8437         char *p, *q, *r = "";
8438         p = strchr(message, '{');
8439         if (p) {
8440             q = strchr(p, '}');
8441             if (q) {
8442                 *q = NULLCHAR;
8443                 r = p + 1;
8444             }
8445         }
8446
8447         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8448         return;
8449
8450     } else if (strncmp(message, "White resign", 12) == 0) {
8451         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8452         return;
8453     } else if (strncmp(message, "Black resign", 12) == 0) {
8454         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8455         return;
8456     } else if (strncmp(message, "White matches", 13) == 0 ||
8457                strncmp(message, "Black matches", 13) == 0   ) {
8458         /* [HGM] ignore GNUShogi noises */
8459         return;
8460     } else if (strncmp(message, "White", 5) == 0 &&
8461                message[5] != '(' &&
8462                StrStr(message, "Black") == NULL) {
8463         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8464         return;
8465     } else if (strncmp(message, "Black", 5) == 0 &&
8466                message[5] != '(') {
8467         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8468         return;
8469     } else if (strcmp(message, "resign") == 0 ||
8470                strcmp(message, "computer resigns") == 0) {
8471         switch (gameMode) {
8472           case MachinePlaysBlack:
8473           case IcsPlayingBlack:
8474             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8475             break;
8476           case MachinePlaysWhite:
8477           case IcsPlayingWhite:
8478             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8479             break;
8480           case TwoMachinesPlay:
8481             if (cps->twoMachinesColor[0] == 'w')
8482               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8483             else
8484               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8485             break;
8486           default:
8487             /* can't happen */
8488             break;
8489         }
8490         return;
8491     } else if (strncmp(message, "opponent mates", 14) == 0) {
8492         switch (gameMode) {
8493           case MachinePlaysBlack:
8494           case IcsPlayingBlack:
8495             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8496             break;
8497           case MachinePlaysWhite:
8498           case IcsPlayingWhite:
8499             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8500             break;
8501           case TwoMachinesPlay:
8502             if (cps->twoMachinesColor[0] == 'w')
8503               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8504             else
8505               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8506             break;
8507           default:
8508             /* can't happen */
8509             break;
8510         }
8511         return;
8512     } else if (strncmp(message, "computer mates", 14) == 0) {
8513         switch (gameMode) {
8514           case MachinePlaysBlack:
8515           case IcsPlayingBlack:
8516             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8517             break;
8518           case MachinePlaysWhite:
8519           case IcsPlayingWhite:
8520             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8521             break;
8522           case TwoMachinesPlay:
8523             if (cps->twoMachinesColor[0] == 'w')
8524               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8525             else
8526               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8527             break;
8528           default:
8529             /* can't happen */
8530             break;
8531         }
8532         return;
8533     } else if (strncmp(message, "checkmate", 9) == 0) {
8534         if (WhiteOnMove(forwardMostMove)) {
8535             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8536         } else {
8537             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8538         }
8539         return;
8540     } else if (strstr(message, "Draw") != NULL ||
8541                strstr(message, "game is a draw") != NULL) {
8542         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8543         return;
8544     } else if (strstr(message, "offer") != NULL &&
8545                strstr(message, "draw") != NULL) {
8546 #if ZIPPY
8547         if (appData.zippyPlay && first.initDone) {
8548             /* Relay offer to ICS */
8549             SendToICS(ics_prefix);
8550             SendToICS("draw\n");
8551         }
8552 #endif
8553         cps->offeredDraw = 2; /* valid until this engine moves twice */
8554         if (gameMode == TwoMachinesPlay) {
8555             if (cps->other->offeredDraw) {
8556                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8557             /* [HGM] in two-machine mode we delay relaying draw offer      */
8558             /* until after we also have move, to see if it is really claim */
8559             }
8560         } else if (gameMode == MachinePlaysWhite ||
8561                    gameMode == MachinePlaysBlack) {
8562           if (userOfferedDraw) {
8563             DisplayInformation(_("Machine accepts your draw offer"));
8564             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8565           } else {
8566             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8567           }
8568         }
8569     }
8570
8571
8572     /*
8573      * Look for thinking output
8574      */
8575     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8576           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8577                                 ) {
8578         int plylev, mvleft, mvtot, curscore, time;
8579         char mvname[MOVE_LEN];
8580         u64 nodes; // [DM]
8581         char plyext;
8582         int ignore = FALSE;
8583         int prefixHint = FALSE;
8584         mvname[0] = NULLCHAR;
8585
8586         switch (gameMode) {
8587           case MachinePlaysBlack:
8588           case IcsPlayingBlack:
8589             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8590             break;
8591           case MachinePlaysWhite:
8592           case IcsPlayingWhite:
8593             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8594             break;
8595           case AnalyzeMode:
8596           case AnalyzeFile:
8597             break;
8598           case IcsObserving: /* [DM] icsEngineAnalyze */
8599             if (!appData.icsEngineAnalyze) ignore = TRUE;
8600             break;
8601           case TwoMachinesPlay:
8602             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8603                 ignore = TRUE;
8604             }
8605             break;
8606           default:
8607             ignore = TRUE;
8608             break;
8609         }
8610
8611         if (!ignore) {
8612             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8613             buf1[0] = NULLCHAR;
8614             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8615                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8616
8617                 if (plyext != ' ' && plyext != '\t') {
8618                     time *= 100;
8619                 }
8620
8621                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8622                 if( cps->scoreIsAbsolute &&
8623                     ( gameMode == MachinePlaysBlack ||
8624                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8625                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8626                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8627                      !WhiteOnMove(currentMove)
8628                     ) )
8629                 {
8630                     curscore = -curscore;
8631                 }
8632
8633                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8634
8635                 tempStats.depth = plylev;
8636                 tempStats.nodes = nodes;
8637                 tempStats.time = time;
8638                 tempStats.score = curscore;
8639                 tempStats.got_only_move = 0;
8640
8641                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8642                         int ticklen;
8643
8644                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8645                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8646                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8647                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8648                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8649                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8650                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8651                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8652                 }
8653
8654                 /* Buffer overflow protection */
8655                 if (pv[0] != NULLCHAR) {
8656                     if (strlen(pv) >= sizeof(tempStats.movelist)
8657                         && appData.debugMode) {
8658                         fprintf(debugFP,
8659                                 "PV is too long; using the first %u bytes.\n",
8660                                 (unsigned) sizeof(tempStats.movelist) - 1);
8661                     }
8662
8663                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8664                 } else {
8665                     sprintf(tempStats.movelist, " no PV\n");
8666                 }
8667
8668                 if (tempStats.seen_stat) {
8669                     tempStats.ok_to_send = 1;
8670                 }
8671
8672                 if (strchr(tempStats.movelist, '(') != NULL) {
8673                     tempStats.line_is_book = 1;
8674                     tempStats.nr_moves = 0;
8675                     tempStats.moves_left = 0;
8676                 } else {
8677                     tempStats.line_is_book = 0;
8678                 }
8679
8680                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8681                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8682
8683                 SendProgramStatsToFrontend( cps, &tempStats );
8684
8685                 /*
8686                     [AS] Protect the thinkOutput buffer from overflow... this
8687                     is only useful if buf1 hasn't overflowed first!
8688                 */
8689                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8690                          plylev,
8691                          (gameMode == TwoMachinesPlay ?
8692                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8693                          ((double) curscore) / 100.0,
8694                          prefixHint ? lastHint : "",
8695                          prefixHint ? " " : "" );
8696
8697                 if( buf1[0] != NULLCHAR ) {
8698                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8699
8700                     if( strlen(pv) > max_len ) {
8701                         if( appData.debugMode) {
8702                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8703                         }
8704                         pv[max_len+1] = '\0';
8705                     }
8706
8707                     strcat( thinkOutput, pv);
8708                 }
8709
8710                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8711                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8712                     DisplayMove(currentMove - 1);
8713                 }
8714                 return;
8715
8716             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8717                 /* crafty (9.25+) says "(only move) <move>"
8718                  * if there is only 1 legal move
8719                  */
8720                 sscanf(p, "(only move) %s", buf1);
8721                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8722                 sprintf(programStats.movelist, "%s (only move)", buf1);
8723                 programStats.depth = 1;
8724                 programStats.nr_moves = 1;
8725                 programStats.moves_left = 1;
8726                 programStats.nodes = 1;
8727                 programStats.time = 1;
8728                 programStats.got_only_move = 1;
8729
8730                 /* Not really, but we also use this member to
8731                    mean "line isn't going to change" (Crafty
8732                    isn't searching, so stats won't change) */
8733                 programStats.line_is_book = 1;
8734
8735                 SendProgramStatsToFrontend( cps, &programStats );
8736
8737                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8738                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8739                     DisplayMove(currentMove - 1);
8740                 }
8741                 return;
8742             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8743                               &time, &nodes, &plylev, &mvleft,
8744                               &mvtot, mvname) >= 5) {
8745                 /* The stat01: line is from Crafty (9.29+) in response
8746                    to the "." command */
8747                 programStats.seen_stat = 1;
8748                 cps->maybeThinking = TRUE;
8749
8750                 if (programStats.got_only_move || !appData.periodicUpdates)
8751                   return;
8752
8753                 programStats.depth = plylev;
8754                 programStats.time = time;
8755                 programStats.nodes = nodes;
8756                 programStats.moves_left = mvleft;
8757                 programStats.nr_moves = mvtot;
8758                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8759                 programStats.ok_to_send = 1;
8760                 programStats.movelist[0] = '\0';
8761
8762                 SendProgramStatsToFrontend( cps, &programStats );
8763
8764                 return;
8765
8766             } else if (strncmp(message,"++",2) == 0) {
8767                 /* Crafty 9.29+ outputs this */
8768                 programStats.got_fail = 2;
8769                 return;
8770
8771             } else if (strncmp(message,"--",2) == 0) {
8772                 /* Crafty 9.29+ outputs this */
8773                 programStats.got_fail = 1;
8774                 return;
8775
8776             } else if (thinkOutput[0] != NULLCHAR &&
8777                        strncmp(message, "    ", 4) == 0) {
8778                 unsigned message_len;
8779
8780                 p = message;
8781                 while (*p && *p == ' ') p++;
8782
8783                 message_len = strlen( p );
8784
8785                 /* [AS] Avoid buffer overflow */
8786                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8787                     strcat(thinkOutput, " ");
8788                     strcat(thinkOutput, p);
8789                 }
8790
8791                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8792                     strcat(programStats.movelist, " ");
8793                     strcat(programStats.movelist, p);
8794                 }
8795
8796                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8797                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8798                     DisplayMove(currentMove - 1);
8799                 }
8800                 return;
8801             }
8802         }
8803         else {
8804             buf1[0] = NULLCHAR;
8805
8806             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8807                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8808             {
8809                 ChessProgramStats cpstats;
8810
8811                 if (plyext != ' ' && plyext != '\t') {
8812                     time *= 100;
8813                 }
8814
8815                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8816                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8817                     curscore = -curscore;
8818                 }
8819
8820                 cpstats.depth = plylev;
8821                 cpstats.nodes = nodes;
8822                 cpstats.time = time;
8823                 cpstats.score = curscore;
8824                 cpstats.got_only_move = 0;
8825                 cpstats.movelist[0] = '\0';
8826
8827                 if (buf1[0] != NULLCHAR) {
8828                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8829                 }
8830
8831                 cpstats.ok_to_send = 0;
8832                 cpstats.line_is_book = 0;
8833                 cpstats.nr_moves = 0;
8834                 cpstats.moves_left = 0;
8835
8836                 SendProgramStatsToFrontend( cps, &cpstats );
8837             }
8838         }
8839     }
8840 }
8841
8842
8843 /* Parse a game score from the character string "game", and
8844    record it as the history of the current game.  The game
8845    score is NOT assumed to start from the standard position.
8846    The display is not updated in any way.
8847    */
8848 void
8849 ParseGameHistory(game)
8850      char *game;
8851 {
8852     ChessMove moveType;
8853     int fromX, fromY, toX, toY, boardIndex;
8854     char promoChar;
8855     char *p, *q;
8856     char buf[MSG_SIZ];
8857
8858     if (appData.debugMode)
8859       fprintf(debugFP, "Parsing game history: %s\n", game);
8860
8861     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8862     gameInfo.site = StrSave(appData.icsHost);
8863     gameInfo.date = PGNDate();
8864     gameInfo.round = StrSave("-");
8865
8866     /* Parse out names of players */
8867     while (*game == ' ') game++;
8868     p = buf;
8869     while (*game != ' ') *p++ = *game++;
8870     *p = NULLCHAR;
8871     gameInfo.white = StrSave(buf);
8872     while (*game == ' ') game++;
8873     p = buf;
8874     while (*game != ' ' && *game != '\n') *p++ = *game++;
8875     *p = NULLCHAR;
8876     gameInfo.black = StrSave(buf);
8877
8878     /* Parse moves */
8879     boardIndex = blackPlaysFirst ? 1 : 0;
8880     yynewstr(game);
8881     for (;;) {
8882         yyboardindex = boardIndex;
8883         moveType = (ChessMove) Myylex();
8884         switch (moveType) {
8885           case IllegalMove:             /* maybe suicide chess, etc. */
8886   if (appData.debugMode) {
8887     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8888     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8889     setbuf(debugFP, NULL);
8890   }
8891           case WhitePromotion:
8892           case BlackPromotion:
8893           case WhiteNonPromotion:
8894           case BlackNonPromotion:
8895           case NormalMove:
8896           case WhiteCapturesEnPassant:
8897           case BlackCapturesEnPassant:
8898           case WhiteKingSideCastle:
8899           case WhiteQueenSideCastle:
8900           case BlackKingSideCastle:
8901           case BlackQueenSideCastle:
8902           case WhiteKingSideCastleWild:
8903           case WhiteQueenSideCastleWild:
8904           case BlackKingSideCastleWild:
8905           case BlackQueenSideCastleWild:
8906           /* PUSH Fabien */
8907           case WhiteHSideCastleFR:
8908           case WhiteASideCastleFR:
8909           case BlackHSideCastleFR:
8910           case BlackASideCastleFR:
8911           /* POP Fabien */
8912             fromX = currentMoveString[0] - AAA;
8913             fromY = currentMoveString[1] - ONE;
8914             toX = currentMoveString[2] - AAA;
8915             toY = currentMoveString[3] - ONE;
8916             promoChar = currentMoveString[4];
8917             break;
8918           case WhiteDrop:
8919           case BlackDrop:
8920             fromX = moveType == WhiteDrop ?
8921               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8922             (int) CharToPiece(ToLower(currentMoveString[0]));
8923             fromY = DROP_RANK;
8924             toX = currentMoveString[2] - AAA;
8925             toY = currentMoveString[3] - ONE;
8926             promoChar = NULLCHAR;
8927             break;
8928           case AmbiguousMove:
8929             /* bug? */
8930             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8931   if (appData.debugMode) {
8932     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8933     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8934     setbuf(debugFP, NULL);
8935   }
8936             DisplayError(buf, 0);
8937             return;
8938           case ImpossibleMove:
8939             /* bug? */
8940             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8941   if (appData.debugMode) {
8942     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8943     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8944     setbuf(debugFP, NULL);
8945   }
8946             DisplayError(buf, 0);
8947             return;
8948           case EndOfFile:
8949             if (boardIndex < backwardMostMove) {
8950                 /* Oops, gap.  How did that happen? */
8951                 DisplayError(_("Gap in move list"), 0);
8952                 return;
8953             }
8954             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8955             if (boardIndex > forwardMostMove) {
8956                 forwardMostMove = boardIndex;
8957             }
8958             return;
8959           case ElapsedTime:
8960             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8961                 strcat(parseList[boardIndex-1], " ");
8962                 strcat(parseList[boardIndex-1], yy_text);
8963             }
8964             continue;
8965           case Comment:
8966           case PGNTag:
8967           case NAG:
8968           default:
8969             /* ignore */
8970             continue;
8971           case WhiteWins:
8972           case BlackWins:
8973           case GameIsDrawn:
8974           case GameUnfinished:
8975             if (gameMode == IcsExamining) {
8976                 if (boardIndex < backwardMostMove) {
8977                     /* Oops, gap.  How did that happen? */
8978                     return;
8979                 }
8980                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8981                 return;
8982             }
8983             gameInfo.result = moveType;
8984             p = strchr(yy_text, '{');
8985             if (p == NULL) p = strchr(yy_text, '(');
8986             if (p == NULL) {
8987                 p = yy_text;
8988                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8989             } else {
8990                 q = strchr(p, *p == '{' ? '}' : ')');
8991                 if (q != NULL) *q = NULLCHAR;
8992                 p++;
8993             }
8994             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8995             gameInfo.resultDetails = StrSave(p);
8996             continue;
8997         }
8998         if (boardIndex >= forwardMostMove &&
8999             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9000             backwardMostMove = blackPlaysFirst ? 1 : 0;
9001             return;
9002         }
9003         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9004                                  fromY, fromX, toY, toX, promoChar,
9005                                  parseList[boardIndex]);
9006         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9007         /* currentMoveString is set as a side-effect of yylex */
9008         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9009         strcat(moveList[boardIndex], "\n");
9010         boardIndex++;
9011         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9012         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9013           case MT_NONE:
9014           case MT_STALEMATE:
9015           default:
9016             break;
9017           case MT_CHECK:
9018             if(gameInfo.variant != VariantShogi)
9019                 strcat(parseList[boardIndex - 1], "+");
9020             break;
9021           case MT_CHECKMATE:
9022           case MT_STAINMATE:
9023             strcat(parseList[boardIndex - 1], "#");
9024             break;
9025         }
9026     }
9027 }
9028
9029
9030 /* Apply a move to the given board  */
9031 void
9032 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9033      int fromX, fromY, toX, toY;
9034      int promoChar;
9035      Board board;
9036 {
9037   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9038   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9039
9040     /* [HGM] compute & store e.p. status and castling rights for new position */
9041     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9042
9043       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9044       oldEP = (signed char)board[EP_STATUS];
9045       board[EP_STATUS] = EP_NONE;
9046
9047   if (fromY == DROP_RANK) {
9048         /* must be first */
9049         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9050             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9051             return;
9052         }
9053         piece = board[toY][toX] = (ChessSquare) fromX;
9054   } else {
9055       int i;
9056
9057       if( board[toY][toX] != EmptySquare )
9058            board[EP_STATUS] = EP_CAPTURE;
9059
9060       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9061            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9062                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9063       } else
9064       if( board[fromY][fromX] == WhitePawn ) {
9065            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9066                board[EP_STATUS] = EP_PAWN_MOVE;
9067            if( toY-fromY==2) {
9068                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9069                         gameInfo.variant != VariantBerolina || toX < fromX)
9070                       board[EP_STATUS] = toX | berolina;
9071                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9072                         gameInfo.variant != VariantBerolina || toX > fromX)
9073                       board[EP_STATUS] = toX;
9074            }
9075       } else
9076       if( board[fromY][fromX] == BlackPawn ) {
9077            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9078                board[EP_STATUS] = EP_PAWN_MOVE;
9079            if( toY-fromY== -2) {
9080                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9081                         gameInfo.variant != VariantBerolina || toX < fromX)
9082                       board[EP_STATUS] = toX | berolina;
9083                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9084                         gameInfo.variant != VariantBerolina || toX > fromX)
9085                       board[EP_STATUS] = toX;
9086            }
9087        }
9088
9089        for(i=0; i<nrCastlingRights; i++) {
9090            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9091               board[CASTLING][i] == toX   && castlingRank[i] == toY
9092              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9093        }
9094
9095      if (fromX == toX && fromY == toY) return;
9096
9097      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9098      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9099      if(gameInfo.variant == VariantKnightmate)
9100          king += (int) WhiteUnicorn - (int) WhiteKing;
9101
9102     /* Code added by Tord: */
9103     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9104     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9105         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9106       board[fromY][fromX] = EmptySquare;
9107       board[toY][toX] = EmptySquare;
9108       if((toX > fromX) != (piece == WhiteRook)) {
9109         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9110       } else {
9111         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9112       }
9113     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9114                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9115       board[fromY][fromX] = EmptySquare;
9116       board[toY][toX] = EmptySquare;
9117       if((toX > fromX) != (piece == BlackRook)) {
9118         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9119       } else {
9120         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9121       }
9122     /* End of code added by Tord */
9123
9124     } else if (board[fromY][fromX] == king
9125         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9126         && toY == fromY && toX > fromX+1) {
9127         board[fromY][fromX] = EmptySquare;
9128         board[toY][toX] = king;
9129         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9130         board[fromY][BOARD_RGHT-1] = EmptySquare;
9131     } else if (board[fromY][fromX] == king
9132         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9133                && toY == fromY && toX < fromX-1) {
9134         board[fromY][fromX] = EmptySquare;
9135         board[toY][toX] = king;
9136         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9137         board[fromY][BOARD_LEFT] = EmptySquare;
9138     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9139                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9140                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9141                ) {
9142         /* white pawn promotion */
9143         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9144         if(gameInfo.variant==VariantBughouse ||
9145            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9146             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9147         board[fromY][fromX] = EmptySquare;
9148     } else if ((fromY >= BOARD_HEIGHT>>1)
9149                && (toX != fromX)
9150                && gameInfo.variant != VariantXiangqi
9151                && gameInfo.variant != VariantBerolina
9152                && (board[fromY][fromX] == WhitePawn)
9153                && (board[toY][toX] == EmptySquare)) {
9154         board[fromY][fromX] = EmptySquare;
9155         board[toY][toX] = WhitePawn;
9156         captured = board[toY - 1][toX];
9157         board[toY - 1][toX] = EmptySquare;
9158     } else if ((fromY == BOARD_HEIGHT-4)
9159                && (toX == fromX)
9160                && gameInfo.variant == VariantBerolina
9161                && (board[fromY][fromX] == WhitePawn)
9162                && (board[toY][toX] == EmptySquare)) {
9163         board[fromY][fromX] = EmptySquare;
9164         board[toY][toX] = WhitePawn;
9165         if(oldEP & EP_BEROLIN_A) {
9166                 captured = board[fromY][fromX-1];
9167                 board[fromY][fromX-1] = EmptySquare;
9168         }else{  captured = board[fromY][fromX+1];
9169                 board[fromY][fromX+1] = EmptySquare;
9170         }
9171     } else if (board[fromY][fromX] == king
9172         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9173                && toY == fromY && toX > fromX+1) {
9174         board[fromY][fromX] = EmptySquare;
9175         board[toY][toX] = king;
9176         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9177         board[fromY][BOARD_RGHT-1] = EmptySquare;
9178     } else if (board[fromY][fromX] == king
9179         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9180                && toY == fromY && toX < fromX-1) {
9181         board[fromY][fromX] = EmptySquare;
9182         board[toY][toX] = king;
9183         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9184         board[fromY][BOARD_LEFT] = EmptySquare;
9185     } else if (fromY == 7 && fromX == 3
9186                && board[fromY][fromX] == BlackKing
9187                && toY == 7 && toX == 5) {
9188         board[fromY][fromX] = EmptySquare;
9189         board[toY][toX] = BlackKing;
9190         board[fromY][7] = EmptySquare;
9191         board[toY][4] = BlackRook;
9192     } else if (fromY == 7 && fromX == 3
9193                && board[fromY][fromX] == BlackKing
9194                && toY == 7 && toX == 1) {
9195         board[fromY][fromX] = EmptySquare;
9196         board[toY][toX] = BlackKing;
9197         board[fromY][0] = EmptySquare;
9198         board[toY][2] = BlackRook;
9199     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9200                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9201                && toY < promoRank && promoChar
9202                ) {
9203         /* black pawn promotion */
9204         board[toY][toX] = CharToPiece(ToLower(promoChar));
9205         if(gameInfo.variant==VariantBughouse ||
9206            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9207             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9208         board[fromY][fromX] = EmptySquare;
9209     } else if ((fromY < BOARD_HEIGHT>>1)
9210                && (toX != fromX)
9211                && gameInfo.variant != VariantXiangqi
9212                && gameInfo.variant != VariantBerolina
9213                && (board[fromY][fromX] == BlackPawn)
9214                && (board[toY][toX] == EmptySquare)) {
9215         board[fromY][fromX] = EmptySquare;
9216         board[toY][toX] = BlackPawn;
9217         captured = board[toY + 1][toX];
9218         board[toY + 1][toX] = EmptySquare;
9219     } else if ((fromY == 3)
9220                && (toX == fromX)
9221                && gameInfo.variant == VariantBerolina
9222                && (board[fromY][fromX] == BlackPawn)
9223                && (board[toY][toX] == EmptySquare)) {
9224         board[fromY][fromX] = EmptySquare;
9225         board[toY][toX] = BlackPawn;
9226         if(oldEP & EP_BEROLIN_A) {
9227                 captured = board[fromY][fromX-1];
9228                 board[fromY][fromX-1] = EmptySquare;
9229         }else{  captured = board[fromY][fromX+1];
9230                 board[fromY][fromX+1] = EmptySquare;
9231         }
9232     } else {
9233         board[toY][toX] = board[fromY][fromX];
9234         board[fromY][fromX] = EmptySquare;
9235     }
9236   }
9237
9238     if (gameInfo.holdingsWidth != 0) {
9239
9240       /* !!A lot more code needs to be written to support holdings  */
9241       /* [HGM] OK, so I have written it. Holdings are stored in the */
9242       /* penultimate board files, so they are automaticlly stored   */
9243       /* in the game history.                                       */
9244       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9245                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9246         /* Delete from holdings, by decreasing count */
9247         /* and erasing image if necessary            */
9248         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9249         if(p < (int) BlackPawn) { /* white drop */
9250              p -= (int)WhitePawn;
9251                  p = PieceToNumber((ChessSquare)p);
9252              if(p >= gameInfo.holdingsSize) p = 0;
9253              if(--board[p][BOARD_WIDTH-2] <= 0)
9254                   board[p][BOARD_WIDTH-1] = EmptySquare;
9255              if((int)board[p][BOARD_WIDTH-2] < 0)
9256                         board[p][BOARD_WIDTH-2] = 0;
9257         } else {                  /* black drop */
9258              p -= (int)BlackPawn;
9259                  p = PieceToNumber((ChessSquare)p);
9260              if(p >= gameInfo.holdingsSize) p = 0;
9261              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9262                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9263              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9264                         board[BOARD_HEIGHT-1-p][1] = 0;
9265         }
9266       }
9267       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9268           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9269         /* [HGM] holdings: Add to holdings, if holdings exist */
9270         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9271                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9272                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9273         }
9274         p = (int) captured;
9275         if (p >= (int) BlackPawn) {
9276           p -= (int)BlackPawn;
9277           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9278                   /* in Shogi restore piece to its original  first */
9279                   captured = (ChessSquare) (DEMOTED captured);
9280                   p = DEMOTED p;
9281           }
9282           p = PieceToNumber((ChessSquare)p);
9283           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9284           board[p][BOARD_WIDTH-2]++;
9285           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9286         } else {
9287           p -= (int)WhitePawn;
9288           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9289                   captured = (ChessSquare) (DEMOTED captured);
9290                   p = DEMOTED p;
9291           }
9292           p = PieceToNumber((ChessSquare)p);
9293           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9294           board[BOARD_HEIGHT-1-p][1]++;
9295           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9296         }
9297       }
9298     } else if (gameInfo.variant == VariantAtomic) {
9299       if (captured != EmptySquare) {
9300         int y, x;
9301         for (y = toY-1; y <= toY+1; y++) {
9302           for (x = toX-1; x <= toX+1; x++) {
9303             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9304                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9305               board[y][x] = EmptySquare;
9306             }
9307           }
9308         }
9309         board[toY][toX] = EmptySquare;
9310       }
9311     }
9312     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9313         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9314     } else
9315     if(promoChar == '+') {
9316         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9317         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9318     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9319         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9320     }
9321     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9322                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9323         // [HGM] superchess: take promotion piece out of holdings
9324         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9325         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9326             if(!--board[k][BOARD_WIDTH-2])
9327                 board[k][BOARD_WIDTH-1] = EmptySquare;
9328         } else {
9329             if(!--board[BOARD_HEIGHT-1-k][1])
9330                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9331         }
9332     }
9333
9334 }
9335
9336 /* Updates forwardMostMove */
9337 void
9338 MakeMove(fromX, fromY, toX, toY, promoChar)
9339      int fromX, fromY, toX, toY;
9340      int promoChar;
9341 {
9342 //    forwardMostMove++; // [HGM] bare: moved downstream
9343
9344     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9345         int timeLeft; static int lastLoadFlag=0; int king, piece;
9346         piece = boards[forwardMostMove][fromY][fromX];
9347         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9348         if(gameInfo.variant == VariantKnightmate)
9349             king += (int) WhiteUnicorn - (int) WhiteKing;
9350         if(forwardMostMove == 0) {
9351             if(blackPlaysFirst)
9352                 fprintf(serverMoves, "%s;", second.tidy);
9353             fprintf(serverMoves, "%s;", first.tidy);
9354             if(!blackPlaysFirst)
9355                 fprintf(serverMoves, "%s;", second.tidy);
9356         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9357         lastLoadFlag = loadFlag;
9358         // print base move
9359         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9360         // print castling suffix
9361         if( toY == fromY && piece == king ) {
9362             if(toX-fromX > 1)
9363                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9364             if(fromX-toX >1)
9365                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9366         }
9367         // e.p. suffix
9368         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9369              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9370              boards[forwardMostMove][toY][toX] == EmptySquare
9371              && fromX != toX && fromY != toY)
9372                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9373         // promotion suffix
9374         if(promoChar != NULLCHAR)
9375                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9376         if(!loadFlag) {
9377             fprintf(serverMoves, "/%d/%d",
9378                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9379             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9380             else                      timeLeft = blackTimeRemaining/1000;
9381             fprintf(serverMoves, "/%d", timeLeft);
9382         }
9383         fflush(serverMoves);
9384     }
9385
9386     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9387       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9388                         0, 1);
9389       return;
9390     }
9391     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9392     if (commentList[forwardMostMove+1] != NULL) {
9393         free(commentList[forwardMostMove+1]);
9394         commentList[forwardMostMove+1] = NULL;
9395     }
9396     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9397     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9398     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9399     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9400     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9401     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9402     gameInfo.result = GameUnfinished;
9403     if (gameInfo.resultDetails != NULL) {
9404         free(gameInfo.resultDetails);
9405         gameInfo.resultDetails = NULL;
9406     }
9407     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9408                               moveList[forwardMostMove - 1]);
9409     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9410                              PosFlags(forwardMostMove - 1),
9411                              fromY, fromX, toY, toX, promoChar,
9412                              parseList[forwardMostMove - 1]);
9413     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9414       case MT_NONE:
9415       case MT_STALEMATE:
9416       default:
9417         break;
9418       case MT_CHECK:
9419         if(gameInfo.variant != VariantShogi)
9420             strcat(parseList[forwardMostMove - 1], "+");
9421         break;
9422       case MT_CHECKMATE:
9423       case MT_STAINMATE:
9424         strcat(parseList[forwardMostMove - 1], "#");
9425         break;
9426     }
9427     if (appData.debugMode) {
9428         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9429     }
9430
9431 }
9432
9433 /* Updates currentMove if not pausing */
9434 void
9435 ShowMove(fromX, fromY, toX, toY)
9436 {
9437     int instant = (gameMode == PlayFromGameFile) ?
9438         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9439     if(appData.noGUI) return;
9440     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9441         if (!instant) {
9442             if (forwardMostMove == currentMove + 1) {
9443                 AnimateMove(boards[forwardMostMove - 1],
9444                             fromX, fromY, toX, toY);
9445             }
9446             if (appData.highlightLastMove) {
9447                 SetHighlights(fromX, fromY, toX, toY);
9448             }
9449         }
9450         currentMove = forwardMostMove;
9451     }
9452
9453     if (instant) return;
9454
9455     DisplayMove(currentMove - 1);
9456     DrawPosition(FALSE, boards[currentMove]);
9457     DisplayBothClocks();
9458     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9459     DisplayBook(currentMove);
9460 }
9461
9462 void SendEgtPath(ChessProgramState *cps)
9463 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9464         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9465
9466         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9467
9468         while(*p) {
9469             char c, *q = name+1, *r, *s;
9470
9471             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9472             while(*p && *p != ',') *q++ = *p++;
9473             *q++ = ':'; *q = 0;
9474             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9475                 strcmp(name, ",nalimov:") == 0 ) {
9476                 // take nalimov path from the menu-changeable option first, if it is defined
9477               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9478                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9479             } else
9480             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9481                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9482                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9483                 s = r = StrStr(s, ":") + 1; // beginning of path info
9484                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9485                 c = *r; *r = 0;             // temporarily null-terminate path info
9486                     *--q = 0;               // strip of trailig ':' from name
9487                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9488                 *r = c;
9489                 SendToProgram(buf,cps);     // send egtbpath command for this format
9490             }
9491             if(*p == ',') p++; // read away comma to position for next format name
9492         }
9493 }
9494
9495 void
9496 InitChessProgram(cps, setup)
9497      ChessProgramState *cps;
9498      int setup; /* [HGM] needed to setup FRC opening position */
9499 {
9500     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9501     if (appData.noChessProgram) return;
9502     hintRequested = FALSE;
9503     bookRequested = FALSE;
9504
9505     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9506     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9507     if(cps->memSize) { /* [HGM] memory */
9508       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9509         SendToProgram(buf, cps);
9510     }
9511     SendEgtPath(cps); /* [HGM] EGT */
9512     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9513       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9514         SendToProgram(buf, cps);
9515     }
9516
9517     SendToProgram(cps->initString, cps);
9518     if (gameInfo.variant != VariantNormal &&
9519         gameInfo.variant != VariantLoadable
9520         /* [HGM] also send variant if board size non-standard */
9521         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9522                                             ) {
9523       char *v = VariantName(gameInfo.variant);
9524       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9525         /* [HGM] in protocol 1 we have to assume all variants valid */
9526         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9527         DisplayFatalError(buf, 0, 1);
9528         return;
9529       }
9530
9531       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9532       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9533       if( gameInfo.variant == VariantXiangqi )
9534            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9535       if( gameInfo.variant == VariantShogi )
9536            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9537       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9538            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9539       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9540           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9541            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9542       if( gameInfo.variant == VariantCourier )
9543            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9544       if( gameInfo.variant == VariantSuper )
9545            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9546       if( gameInfo.variant == VariantGreat )
9547            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9548       if( gameInfo.variant == VariantSChess )
9549            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9550       if( gameInfo.variant == VariantGrand )
9551            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9552
9553       if(overruled) {
9554         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9555                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9556            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9557            if(StrStr(cps->variants, b) == NULL) {
9558                // specific sized variant not known, check if general sizing allowed
9559                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9560                    if(StrStr(cps->variants, "boardsize") == NULL) {
9561                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9562                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9563                        DisplayFatalError(buf, 0, 1);
9564                        return;
9565                    }
9566                    /* [HGM] here we really should compare with the maximum supported board size */
9567                }
9568            }
9569       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9570       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9571       SendToProgram(buf, cps);
9572     }
9573     currentlyInitializedVariant = gameInfo.variant;
9574
9575     /* [HGM] send opening position in FRC to first engine */
9576     if(setup) {
9577           SendToProgram("force\n", cps);
9578           SendBoard(cps, 0);
9579           /* engine is now in force mode! Set flag to wake it up after first move. */
9580           setboardSpoiledMachineBlack = 1;
9581     }
9582
9583     if (cps->sendICS) {
9584       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9585       SendToProgram(buf, cps);
9586     }
9587     cps->maybeThinking = FALSE;
9588     cps->offeredDraw = 0;
9589     if (!appData.icsActive) {
9590         SendTimeControl(cps, movesPerSession, timeControl,
9591                         timeIncrement, appData.searchDepth,
9592                         searchTime);
9593     }
9594     if (appData.showThinking
9595         // [HGM] thinking: four options require thinking output to be sent
9596         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9597                                 ) {
9598         SendToProgram("post\n", cps);
9599     }
9600     SendToProgram("hard\n", cps);
9601     if (!appData.ponderNextMove) {
9602         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9603            it without being sure what state we are in first.  "hard"
9604            is not a toggle, so that one is OK.
9605          */
9606         SendToProgram("easy\n", cps);
9607     }
9608     if (cps->usePing) {
9609       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9610       SendToProgram(buf, cps);
9611     }
9612     cps->initDone = TRUE;
9613     ClearEngineOutputPane(cps == &second);
9614 }
9615
9616
9617 void
9618 StartChessProgram(cps)
9619      ChessProgramState *cps;
9620 {
9621     char buf[MSG_SIZ];
9622     int err;
9623
9624     if (appData.noChessProgram) return;
9625     cps->initDone = FALSE;
9626
9627     if (strcmp(cps->host, "localhost") == 0) {
9628         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9629     } else if (*appData.remoteShell == NULLCHAR) {
9630         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9631     } else {
9632         if (*appData.remoteUser == NULLCHAR) {
9633           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9634                     cps->program);
9635         } else {
9636           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9637                     cps->host, appData.remoteUser, cps->program);
9638         }
9639         err = StartChildProcess(buf, "", &cps->pr);
9640     }
9641
9642     if (err != 0) {
9643       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9644         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9645         if(cps != &first) return;
9646         appData.noChessProgram = TRUE;
9647         ThawUI();
9648         SetNCPMode();
9649 //      DisplayFatalError(buf, err, 1);
9650 //      cps->pr = NoProc;
9651 //      cps->isr = NULL;
9652         return;
9653     }
9654
9655     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9656     if (cps->protocolVersion > 1) {
9657       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9658       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9659       cps->comboCnt = 0;  //                and values of combo boxes
9660       SendToProgram(buf, cps);
9661     } else {
9662       SendToProgram("xboard\n", cps);
9663     }
9664 }
9665
9666 void
9667 TwoMachinesEventIfReady P((void))
9668 {
9669   static int curMess = 0;
9670   if (first.lastPing != first.lastPong) {
9671     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9672     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9673     return;
9674   }
9675   if (second.lastPing != second.lastPong) {
9676     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9677     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9678     return;
9679   }
9680   DisplayMessage("", ""); curMess = 0;
9681   ThawUI();
9682   TwoMachinesEvent();
9683 }
9684
9685 char *
9686 MakeName(char *template)
9687 {
9688     time_t clock;
9689     struct tm *tm;
9690     static char buf[MSG_SIZ];
9691     char *p = buf;
9692     int i;
9693
9694     clock = time((time_t *)NULL);
9695     tm = localtime(&clock);
9696
9697     while(*p++ = *template++) if(p[-1] == '%') {
9698         switch(*template++) {
9699           case 0:   *p = 0; return buf;
9700           case 'Y': i = tm->tm_year+1900; break;
9701           case 'y': i = tm->tm_year-100; break;
9702           case 'M': i = tm->tm_mon+1; break;
9703           case 'd': i = tm->tm_mday; break;
9704           case 'h': i = tm->tm_hour; break;
9705           case 'm': i = tm->tm_min; break;
9706           case 's': i = tm->tm_sec; break;
9707           default:  i = 0;
9708         }
9709         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9710     }
9711     return buf;
9712 }
9713
9714 int
9715 CountPlayers(char *p)
9716 {
9717     int n = 0;
9718     while(p = strchr(p, '\n')) p++, n++; // count participants
9719     return n;
9720 }
9721
9722 FILE *
9723 WriteTourneyFile(char *results)
9724 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9725     FILE *f = fopen(appData.tourneyFile, "w");
9726     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9727         // create a file with tournament description
9728         fprintf(f, "-participants {%s}\n", appData.participants);
9729         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9730         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9731         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9732         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9733         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9734         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9735         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9736         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9737         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9738         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9739         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9740         if(searchTime > 0)
9741                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9742         else {
9743                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9744                 fprintf(f, "-tc %s\n", appData.timeControl);
9745                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9746         }
9747         fprintf(f, "-results \"%s\"\n", results);
9748     }
9749     return f;
9750 }
9751
9752 int
9753 CreateTourney(char *name)
9754 {
9755         FILE *f;
9756         if(name[0] == NULLCHAR) {
9757             if(appData.participants[0])
9758                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9759             return 0;
9760         }
9761         f = fopen(name, "r");
9762         if(f) { // file exists
9763             ASSIGN(appData.tourneyFile, name);
9764             ParseArgsFromFile(f); // parse it
9765         } else {
9766             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9767             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9768                 DisplayError(_("Not enough participants"), 0);
9769                 return 0;
9770             }
9771             ASSIGN(appData.tourneyFile, name);
9772             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9773             if((f = WriteTourneyFile("")) == NULL) return 0;
9774         }
9775         fclose(f);
9776         appData.noChessProgram = FALSE;
9777         appData.clockMode = TRUE;
9778         SetGNUMode();
9779         return 1;
9780 }
9781
9782 #define MAXENGINES 1000
9783 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9784
9785 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9786 {
9787     char buf[MSG_SIZ], *p, *q;
9788     int i=1;
9789     while(*names) {
9790         p = names; q = buf;
9791         while(*p && *p != '\n') *q++ = *p++;
9792         *q = 0;
9793         if(engineList[i]) free(engineList[i]);
9794         engineList[i] = strdup(buf);
9795         if(*p == '\n') p++;
9796         TidyProgramName(engineList[i], "localhost", buf);
9797         if(engineMnemonic[i]) free(engineMnemonic[i]);
9798         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9799             strcat(buf, " (");
9800             sscanf(q + 8, "%s", buf + strlen(buf));
9801             strcat(buf, ")");
9802         }
9803         engineMnemonic[i] = strdup(buf);
9804         names = p; i++;
9805       if(i > MAXENGINES - 2) break;
9806     }
9807     engineList[i] = NULL;
9808 }
9809
9810 // following implemented as macro to avoid type limitations
9811 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9812
9813 void SwapEngines(int n)
9814 {   // swap settings for first engine and other engine (so far only some selected options)
9815     int h;
9816     char *p;
9817     if(n == 0) return;
9818     SWAP(directory, p)
9819     SWAP(chessProgram, p)
9820     SWAP(isUCI, h)
9821     SWAP(hasOwnBookUCI, h)
9822     SWAP(protocolVersion, h)
9823     SWAP(reuse, h)
9824     SWAP(scoreIsAbsolute, h)
9825     SWAP(timeOdds, h)
9826     SWAP(logo, p)
9827     SWAP(pgnName, p)
9828     SWAP(pvSAN, h)
9829 }
9830
9831 void
9832 SetPlayer(int player)
9833 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9834     int i;
9835     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9836     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9837     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9838     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9839     if(mnemonic[i]) {
9840         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9841         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9842         ParseArgsFromString(buf);
9843     }
9844     free(engineName);
9845 }
9846
9847 int
9848 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9849 {   // determine players from game number
9850     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9851
9852     if(appData.tourneyType == 0) {
9853         roundsPerCycle = (nPlayers - 1) | 1;
9854         pairingsPerRound = nPlayers / 2;
9855     } else if(appData.tourneyType > 0) {
9856         roundsPerCycle = nPlayers - appData.tourneyType;
9857         pairingsPerRound = appData.tourneyType;
9858     }
9859     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9860     gamesPerCycle = gamesPerRound * roundsPerCycle;
9861     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9862     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9863     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9864     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9865     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9866     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9867
9868     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9869     if(appData.roundSync) *syncInterval = gamesPerRound;
9870
9871     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9872
9873     if(appData.tourneyType == 0) {
9874         if(curPairing == (nPlayers-1)/2 ) {
9875             *whitePlayer = curRound;
9876             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9877         } else {
9878             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9879             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9880             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9881             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9882         }
9883     } else if(appData.tourneyType > 0) {
9884         *whitePlayer = curPairing;
9885         *blackPlayer = curRound + appData.tourneyType;
9886     }
9887
9888     // take care of white/black alternation per round. 
9889     // For cycles and games this is already taken care of by default, derived from matchGame!
9890     return curRound & 1;
9891 }
9892
9893 int
9894 NextTourneyGame(int nr, int *swapColors)
9895 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9896     char *p, *q;
9897     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9898     FILE *tf;
9899     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9900     tf = fopen(appData.tourneyFile, "r");
9901     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9902     ParseArgsFromFile(tf); fclose(tf);
9903     InitTimeControls(); // TC might be altered from tourney file
9904
9905     nPlayers = CountPlayers(appData.participants); // count participants
9906     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9907     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9908
9909     if(syncInterval) {
9910         p = q = appData.results;
9911         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9912         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9913             DisplayMessage(_("Waiting for other game(s)"),"");
9914             waitingForGame = TRUE;
9915             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9916             return 0;
9917         }
9918         waitingForGame = FALSE;
9919     }
9920
9921     if(appData.tourneyType < 0) {
9922         if(nr>=0 && !pairingReceived) {
9923             char buf[1<<16];
9924             if(pairing.pr == NoProc) {
9925                 if(!appData.pairingEngine[0]) {
9926                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9927                     return 0;
9928                 }
9929                 StartChessProgram(&pairing); // starts the pairing engine
9930             }
9931             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9932             SendToProgram(buf, &pairing);
9933             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9934             SendToProgram(buf, &pairing);
9935             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9936         }
9937         pairingReceived = 0;                              // ... so we continue here 
9938         *swapColors = 0;
9939         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9940         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9941         matchGame = 1; roundNr = nr / syncInterval + 1;
9942     }
9943
9944     if(first.pr != NoProc) return 1; // engines already loaded
9945
9946     // redefine engines, engine dir, etc.
9947     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9948     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9949     SwapEngines(1);
9950     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9951     SwapEngines(1);         // and make that valid for second engine by swapping
9952     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9953     InitEngine(&second, 1);
9954     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9955     UpdateLogos(FALSE);     // leave display to ModeHiglight()
9956     return 1;
9957 }
9958
9959 void
9960 NextMatchGame()
9961 {   // performs game initialization that does not invoke engines, and then tries to start the game
9962     int firstWhite, swapColors = 0;
9963     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9964     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9965     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9966     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9967     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9968     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9969     Reset(FALSE, first.pr != NoProc);
9970     appData.noChessProgram = FALSE;
9971     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9972     TwoMachinesEvent();
9973 }
9974
9975 void UserAdjudicationEvent( int result )
9976 {
9977     ChessMove gameResult = GameIsDrawn;
9978
9979     if( result > 0 ) {
9980         gameResult = WhiteWins;
9981     }
9982     else if( result < 0 ) {
9983         gameResult = BlackWins;
9984     }
9985
9986     if( gameMode == TwoMachinesPlay ) {
9987         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9988     }
9989 }
9990
9991
9992 // [HGM] save: calculate checksum of game to make games easily identifiable
9993 int StringCheckSum(char *s)
9994 {
9995         int i = 0;
9996         if(s==NULL) return 0;
9997         while(*s) i = i*259 + *s++;
9998         return i;
9999 }
10000
10001 int GameCheckSum()
10002 {
10003         int i, sum=0;
10004         for(i=backwardMostMove; i<forwardMostMove; i++) {
10005                 sum += pvInfoList[i].depth;
10006                 sum += StringCheckSum(parseList[i]);
10007                 sum += StringCheckSum(commentList[i]);
10008                 sum *= 261;
10009         }
10010         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10011         return sum + StringCheckSum(commentList[i]);
10012 } // end of save patch
10013
10014 void
10015 GameEnds(result, resultDetails, whosays)
10016      ChessMove result;
10017      char *resultDetails;
10018      int whosays;
10019 {
10020     GameMode nextGameMode;
10021     int isIcsGame;
10022     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10023
10024     if(endingGame) return; /* [HGM] crash: forbid recursion */
10025     endingGame = 1;
10026     if(twoBoards) { // [HGM] dual: switch back to one board
10027         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10028         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10029     }
10030     if (appData.debugMode) {
10031       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10032               result, resultDetails ? resultDetails : "(null)", whosays);
10033     }
10034
10035     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10036
10037     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10038         /* If we are playing on ICS, the server decides when the
10039            game is over, but the engine can offer to draw, claim
10040            a draw, or resign.
10041          */
10042 #if ZIPPY
10043         if (appData.zippyPlay && first.initDone) {
10044             if (result == GameIsDrawn) {
10045                 /* In case draw still needs to be claimed */
10046                 SendToICS(ics_prefix);
10047                 SendToICS("draw\n");
10048             } else if (StrCaseStr(resultDetails, "resign")) {
10049                 SendToICS(ics_prefix);
10050                 SendToICS("resign\n");
10051             }
10052         }
10053 #endif
10054         endingGame = 0; /* [HGM] crash */
10055         return;
10056     }
10057
10058     /* If we're loading the game from a file, stop */
10059     if (whosays == GE_FILE) {
10060       (void) StopLoadGameTimer();
10061       gameFileFP = NULL;
10062     }
10063
10064     /* Cancel draw offers */
10065     first.offeredDraw = second.offeredDraw = 0;
10066
10067     /* If this is an ICS game, only ICS can really say it's done;
10068        if not, anyone can. */
10069     isIcsGame = (gameMode == IcsPlayingWhite ||
10070                  gameMode == IcsPlayingBlack ||
10071                  gameMode == IcsObserving    ||
10072                  gameMode == IcsExamining);
10073
10074     if (!isIcsGame || whosays == GE_ICS) {
10075         /* OK -- not an ICS game, or ICS said it was done */
10076         StopClocks();
10077         if (!isIcsGame && !appData.noChessProgram)
10078           SetUserThinkingEnables();
10079
10080         /* [HGM] if a machine claims the game end we verify this claim */
10081         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10082             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10083                 char claimer;
10084                 ChessMove trueResult = (ChessMove) -1;
10085
10086                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10087                                             first.twoMachinesColor[0] :
10088                                             second.twoMachinesColor[0] ;
10089
10090                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10091                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10092                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10093                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10094                 } else
10095                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10096                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10097                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10098                 } else
10099                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10100                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10101                 }
10102
10103                 // now verify win claims, but not in drop games, as we don't understand those yet
10104                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10105                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10106                     (result == WhiteWins && claimer == 'w' ||
10107                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10108                       if (appData.debugMode) {
10109                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10110                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10111                       }
10112                       if(result != trueResult) {
10113                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10114                               result = claimer == 'w' ? BlackWins : WhiteWins;
10115                               resultDetails = buf;
10116                       }
10117                 } else
10118                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10119                     && (forwardMostMove <= backwardMostMove ||
10120                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10121                         (claimer=='b')==(forwardMostMove&1))
10122                                                                                   ) {
10123                       /* [HGM] verify: draws that were not flagged are false claims */
10124                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10125                       result = claimer == 'w' ? BlackWins : WhiteWins;
10126                       resultDetails = buf;
10127                 }
10128                 /* (Claiming a loss is accepted no questions asked!) */
10129             }
10130             /* [HGM] bare: don't allow bare King to win */
10131             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10132                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10133                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10134                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10135                && result != GameIsDrawn)
10136             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10137                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10138                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10139                         if(p >= 0 && p <= (int)WhiteKing) k++;
10140                 }
10141                 if (appData.debugMode) {
10142                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10143                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10144                 }
10145                 if(k <= 1) {
10146                         result = GameIsDrawn;
10147                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10148                         resultDetails = buf;
10149                 }
10150             }
10151         }
10152
10153
10154         if(serverMoves != NULL && !loadFlag) { char c = '=';
10155             if(result==WhiteWins) c = '+';
10156             if(result==BlackWins) c = '-';
10157             if(resultDetails != NULL)
10158                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10159         }
10160         if (resultDetails != NULL) {
10161             gameInfo.result = result;
10162             gameInfo.resultDetails = StrSave(resultDetails);
10163
10164             /* display last move only if game was not loaded from file */
10165             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10166                 DisplayMove(currentMove - 1);
10167
10168             if (forwardMostMove != 0) {
10169                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10170                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10171                                                                 ) {
10172                     if (*appData.saveGameFile != NULLCHAR) {
10173                         SaveGameToFile(appData.saveGameFile, TRUE);
10174                     } else if (appData.autoSaveGames) {
10175                         AutoSaveGame();
10176                     }
10177                     if (*appData.savePositionFile != NULLCHAR) {
10178                         SavePositionToFile(appData.savePositionFile);
10179                     }
10180                 }
10181             }
10182
10183             /* Tell program how game ended in case it is learning */
10184             /* [HGM] Moved this to after saving the PGN, just in case */
10185             /* engine died and we got here through time loss. In that */
10186             /* case we will get a fatal error writing the pipe, which */
10187             /* would otherwise lose us the PGN.                       */
10188             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10189             /* output during GameEnds should never be fatal anymore   */
10190             if (gameMode == MachinePlaysWhite ||
10191                 gameMode == MachinePlaysBlack ||
10192                 gameMode == TwoMachinesPlay ||
10193                 gameMode == IcsPlayingWhite ||
10194                 gameMode == IcsPlayingBlack ||
10195                 gameMode == BeginningOfGame) {
10196                 char buf[MSG_SIZ];
10197                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10198                         resultDetails);
10199                 if (first.pr != NoProc) {
10200                     SendToProgram(buf, &first);
10201                 }
10202                 if (second.pr != NoProc &&
10203                     gameMode == TwoMachinesPlay) {
10204                     SendToProgram(buf, &second);
10205                 }
10206             }
10207         }
10208
10209         if (appData.icsActive) {
10210             if (appData.quietPlay &&
10211                 (gameMode == IcsPlayingWhite ||
10212                  gameMode == IcsPlayingBlack)) {
10213                 SendToICS(ics_prefix);
10214                 SendToICS("set shout 1\n");
10215             }
10216             nextGameMode = IcsIdle;
10217             ics_user_moved = FALSE;
10218             /* clean up premove.  It's ugly when the game has ended and the
10219              * premove highlights are still on the board.
10220              */
10221             if (gotPremove) {
10222               gotPremove = FALSE;
10223               ClearPremoveHighlights();
10224               DrawPosition(FALSE, boards[currentMove]);
10225             }
10226             if (whosays == GE_ICS) {
10227                 switch (result) {
10228                 case WhiteWins:
10229                     if (gameMode == IcsPlayingWhite)
10230                         PlayIcsWinSound();
10231                     else if(gameMode == IcsPlayingBlack)
10232                         PlayIcsLossSound();
10233                     break;
10234                 case BlackWins:
10235                     if (gameMode == IcsPlayingBlack)
10236                         PlayIcsWinSound();
10237                     else if(gameMode == IcsPlayingWhite)
10238                         PlayIcsLossSound();
10239                     break;
10240                 case GameIsDrawn:
10241                     PlayIcsDrawSound();
10242                     break;
10243                 default:
10244                     PlayIcsUnfinishedSound();
10245                 }
10246             }
10247         } else if (gameMode == EditGame ||
10248                    gameMode == PlayFromGameFile ||
10249                    gameMode == AnalyzeMode ||
10250                    gameMode == AnalyzeFile) {
10251             nextGameMode = gameMode;
10252         } else {
10253             nextGameMode = EndOfGame;
10254         }
10255         pausing = FALSE;
10256         ModeHighlight();
10257     } else {
10258         nextGameMode = gameMode;
10259     }
10260
10261     if (appData.noChessProgram) {
10262         gameMode = nextGameMode;
10263         ModeHighlight();
10264         endingGame = 0; /* [HGM] crash */
10265         return;
10266     }
10267
10268     if (first.reuse) {
10269         /* Put first chess program into idle state */
10270         if (first.pr != NoProc &&
10271             (gameMode == MachinePlaysWhite ||
10272              gameMode == MachinePlaysBlack ||
10273              gameMode == TwoMachinesPlay ||
10274              gameMode == IcsPlayingWhite ||
10275              gameMode == IcsPlayingBlack ||
10276              gameMode == BeginningOfGame)) {
10277             SendToProgram("force\n", &first);
10278             if (first.usePing) {
10279               char buf[MSG_SIZ];
10280               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10281               SendToProgram(buf, &first);
10282             }
10283         }
10284     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10285         /* Kill off first chess program */
10286         if (first.isr != NULL)
10287           RemoveInputSource(first.isr);
10288         first.isr = NULL;
10289
10290         if (first.pr != NoProc) {
10291             ExitAnalyzeMode();
10292             DoSleep( appData.delayBeforeQuit );
10293             SendToProgram("quit\n", &first);
10294             DoSleep( appData.delayAfterQuit );
10295             DestroyChildProcess(first.pr, first.useSigterm);
10296         }
10297         first.pr = NoProc;
10298     }
10299     if (second.reuse) {
10300         /* Put second chess program into idle state */
10301         if (second.pr != NoProc &&
10302             gameMode == TwoMachinesPlay) {
10303             SendToProgram("force\n", &second);
10304             if (second.usePing) {
10305               char buf[MSG_SIZ];
10306               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10307               SendToProgram(buf, &second);
10308             }
10309         }
10310     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10311         /* Kill off second chess program */
10312         if (second.isr != NULL)
10313           RemoveInputSource(second.isr);
10314         second.isr = NULL;
10315
10316         if (second.pr != NoProc) {
10317             DoSleep( appData.delayBeforeQuit );
10318             SendToProgram("quit\n", &second);
10319             DoSleep( appData.delayAfterQuit );
10320             DestroyChildProcess(second.pr, second.useSigterm);
10321         }
10322         second.pr = NoProc;
10323     }
10324
10325     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10326         char resChar = '=';
10327         switch (result) {
10328         case WhiteWins:
10329           resChar = '+';
10330           if (first.twoMachinesColor[0] == 'w') {
10331             first.matchWins++;
10332           } else {
10333             second.matchWins++;
10334           }
10335           break;
10336         case BlackWins:
10337           resChar = '-';
10338           if (first.twoMachinesColor[0] == 'b') {
10339             first.matchWins++;
10340           } else {
10341             second.matchWins++;
10342           }
10343           break;
10344         case GameUnfinished:
10345           resChar = ' ';
10346         default:
10347           break;
10348         }
10349
10350         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10351         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10352             ReserveGame(nextGame, resChar); // sets nextGame
10353             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10354             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10355         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10356
10357         if (nextGame <= appData.matchGames && !abortMatch) {
10358             gameMode = nextGameMode;
10359             matchGame = nextGame; // this will be overruled in tourney mode!
10360             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10361             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10362             endingGame = 0; /* [HGM] crash */
10363             return;
10364         } else {
10365             gameMode = nextGameMode;
10366             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10367                      first.tidy, second.tidy,
10368                      first.matchWins, second.matchWins,
10369                      appData.matchGames - (first.matchWins + second.matchWins));
10370             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10371             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10372             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10373                 first.twoMachinesColor = "black\n";
10374                 second.twoMachinesColor = "white\n";
10375             } else {
10376                 first.twoMachinesColor = "white\n";
10377                 second.twoMachinesColor = "black\n";
10378             }
10379         }
10380     }
10381     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10382         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10383       ExitAnalyzeMode();
10384     gameMode = nextGameMode;
10385     ModeHighlight();
10386     endingGame = 0;  /* [HGM] crash */
10387     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10388         if(matchMode == TRUE) { // match through command line: exit with or without popup
10389             if(ranking) {
10390                 ToNrEvent(forwardMostMove);
10391                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10392                 else ExitEvent(0);
10393             } else DisplayFatalError(buf, 0, 0);
10394         } else { // match through menu; just stop, with or without popup
10395             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10396             ModeHighlight();
10397             if(ranking){
10398                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10399             } else DisplayNote(buf);
10400       }
10401       if(ranking) free(ranking);
10402     }
10403 }
10404
10405 /* Assumes program was just initialized (initString sent).
10406    Leaves program in force mode. */
10407 void
10408 FeedMovesToProgram(cps, upto)
10409      ChessProgramState *cps;
10410      int upto;
10411 {
10412     int i;
10413
10414     if (appData.debugMode)
10415       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10416               startedFromSetupPosition ? "position and " : "",
10417               backwardMostMove, upto, cps->which);
10418     if(currentlyInitializedVariant != gameInfo.variant) {
10419       char buf[MSG_SIZ];
10420         // [HGM] variantswitch: make engine aware of new variant
10421         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10422                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10423         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10424         SendToProgram(buf, cps);
10425         currentlyInitializedVariant = gameInfo.variant;
10426     }
10427     SendToProgram("force\n", cps);
10428     if (startedFromSetupPosition) {
10429         SendBoard(cps, backwardMostMove);
10430     if (appData.debugMode) {
10431         fprintf(debugFP, "feedMoves\n");
10432     }
10433     }
10434     for (i = backwardMostMove; i < upto; i++) {
10435         SendMoveToProgram(i, cps);
10436     }
10437 }
10438
10439
10440 int
10441 ResurrectChessProgram()
10442 {
10443      /* The chess program may have exited.
10444         If so, restart it and feed it all the moves made so far. */
10445     static int doInit = 0;
10446
10447     if (appData.noChessProgram) return 1;
10448
10449     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10450         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10451         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10452         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10453     } else {
10454         if (first.pr != NoProc) return 1;
10455         StartChessProgram(&first);
10456     }
10457     InitChessProgram(&first, FALSE);
10458     FeedMovesToProgram(&first, currentMove);
10459
10460     if (!first.sendTime) {
10461         /* can't tell gnuchess what its clock should read,
10462            so we bow to its notion. */
10463         ResetClocks();
10464         timeRemaining[0][currentMove] = whiteTimeRemaining;
10465         timeRemaining[1][currentMove] = blackTimeRemaining;
10466     }
10467
10468     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10469                 appData.icsEngineAnalyze) && first.analysisSupport) {
10470       SendToProgram("analyze\n", &first);
10471       first.analyzing = TRUE;
10472     }
10473     return 1;
10474 }
10475
10476 /*
10477  * Button procedures
10478  */
10479 void
10480 Reset(redraw, init)
10481      int redraw, init;
10482 {
10483     int i;
10484
10485     if (appData.debugMode) {
10486         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10487                 redraw, init, gameMode);
10488     }
10489     CleanupTail(); // [HGM] vari: delete any stored variations
10490     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10491     pausing = pauseExamInvalid = FALSE;
10492     startedFromSetupPosition = blackPlaysFirst = FALSE;
10493     firstMove = TRUE;
10494     whiteFlag = blackFlag = FALSE;
10495     userOfferedDraw = FALSE;
10496     hintRequested = bookRequested = FALSE;
10497     first.maybeThinking = FALSE;
10498     second.maybeThinking = FALSE;
10499     first.bookSuspend = FALSE; // [HGM] book
10500     second.bookSuspend = FALSE;
10501     thinkOutput[0] = NULLCHAR;
10502     lastHint[0] = NULLCHAR;
10503     ClearGameInfo(&gameInfo);
10504     gameInfo.variant = StringToVariant(appData.variant);
10505     ics_user_moved = ics_clock_paused = FALSE;
10506     ics_getting_history = H_FALSE;
10507     ics_gamenum = -1;
10508     white_holding[0] = black_holding[0] = NULLCHAR;
10509     ClearProgramStats();
10510     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10511
10512     ResetFrontEnd();
10513     ClearHighlights();
10514     flipView = appData.flipView;
10515     ClearPremoveHighlights();
10516     gotPremove = FALSE;
10517     alarmSounded = FALSE;
10518
10519     GameEnds(EndOfFile, NULL, GE_PLAYER);
10520     if(appData.serverMovesName != NULL) {
10521         /* [HGM] prepare to make moves file for broadcasting */
10522         clock_t t = clock();
10523         if(serverMoves != NULL) fclose(serverMoves);
10524         serverMoves = fopen(appData.serverMovesName, "r");
10525         if(serverMoves != NULL) {
10526             fclose(serverMoves);
10527             /* delay 15 sec before overwriting, so all clients can see end */
10528             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10529         }
10530         serverMoves = fopen(appData.serverMovesName, "w");
10531     }
10532
10533     ExitAnalyzeMode();
10534     gameMode = BeginningOfGame;
10535     ModeHighlight();
10536     if(appData.icsActive) gameInfo.variant = VariantNormal;
10537     currentMove = forwardMostMove = backwardMostMove = 0;
10538     InitPosition(redraw);
10539     for (i = 0; i < MAX_MOVES; i++) {
10540         if (commentList[i] != NULL) {
10541             free(commentList[i]);
10542             commentList[i] = NULL;
10543         }
10544     }
10545     ResetClocks();
10546     timeRemaining[0][0] = whiteTimeRemaining;
10547     timeRemaining[1][0] = blackTimeRemaining;
10548
10549     if (first.pr == NULL) {
10550         StartChessProgram(&first);
10551     }
10552     if (init) {
10553             InitChessProgram(&first, startedFromSetupPosition);
10554     }
10555     DisplayTitle("");
10556     DisplayMessage("", "");
10557     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10558     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10559 }
10560
10561 void
10562 AutoPlayGameLoop()
10563 {
10564     for (;;) {
10565         if (!AutoPlayOneMove())
10566           return;
10567         if (matchMode || appData.timeDelay == 0)
10568           continue;
10569         if (appData.timeDelay < 0)
10570           return;
10571         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10572         break;
10573     }
10574 }
10575
10576
10577 int
10578 AutoPlayOneMove()
10579 {
10580     int fromX, fromY, toX, toY;
10581
10582     if (appData.debugMode) {
10583       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10584     }
10585
10586     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10587       return FALSE;
10588
10589     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10590       pvInfoList[currentMove].depth = programStats.depth;
10591       pvInfoList[currentMove].score = programStats.score;
10592       pvInfoList[currentMove].time  = 0;
10593       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10594     }
10595
10596     if (currentMove >= forwardMostMove) {
10597       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10598 //      gameMode = EndOfGame;
10599 //      ModeHighlight();
10600
10601       /* [AS] Clear current move marker at the end of a game */
10602       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10603
10604       return FALSE;
10605     }
10606
10607     toX = moveList[currentMove][2] - AAA;
10608     toY = moveList[currentMove][3] - ONE;
10609
10610     if (moveList[currentMove][1] == '@') {
10611         if (appData.highlightLastMove) {
10612             SetHighlights(-1, -1, toX, toY);
10613         }
10614     } else {
10615         fromX = moveList[currentMove][0] - AAA;
10616         fromY = moveList[currentMove][1] - ONE;
10617
10618         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10619
10620         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10621
10622         if (appData.highlightLastMove) {
10623             SetHighlights(fromX, fromY, toX, toY);
10624         }
10625     }
10626     DisplayMove(currentMove);
10627     SendMoveToProgram(currentMove++, &first);
10628     DisplayBothClocks();
10629     DrawPosition(FALSE, boards[currentMove]);
10630     // [HGM] PV info: always display, routine tests if empty
10631     DisplayComment(currentMove - 1, commentList[currentMove]);
10632     return TRUE;
10633 }
10634
10635
10636 int
10637 LoadGameOneMove(readAhead)
10638      ChessMove readAhead;
10639 {
10640     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10641     char promoChar = NULLCHAR;
10642     ChessMove moveType;
10643     char move[MSG_SIZ];
10644     char *p, *q;
10645
10646     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10647         gameMode != AnalyzeMode && gameMode != Training) {
10648         gameFileFP = NULL;
10649         return FALSE;
10650     }
10651
10652     yyboardindex = forwardMostMove;
10653     if (readAhead != EndOfFile) {
10654       moveType = readAhead;
10655     } else {
10656       if (gameFileFP == NULL)
10657           return FALSE;
10658       moveType = (ChessMove) Myylex();
10659     }
10660
10661     done = FALSE;
10662     switch (moveType) {
10663       case Comment:
10664         if (appData.debugMode)
10665           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10666         p = yy_text;
10667
10668         /* append the comment but don't display it */
10669         AppendComment(currentMove, p, FALSE);
10670         return TRUE;
10671
10672       case WhiteCapturesEnPassant:
10673       case BlackCapturesEnPassant:
10674       case WhitePromotion:
10675       case BlackPromotion:
10676       case WhiteNonPromotion:
10677       case BlackNonPromotion:
10678       case NormalMove:
10679       case WhiteKingSideCastle:
10680       case WhiteQueenSideCastle:
10681       case BlackKingSideCastle:
10682       case BlackQueenSideCastle:
10683       case WhiteKingSideCastleWild:
10684       case WhiteQueenSideCastleWild:
10685       case BlackKingSideCastleWild:
10686       case BlackQueenSideCastleWild:
10687       /* PUSH Fabien */
10688       case WhiteHSideCastleFR:
10689       case WhiteASideCastleFR:
10690       case BlackHSideCastleFR:
10691       case BlackASideCastleFR:
10692       /* POP Fabien */
10693         if (appData.debugMode)
10694           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10695         fromX = currentMoveString[0] - AAA;
10696         fromY = currentMoveString[1] - ONE;
10697         toX = currentMoveString[2] - AAA;
10698         toY = currentMoveString[3] - ONE;
10699         promoChar = currentMoveString[4];
10700         break;
10701
10702       case WhiteDrop:
10703       case BlackDrop:
10704         if (appData.debugMode)
10705           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10706         fromX = moveType == WhiteDrop ?
10707           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10708         (int) CharToPiece(ToLower(currentMoveString[0]));
10709         fromY = DROP_RANK;
10710         toX = currentMoveString[2] - AAA;
10711         toY = currentMoveString[3] - ONE;
10712         break;
10713
10714       case WhiteWins:
10715       case BlackWins:
10716       case GameIsDrawn:
10717       case GameUnfinished:
10718         if (appData.debugMode)
10719           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10720         p = strchr(yy_text, '{');
10721         if (p == NULL) p = strchr(yy_text, '(');
10722         if (p == NULL) {
10723             p = yy_text;
10724             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10725         } else {
10726             q = strchr(p, *p == '{' ? '}' : ')');
10727             if (q != NULL) *q = NULLCHAR;
10728             p++;
10729         }
10730         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10731         GameEnds(moveType, p, GE_FILE);
10732         done = TRUE;
10733         if (cmailMsgLoaded) {
10734             ClearHighlights();
10735             flipView = WhiteOnMove(currentMove);
10736             if (moveType == GameUnfinished) flipView = !flipView;
10737             if (appData.debugMode)
10738               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10739         }
10740         break;
10741
10742       case EndOfFile:
10743         if (appData.debugMode)
10744           fprintf(debugFP, "Parser hit end of file\n");
10745         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10746           case MT_NONE:
10747           case MT_CHECK:
10748             break;
10749           case MT_CHECKMATE:
10750           case MT_STAINMATE:
10751             if (WhiteOnMove(currentMove)) {
10752                 GameEnds(BlackWins, "Black mates", GE_FILE);
10753             } else {
10754                 GameEnds(WhiteWins, "White mates", GE_FILE);
10755             }
10756             break;
10757           case MT_STALEMATE:
10758             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10759             break;
10760         }
10761         done = TRUE;
10762         break;
10763
10764       case MoveNumberOne:
10765         if (lastLoadGameStart == GNUChessGame) {
10766             /* GNUChessGames have numbers, but they aren't move numbers */
10767             if (appData.debugMode)
10768               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10769                       yy_text, (int) moveType);
10770             return LoadGameOneMove(EndOfFile); /* tail recursion */
10771         }
10772         /* else fall thru */
10773
10774       case XBoardGame:
10775       case GNUChessGame:
10776       case PGNTag:
10777         /* Reached start of next game in file */
10778         if (appData.debugMode)
10779           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10780         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10781           case MT_NONE:
10782           case MT_CHECK:
10783             break;
10784           case MT_CHECKMATE:
10785           case MT_STAINMATE:
10786             if (WhiteOnMove(currentMove)) {
10787                 GameEnds(BlackWins, "Black mates", GE_FILE);
10788             } else {
10789                 GameEnds(WhiteWins, "White mates", GE_FILE);
10790             }
10791             break;
10792           case MT_STALEMATE:
10793             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10794             break;
10795         }
10796         done = TRUE;
10797         break;
10798
10799       case PositionDiagram:     /* should not happen; ignore */
10800       case ElapsedTime:         /* ignore */
10801       case NAG:                 /* ignore */
10802         if (appData.debugMode)
10803           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10804                   yy_text, (int) moveType);
10805         return LoadGameOneMove(EndOfFile); /* tail recursion */
10806
10807       case IllegalMove:
10808         if (appData.testLegality) {
10809             if (appData.debugMode)
10810               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10811             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10812                     (forwardMostMove / 2) + 1,
10813                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10814             DisplayError(move, 0);
10815             done = TRUE;
10816         } else {
10817             if (appData.debugMode)
10818               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10819                       yy_text, currentMoveString);
10820             fromX = currentMoveString[0] - AAA;
10821             fromY = currentMoveString[1] - ONE;
10822             toX = currentMoveString[2] - AAA;
10823             toY = currentMoveString[3] - ONE;
10824             promoChar = currentMoveString[4];
10825         }
10826         break;
10827
10828       case AmbiguousMove:
10829         if (appData.debugMode)
10830           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10831         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10832                 (forwardMostMove / 2) + 1,
10833                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10834         DisplayError(move, 0);
10835         done = TRUE;
10836         break;
10837
10838       default:
10839       case ImpossibleMove:
10840         if (appData.debugMode)
10841           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10842         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10843                 (forwardMostMove / 2) + 1,
10844                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10845         DisplayError(move, 0);
10846         done = TRUE;
10847         break;
10848     }
10849
10850     if (done) {
10851         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10852             DrawPosition(FALSE, boards[currentMove]);
10853             DisplayBothClocks();
10854             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10855               DisplayComment(currentMove - 1, commentList[currentMove]);
10856         }
10857         (void) StopLoadGameTimer();
10858         gameFileFP = NULL;
10859         cmailOldMove = forwardMostMove;
10860         return FALSE;
10861     } else {
10862         /* currentMoveString is set as a side-effect of yylex */
10863
10864         thinkOutput[0] = NULLCHAR;
10865         MakeMove(fromX, fromY, toX, toY, promoChar);
10866         currentMove = forwardMostMove;
10867         return TRUE;
10868     }
10869 }
10870
10871 /* Load the nth game from the given file */
10872 int
10873 LoadGameFromFile(filename, n, title, useList)
10874      char *filename;
10875      int n;
10876      char *title;
10877      /*Boolean*/ int useList;
10878 {
10879     FILE *f;
10880     char buf[MSG_SIZ];
10881
10882     if (strcmp(filename, "-") == 0) {
10883         f = stdin;
10884         title = "stdin";
10885     } else {
10886         f = fopen(filename, "rb");
10887         if (f == NULL) {
10888           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10889             DisplayError(buf, errno);
10890             return FALSE;
10891         }
10892     }
10893     if (fseek(f, 0, 0) == -1) {
10894         /* f is not seekable; probably a pipe */
10895         useList = FALSE;
10896     }
10897     if (useList && n == 0) {
10898         int error = GameListBuild(f);
10899         if (error) {
10900             DisplayError(_("Cannot build game list"), error);
10901         } else if (!ListEmpty(&gameList) &&
10902                    ((ListGame *) gameList.tailPred)->number > 1) {
10903             GameListPopUp(f, title);
10904             return TRUE;
10905         }
10906         GameListDestroy();
10907         n = 1;
10908     }
10909     if (n == 0) n = 1;
10910     return LoadGame(f, n, title, FALSE);
10911 }
10912
10913
10914 void
10915 MakeRegisteredMove()
10916 {
10917     int fromX, fromY, toX, toY;
10918     char promoChar;
10919     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10920         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10921           case CMAIL_MOVE:
10922           case CMAIL_DRAW:
10923             if (appData.debugMode)
10924               fprintf(debugFP, "Restoring %s for game %d\n",
10925                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10926
10927             thinkOutput[0] = NULLCHAR;
10928             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10929             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10930             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10931             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10932             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10933             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10934             MakeMove(fromX, fromY, toX, toY, promoChar);
10935             ShowMove(fromX, fromY, toX, toY);
10936
10937             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10938               case MT_NONE:
10939               case MT_CHECK:
10940                 break;
10941
10942               case MT_CHECKMATE:
10943               case MT_STAINMATE:
10944                 if (WhiteOnMove(currentMove)) {
10945                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10946                 } else {
10947                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10948                 }
10949                 break;
10950
10951               case MT_STALEMATE:
10952                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10953                 break;
10954             }
10955
10956             break;
10957
10958           case CMAIL_RESIGN:
10959             if (WhiteOnMove(currentMove)) {
10960                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10961             } else {
10962                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10963             }
10964             break;
10965
10966           case CMAIL_ACCEPT:
10967             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10968             break;
10969
10970           default:
10971             break;
10972         }
10973     }
10974
10975     return;
10976 }
10977
10978 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10979 int
10980 CmailLoadGame(f, gameNumber, title, useList)
10981      FILE *f;
10982      int gameNumber;
10983      char *title;
10984      int useList;
10985 {
10986     int retVal;
10987
10988     if (gameNumber > nCmailGames) {
10989         DisplayError(_("No more games in this message"), 0);
10990         return FALSE;
10991     }
10992     if (f == lastLoadGameFP) {
10993         int offset = gameNumber - lastLoadGameNumber;
10994         if (offset == 0) {
10995             cmailMsg[0] = NULLCHAR;
10996             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10997                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10998                 nCmailMovesRegistered--;
10999             }
11000             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11001             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11002                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11003             }
11004         } else {
11005             if (! RegisterMove()) return FALSE;
11006         }
11007     }
11008
11009     retVal = LoadGame(f, gameNumber, title, useList);
11010
11011     /* Make move registered during previous look at this game, if any */
11012     MakeRegisteredMove();
11013
11014     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11015         commentList[currentMove]
11016           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11017         DisplayComment(currentMove - 1, commentList[currentMove]);
11018     }
11019
11020     return retVal;
11021 }
11022
11023 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11024 int
11025 ReloadGame(offset)
11026      int offset;
11027 {
11028     int gameNumber = lastLoadGameNumber + offset;
11029     if (lastLoadGameFP == NULL) {
11030         DisplayError(_("No game has been loaded yet"), 0);
11031         return FALSE;
11032     }
11033     if (gameNumber <= 0) {
11034         DisplayError(_("Can't back up any further"), 0);
11035         return FALSE;
11036     }
11037     if (cmailMsgLoaded) {
11038         return CmailLoadGame(lastLoadGameFP, gameNumber,
11039                              lastLoadGameTitle, lastLoadGameUseList);
11040     } else {
11041         return LoadGame(lastLoadGameFP, gameNumber,
11042                         lastLoadGameTitle, lastLoadGameUseList);
11043     }
11044 }
11045
11046 int keys[EmptySquare+1];
11047
11048 int
11049 PositionMatches(Board b1, Board b2)
11050 {
11051     int r, f, sum=0;
11052     switch(appData.searchMode) {
11053         case 1: return CompareWithRights(b1, b2);
11054         case 2:
11055             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11056                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11057             }
11058             return TRUE;
11059         case 3:
11060             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11061               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11062                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11063             }
11064             return sum==0;
11065         case 4:
11066             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11067                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11068             }
11069             return sum==0;
11070     }
11071     return TRUE;
11072 }
11073
11074 GameInfo dummyInfo;
11075
11076 int GameContainsPosition(FILE *f, ListGame *lg)
11077 {
11078     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11079     int fromX, fromY, toX, toY;
11080     char promoChar;
11081     static int initDone=FALSE;
11082
11083     if(!initDone) {
11084         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = rand()>>8 ^ rand()<<6 ^rand()<<20;
11085         initDone = TRUE;
11086     }
11087     dummyInfo.variant = VariantNormal;
11088     FREE(dummyInfo.fen); dummyInfo.fen = NULL;
11089     dummyInfo.whiteRating = 0;
11090     dummyInfo.blackRating = 0;
11091     FREE(dummyInfo.date); dummyInfo.date = NULL;
11092     fseek(f, lg->offset, 0);
11093     yynewfile(f);
11094     CopyBoard(boards[scratch], initialPosition); // default start position
11095     while(1) {
11096         yyboardindex = scratch + (plyNr&1);
11097       quickFlag = 1;
11098         next = Myylex();
11099       quickFlag = 0;
11100         switch(next) {
11101             case PGNTag:
11102                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11103                 ParsePGNTag(yy_text, &dummyInfo);
11104             if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL;
11105             default:
11106                 continue;
11107
11108             case XBoardGame:
11109             case GNUChessGame:
11110                 if(plyNr) return -1; // after we have seen moves, this is for new game
11111               continue;
11112
11113             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11114             case ImpossibleMove:
11115             case WhiteWins: // game ends here with these four
11116             case BlackWins:
11117             case GameIsDrawn:
11118             case GameUnfinished:
11119                 return -1;
11120
11121             case IllegalMove:
11122                 if(appData.testLegality) return -1;
11123             case WhiteCapturesEnPassant:
11124             case BlackCapturesEnPassant:
11125             case WhitePromotion:
11126             case BlackPromotion:
11127             case WhiteNonPromotion:
11128             case BlackNonPromotion:
11129             case NormalMove:
11130             case WhiteKingSideCastle:
11131             case WhiteQueenSideCastle:
11132             case BlackKingSideCastle:
11133             case BlackQueenSideCastle:
11134             case WhiteKingSideCastleWild:
11135             case WhiteQueenSideCastleWild:
11136             case BlackKingSideCastleWild:
11137             case BlackQueenSideCastleWild:
11138             case WhiteHSideCastleFR:
11139             case WhiteASideCastleFR:
11140             case BlackHSideCastleFR:
11141             case BlackASideCastleFR:
11142                 fromX = currentMoveString[0] - AAA;
11143                 fromY = currentMoveString[1] - ONE;
11144                 toX = currentMoveString[2] - AAA;
11145                 toY = currentMoveString[3] - ONE;
11146                 promoChar = currentMoveString[4];
11147                 break;
11148             case WhiteDrop:
11149             case BlackDrop:
11150                 fromX = next == WhiteDrop ?
11151                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11152                   (int) CharToPiece(ToLower(currentMoveString[0]));
11153                 fromY = DROP_RANK;
11154                 toX = currentMoveString[2] - AAA;
11155                 toY = currentMoveString[3] - ONE;
11156                 break;
11157         }
11158         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11159         if(plyNr == 0) { // but first figure out variant and initial position
11160             if(dummyInfo.variant != gameInfo.variant) return -1; // wrong variant
11161             if(appData.eloThreshold1 && (dummyInfo.whiteRating < appData.eloThreshold1 && dummyInfo.blackRating < appData.eloThreshold1)) return -1;
11162             if(appData.eloThreshold2 && (dummyInfo.whiteRating < appData.eloThreshold2 || dummyInfo.blackRating < appData.eloThreshold2)) return -1;
11163             if(appData.dateThreshold && (!dummyInfo.date || atoi(dummyInfo.date) < appData.dateThreshold)) return -1;
11164             if(btm) CopyBoard(boards[scratch+1], boards[scratch]), plyNr++;
11165             if(PositionMatches(boards[scratch + plyNr], boards[currentMove])) return plyNr;
11166         }
11167         CopyBoard(boards[scratch + (plyNr+1&1)], boards[scratch + (plyNr&1)]);
11168         plyNr++;
11169         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch + (plyNr&1)]);
11170         if(PositionMatches(boards[scratch + (plyNr&1)], boards[currentMove])) return plyNr;
11171     }
11172 }
11173
11174 /* Load the nth game from open file f */
11175 int
11176 LoadGame(f, gameNumber, title, useList)
11177      FILE *f;
11178      int gameNumber;
11179      char *title;
11180      int useList;
11181 {
11182     ChessMove cm;
11183     char buf[MSG_SIZ];
11184     int gn = gameNumber;
11185     ListGame *lg = NULL;
11186     int numPGNTags = 0;
11187     int err, pos = -1;
11188     GameMode oldGameMode;
11189     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11190
11191     if (appData.debugMode)
11192         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11193
11194     if (gameMode == Training )
11195         SetTrainingModeOff();
11196
11197     oldGameMode = gameMode;
11198     if (gameMode != BeginningOfGame) {
11199       Reset(FALSE, TRUE);
11200     }
11201
11202     gameFileFP = f;
11203     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11204         fclose(lastLoadGameFP);
11205     }
11206
11207     if (useList) {
11208         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11209
11210         if (lg) {
11211             fseek(f, lg->offset, 0);
11212             GameListHighlight(gameNumber);
11213             pos = lg->position;
11214             gn = 1;
11215         }
11216         else {
11217             DisplayError(_("Game number out of range"), 0);
11218             return FALSE;
11219         }
11220     } else {
11221         GameListDestroy();
11222         if (fseek(f, 0, 0) == -1) {
11223             if (f == lastLoadGameFP ?
11224                 gameNumber == lastLoadGameNumber + 1 :
11225                 gameNumber == 1) {
11226                 gn = 1;
11227             } else {
11228                 DisplayError(_("Can't seek on game file"), 0);
11229                 return FALSE;
11230             }
11231         }
11232     }
11233     lastLoadGameFP = f;
11234     lastLoadGameNumber = gameNumber;
11235     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11236     lastLoadGameUseList = useList;
11237
11238     yynewfile(f);
11239
11240     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11241       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11242                 lg->gameInfo.black);
11243             DisplayTitle(buf);
11244     } else if (*title != NULLCHAR) {
11245         if (gameNumber > 1) {
11246           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11247             DisplayTitle(buf);
11248         } else {
11249             DisplayTitle(title);
11250         }
11251     }
11252
11253     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11254         gameMode = PlayFromGameFile;
11255         ModeHighlight();
11256     }
11257
11258     currentMove = forwardMostMove = backwardMostMove = 0;
11259     CopyBoard(boards[0], initialPosition);
11260     StopClocks();
11261
11262     /*
11263      * Skip the first gn-1 games in the file.
11264      * Also skip over anything that precedes an identifiable
11265      * start of game marker, to avoid being confused by
11266      * garbage at the start of the file.  Currently
11267      * recognized start of game markers are the move number "1",
11268      * the pattern "gnuchess .* game", the pattern
11269      * "^[#;%] [^ ]* game file", and a PGN tag block.
11270      * A game that starts with one of the latter two patterns
11271      * will also have a move number 1, possibly
11272      * following a position diagram.
11273      * 5-4-02: Let's try being more lenient and allowing a game to
11274      * start with an unnumbered move.  Does that break anything?
11275      */
11276     cm = lastLoadGameStart = EndOfFile;
11277     while (gn > 0) {
11278         yyboardindex = forwardMostMove;
11279         cm = (ChessMove) Myylex();
11280         switch (cm) {
11281           case EndOfFile:
11282             if (cmailMsgLoaded) {
11283                 nCmailGames = CMAIL_MAX_GAMES - gn;
11284             } else {
11285                 Reset(TRUE, TRUE);
11286                 DisplayError(_("Game not found in file"), 0);
11287             }
11288             return FALSE;
11289
11290           case GNUChessGame:
11291           case XBoardGame:
11292             gn--;
11293             lastLoadGameStart = cm;
11294             break;
11295
11296           case MoveNumberOne:
11297             switch (lastLoadGameStart) {
11298               case GNUChessGame:
11299               case XBoardGame:
11300               case PGNTag:
11301                 break;
11302               case MoveNumberOne:
11303               case EndOfFile:
11304                 gn--;           /* count this game */
11305                 lastLoadGameStart = cm;
11306                 break;
11307               default:
11308                 /* impossible */
11309                 break;
11310             }
11311             break;
11312
11313           case PGNTag:
11314             switch (lastLoadGameStart) {
11315               case GNUChessGame:
11316               case PGNTag:
11317               case MoveNumberOne:
11318               case EndOfFile:
11319                 gn--;           /* count this game */
11320                 lastLoadGameStart = cm;
11321                 break;
11322               case XBoardGame:
11323                 lastLoadGameStart = cm; /* game counted already */
11324                 break;
11325               default:
11326                 /* impossible */
11327                 break;
11328             }
11329             if (gn > 0) {
11330                 do {
11331                     yyboardindex = forwardMostMove;
11332                     cm = (ChessMove) Myylex();
11333                 } while (cm == PGNTag || cm == Comment);
11334             }
11335             break;
11336
11337           case WhiteWins:
11338           case BlackWins:
11339           case GameIsDrawn:
11340             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11341                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11342                     != CMAIL_OLD_RESULT) {
11343                     nCmailResults ++ ;
11344                     cmailResult[  CMAIL_MAX_GAMES
11345                                 - gn - 1] = CMAIL_OLD_RESULT;
11346                 }
11347             }
11348             break;
11349
11350           case NormalMove:
11351             /* Only a NormalMove can be at the start of a game
11352              * without a position diagram. */
11353             if (lastLoadGameStart == EndOfFile ) {
11354               gn--;
11355               lastLoadGameStart = MoveNumberOne;
11356             }
11357             break;
11358
11359           default:
11360             break;
11361         }
11362     }
11363
11364     if (appData.debugMode)
11365       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11366
11367     if (cm == XBoardGame) {
11368         /* Skip any header junk before position diagram and/or move 1 */
11369         for (;;) {
11370             yyboardindex = forwardMostMove;
11371             cm = (ChessMove) Myylex();
11372
11373             if (cm == EndOfFile ||
11374                 cm == GNUChessGame || cm == XBoardGame) {
11375                 /* Empty game; pretend end-of-file and handle later */
11376                 cm = EndOfFile;
11377                 break;
11378             }
11379
11380             if (cm == MoveNumberOne || cm == PositionDiagram ||
11381                 cm == PGNTag || cm == Comment)
11382               break;
11383         }
11384     } else if (cm == GNUChessGame) {
11385         if (gameInfo.event != NULL) {
11386             free(gameInfo.event);
11387         }
11388         gameInfo.event = StrSave(yy_text);
11389     }
11390
11391     startedFromSetupPosition = FALSE;
11392     while (cm == PGNTag) {
11393         if (appData.debugMode)
11394           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11395         err = ParsePGNTag(yy_text, &gameInfo);
11396         if (!err) numPGNTags++;
11397
11398         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11399         if(gameInfo.variant != oldVariant) {
11400             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11401             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11402             InitPosition(TRUE);
11403             oldVariant = gameInfo.variant;
11404             if (appData.debugMode)
11405               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11406         }
11407
11408
11409         if (gameInfo.fen != NULL) {
11410           Board initial_position;
11411           startedFromSetupPosition = TRUE;
11412           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11413             Reset(TRUE, TRUE);
11414             DisplayError(_("Bad FEN position in file"), 0);
11415             return FALSE;
11416           }
11417           CopyBoard(boards[0], initial_position);
11418           if (blackPlaysFirst) {
11419             currentMove = forwardMostMove = backwardMostMove = 1;
11420             CopyBoard(boards[1], initial_position);
11421             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11422             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11423             timeRemaining[0][1] = whiteTimeRemaining;
11424             timeRemaining[1][1] = blackTimeRemaining;
11425             if (commentList[0] != NULL) {
11426               commentList[1] = commentList[0];
11427               commentList[0] = NULL;
11428             }
11429           } else {
11430             currentMove = forwardMostMove = backwardMostMove = 0;
11431           }
11432           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11433           {   int i;
11434               initialRulePlies = FENrulePlies;
11435               for( i=0; i< nrCastlingRights; i++ )
11436                   initialRights[i] = initial_position[CASTLING][i];
11437           }
11438           yyboardindex = forwardMostMove;
11439           free(gameInfo.fen);
11440           gameInfo.fen = NULL;
11441         }
11442
11443         yyboardindex = forwardMostMove;
11444         cm = (ChessMove) Myylex();
11445
11446         /* Handle comments interspersed among the tags */
11447         while (cm == Comment) {
11448             char *p;
11449             if (appData.debugMode)
11450               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11451             p = yy_text;
11452             AppendComment(currentMove, p, FALSE);
11453             yyboardindex = forwardMostMove;
11454             cm = (ChessMove) Myylex();
11455         }
11456     }
11457
11458     /* don't rely on existence of Event tag since if game was
11459      * pasted from clipboard the Event tag may not exist
11460      */
11461     if (numPGNTags > 0){
11462         char *tags;
11463         if (gameInfo.variant == VariantNormal) {
11464           VariantClass v = StringToVariant(gameInfo.event);
11465           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11466           if(v < VariantShogi) gameInfo.variant = v;
11467         }
11468         if (!matchMode) {
11469           if( appData.autoDisplayTags ) {
11470             tags = PGNTags(&gameInfo);
11471             TagsPopUp(tags, CmailMsg());
11472             free(tags);
11473           }
11474         }
11475     } else {
11476         /* Make something up, but don't display it now */
11477         SetGameInfo();
11478         TagsPopDown();
11479     }
11480
11481     if (cm == PositionDiagram) {
11482         int i, j;
11483         char *p;
11484         Board initial_position;
11485
11486         if (appData.debugMode)
11487           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11488
11489         if (!startedFromSetupPosition) {
11490             p = yy_text;
11491             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11492               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11493                 switch (*p) {
11494                   case '{':
11495                   case '[':
11496                   case '-':
11497                   case ' ':
11498                   case '\t':
11499                   case '\n':
11500                   case '\r':
11501                     break;
11502                   default:
11503                     initial_position[i][j++] = CharToPiece(*p);
11504                     break;
11505                 }
11506             while (*p == ' ' || *p == '\t' ||
11507                    *p == '\n' || *p == '\r') p++;
11508
11509             if (strncmp(p, "black", strlen("black"))==0)
11510               blackPlaysFirst = TRUE;
11511             else
11512               blackPlaysFirst = FALSE;
11513             startedFromSetupPosition = TRUE;
11514
11515             CopyBoard(boards[0], initial_position);
11516             if (blackPlaysFirst) {
11517                 currentMove = forwardMostMove = backwardMostMove = 1;
11518                 CopyBoard(boards[1], initial_position);
11519                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11520                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11521                 timeRemaining[0][1] = whiteTimeRemaining;
11522                 timeRemaining[1][1] = blackTimeRemaining;
11523                 if (commentList[0] != NULL) {
11524                     commentList[1] = commentList[0];
11525                     commentList[0] = NULL;
11526                 }
11527             } else {
11528                 currentMove = forwardMostMove = backwardMostMove = 0;
11529             }
11530         }
11531         yyboardindex = forwardMostMove;
11532         cm = (ChessMove) Myylex();
11533     }
11534
11535     if (first.pr == NoProc) {
11536         StartChessProgram(&first);
11537     }
11538     InitChessProgram(&first, FALSE);
11539     SendToProgram("force\n", &first);
11540     if (startedFromSetupPosition) {
11541         SendBoard(&first, forwardMostMove);
11542     if (appData.debugMode) {
11543         fprintf(debugFP, "Load Game\n");
11544     }
11545         DisplayBothClocks();
11546     }
11547
11548     /* [HGM] server: flag to write setup moves in broadcast file as one */
11549     loadFlag = appData.suppressLoadMoves;
11550
11551     while (cm == Comment) {
11552         char *p;
11553         if (appData.debugMode)
11554           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11555         p = yy_text;
11556         AppendComment(currentMove, p, FALSE);
11557         yyboardindex = forwardMostMove;
11558         cm = (ChessMove) Myylex();
11559     }
11560
11561     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11562         cm == WhiteWins || cm == BlackWins ||
11563         cm == GameIsDrawn || cm == GameUnfinished) {
11564         DisplayMessage("", _("No moves in game"));
11565         if (cmailMsgLoaded) {
11566             if (appData.debugMode)
11567               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11568             ClearHighlights();
11569             flipView = FALSE;
11570         }
11571         DrawPosition(FALSE, boards[currentMove]);
11572         DisplayBothClocks();
11573         gameMode = EditGame;
11574         ModeHighlight();
11575         gameFileFP = NULL;
11576         cmailOldMove = 0;
11577         return TRUE;
11578     }
11579
11580     // [HGM] PV info: routine tests if comment empty
11581     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11582         DisplayComment(currentMove - 1, commentList[currentMove]);
11583     }
11584     if (!matchMode && appData.timeDelay != 0)
11585       DrawPosition(FALSE, boards[currentMove]);
11586
11587     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11588       programStats.ok_to_send = 1;
11589     }
11590
11591     /* if the first token after the PGN tags is a move
11592      * and not move number 1, retrieve it from the parser
11593      */
11594     if (cm != MoveNumberOne)
11595         LoadGameOneMove(cm);
11596
11597     /* load the remaining moves from the file */
11598     while (LoadGameOneMove(EndOfFile)) {
11599       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11600       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11601     }
11602
11603     /* rewind to the start of the game */
11604     currentMove = backwardMostMove;
11605
11606     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11607
11608     if (oldGameMode == AnalyzeFile ||
11609         oldGameMode == AnalyzeMode) {
11610       AnalyzeFileEvent();
11611     }
11612
11613     if (!matchMode && pos >= 0) {
11614         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11615     } else
11616     if (matchMode || appData.timeDelay == 0) {
11617       ToEndEvent();
11618     } else if (appData.timeDelay > 0) {
11619       AutoPlayGameLoop();
11620     }
11621
11622     if (appData.debugMode)
11623         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11624
11625     loadFlag = 0; /* [HGM] true game starts */
11626     return TRUE;
11627 }
11628
11629 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11630 int
11631 ReloadPosition(offset)
11632      int offset;
11633 {
11634     int positionNumber = lastLoadPositionNumber + offset;
11635     if (lastLoadPositionFP == NULL) {
11636         DisplayError(_("No position has been loaded yet"), 0);
11637         return FALSE;
11638     }
11639     if (positionNumber <= 0) {
11640         DisplayError(_("Can't back up any further"), 0);
11641         return FALSE;
11642     }
11643     return LoadPosition(lastLoadPositionFP, positionNumber,
11644                         lastLoadPositionTitle);
11645 }
11646
11647 /* Load the nth position from the given file */
11648 int
11649 LoadPositionFromFile(filename, n, title)
11650      char *filename;
11651      int n;
11652      char *title;
11653 {
11654     FILE *f;
11655     char buf[MSG_SIZ];
11656
11657     if (strcmp(filename, "-") == 0) {
11658         return LoadPosition(stdin, n, "stdin");
11659     } else {
11660         f = fopen(filename, "rb");
11661         if (f == NULL) {
11662             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11663             DisplayError(buf, errno);
11664             return FALSE;
11665         } else {
11666             return LoadPosition(f, n, title);
11667         }
11668     }
11669 }
11670
11671 /* Load the nth position from the given open file, and close it */
11672 int
11673 LoadPosition(f, positionNumber, title)
11674      FILE *f;
11675      int positionNumber;
11676      char *title;
11677 {
11678     char *p, line[MSG_SIZ];
11679     Board initial_position;
11680     int i, j, fenMode, pn;
11681
11682     if (gameMode == Training )
11683         SetTrainingModeOff();
11684
11685     if (gameMode != BeginningOfGame) {
11686         Reset(FALSE, TRUE);
11687     }
11688     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11689         fclose(lastLoadPositionFP);
11690     }
11691     if (positionNumber == 0) positionNumber = 1;
11692     lastLoadPositionFP = f;
11693     lastLoadPositionNumber = positionNumber;
11694     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11695     if (first.pr == NoProc) {
11696       StartChessProgram(&first);
11697       InitChessProgram(&first, FALSE);
11698     }
11699     pn = positionNumber;
11700     if (positionNumber < 0) {
11701         /* Negative position number means to seek to that byte offset */
11702         if (fseek(f, -positionNumber, 0) == -1) {
11703             DisplayError(_("Can't seek on position file"), 0);
11704             return FALSE;
11705         };
11706         pn = 1;
11707     } else {
11708         if (fseek(f, 0, 0) == -1) {
11709             if (f == lastLoadPositionFP ?
11710                 positionNumber == lastLoadPositionNumber + 1 :
11711                 positionNumber == 1) {
11712                 pn = 1;
11713             } else {
11714                 DisplayError(_("Can't seek on position file"), 0);
11715                 return FALSE;
11716             }
11717         }
11718     }
11719     /* See if this file is FEN or old-style xboard */
11720     if (fgets(line, MSG_SIZ, f) == NULL) {
11721         DisplayError(_("Position not found in file"), 0);
11722         return FALSE;
11723     }
11724     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11725     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11726
11727     if (pn >= 2) {
11728         if (fenMode || line[0] == '#') pn--;
11729         while (pn > 0) {
11730             /* skip positions before number pn */
11731             if (fgets(line, MSG_SIZ, f) == NULL) {
11732                 Reset(TRUE, TRUE);
11733                 DisplayError(_("Position not found in file"), 0);
11734                 return FALSE;
11735             }
11736             if (fenMode || line[0] == '#') pn--;
11737         }
11738     }
11739
11740     if (fenMode) {
11741         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11742             DisplayError(_("Bad FEN position in file"), 0);
11743             return FALSE;
11744         }
11745     } else {
11746         (void) fgets(line, MSG_SIZ, f);
11747         (void) fgets(line, MSG_SIZ, f);
11748
11749         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11750             (void) fgets(line, MSG_SIZ, f);
11751             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11752                 if (*p == ' ')
11753                   continue;
11754                 initial_position[i][j++] = CharToPiece(*p);
11755             }
11756         }
11757
11758         blackPlaysFirst = FALSE;
11759         if (!feof(f)) {
11760             (void) fgets(line, MSG_SIZ, f);
11761             if (strncmp(line, "black", strlen("black"))==0)
11762               blackPlaysFirst = TRUE;
11763         }
11764     }
11765     startedFromSetupPosition = TRUE;
11766
11767     SendToProgram("force\n", &first);
11768     CopyBoard(boards[0], initial_position);
11769     if (blackPlaysFirst) {
11770         currentMove = forwardMostMove = backwardMostMove = 1;
11771         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11772         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11773         CopyBoard(boards[1], initial_position);
11774         DisplayMessage("", _("Black to play"));
11775     } else {
11776         currentMove = forwardMostMove = backwardMostMove = 0;
11777         DisplayMessage("", _("White to play"));
11778     }
11779     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11780     SendBoard(&first, forwardMostMove);
11781     if (appData.debugMode) {
11782 int i, j;
11783   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11784   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11785         fprintf(debugFP, "Load Position\n");
11786     }
11787
11788     if (positionNumber > 1) {
11789       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11790         DisplayTitle(line);
11791     } else {
11792         DisplayTitle(title);
11793     }
11794     gameMode = EditGame;
11795     ModeHighlight();
11796     ResetClocks();
11797     timeRemaining[0][1] = whiteTimeRemaining;
11798     timeRemaining[1][1] = blackTimeRemaining;
11799     DrawPosition(FALSE, boards[currentMove]);
11800
11801     return TRUE;
11802 }
11803
11804
11805 void
11806 CopyPlayerNameIntoFileName(dest, src)
11807      char **dest, *src;
11808 {
11809     while (*src != NULLCHAR && *src != ',') {
11810         if (*src == ' ') {
11811             *(*dest)++ = '_';
11812             src++;
11813         } else {
11814             *(*dest)++ = *src++;
11815         }
11816     }
11817 }
11818
11819 char *DefaultFileName(ext)
11820      char *ext;
11821 {
11822     static char def[MSG_SIZ];
11823     char *p;
11824
11825     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11826         p = def;
11827         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11828         *p++ = '-';
11829         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11830         *p++ = '.';
11831         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11832     } else {
11833         def[0] = NULLCHAR;
11834     }
11835     return def;
11836 }
11837
11838 /* Save the current game to the given file */
11839 int
11840 SaveGameToFile(filename, append)
11841      char *filename;
11842      int append;
11843 {
11844     FILE *f;
11845     char buf[MSG_SIZ];
11846     int result;
11847
11848     if (strcmp(filename, "-") == 0) {
11849         return SaveGame(stdout, 0, NULL);
11850     } else {
11851         f = fopen(filename, append ? "a" : "w");
11852         if (f == NULL) {
11853             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11854             DisplayError(buf, errno);
11855             return FALSE;
11856         } else {
11857             safeStrCpy(buf, lastMsg, MSG_SIZ);
11858             DisplayMessage(_("Waiting for access to save file"), "");
11859             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11860             DisplayMessage(_("Saving game"), "");
11861             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11862             result = SaveGame(f, 0, NULL);
11863             DisplayMessage(buf, "");
11864             return result;
11865         }
11866     }
11867 }
11868
11869 char *
11870 SavePart(str)
11871      char *str;
11872 {
11873     static char buf[MSG_SIZ];
11874     char *p;
11875
11876     p = strchr(str, ' ');
11877     if (p == NULL) return str;
11878     strncpy(buf, str, p - str);
11879     buf[p - str] = NULLCHAR;
11880     return buf;
11881 }
11882
11883 #define PGN_MAX_LINE 75
11884
11885 #define PGN_SIDE_WHITE  0
11886 #define PGN_SIDE_BLACK  1
11887
11888 /* [AS] */
11889 static int FindFirstMoveOutOfBook( int side )
11890 {
11891     int result = -1;
11892
11893     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11894         int index = backwardMostMove;
11895         int has_book_hit = 0;
11896
11897         if( (index % 2) != side ) {
11898             index++;
11899         }
11900
11901         while( index < forwardMostMove ) {
11902             /* Check to see if engine is in book */
11903             int depth = pvInfoList[index].depth;
11904             int score = pvInfoList[index].score;
11905             int in_book = 0;
11906
11907             if( depth <= 2 ) {
11908                 in_book = 1;
11909             }
11910             else if( score == 0 && depth == 63 ) {
11911                 in_book = 1; /* Zappa */
11912             }
11913             else if( score == 2 && depth == 99 ) {
11914                 in_book = 1; /* Abrok */
11915             }
11916
11917             has_book_hit += in_book;
11918
11919             if( ! in_book ) {
11920                 result = index;
11921
11922                 break;
11923             }
11924
11925             index += 2;
11926         }
11927     }
11928
11929     return result;
11930 }
11931
11932 /* [AS] */
11933 void GetOutOfBookInfo( char * buf )
11934 {
11935     int oob[2];
11936     int i;
11937     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11938
11939     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11940     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11941
11942     *buf = '\0';
11943
11944     if( oob[0] >= 0 || oob[1] >= 0 ) {
11945         for( i=0; i<2; i++ ) {
11946             int idx = oob[i];
11947
11948             if( idx >= 0 ) {
11949                 if( i > 0 && oob[0] >= 0 ) {
11950                     strcat( buf, "   " );
11951                 }
11952
11953                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11954                 sprintf( buf+strlen(buf), "%s%.2f",
11955                     pvInfoList[idx].score >= 0 ? "+" : "",
11956                     pvInfoList[idx].score / 100.0 );
11957             }
11958         }
11959     }
11960 }
11961
11962 /* Save game in PGN style and close the file */
11963 int
11964 SaveGamePGN(f)
11965      FILE *f;
11966 {
11967     int i, offset, linelen, newblock;
11968     time_t tm;
11969 //    char *movetext;
11970     char numtext[32];
11971     int movelen, numlen, blank;
11972     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11973
11974     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11975
11976     tm = time((time_t *) NULL);
11977
11978     PrintPGNTags(f, &gameInfo);
11979
11980     if (backwardMostMove > 0 || startedFromSetupPosition) {
11981         char *fen = PositionToFEN(backwardMostMove, NULL);
11982         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11983         fprintf(f, "\n{--------------\n");
11984         PrintPosition(f, backwardMostMove);
11985         fprintf(f, "--------------}\n");
11986         free(fen);
11987     }
11988     else {
11989         /* [AS] Out of book annotation */
11990         if( appData.saveOutOfBookInfo ) {
11991             char buf[64];
11992
11993             GetOutOfBookInfo( buf );
11994
11995             if( buf[0] != '\0' ) {
11996                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11997             }
11998         }
11999
12000         fprintf(f, "\n");
12001     }
12002
12003     i = backwardMostMove;
12004     linelen = 0;
12005     newblock = TRUE;
12006
12007     while (i < forwardMostMove) {
12008         /* Print comments preceding this move */
12009         if (commentList[i] != NULL) {
12010             if (linelen > 0) fprintf(f, "\n");
12011             fprintf(f, "%s", commentList[i]);
12012             linelen = 0;
12013             newblock = TRUE;
12014         }
12015
12016         /* Format move number */
12017         if ((i % 2) == 0)
12018           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12019         else
12020           if (newblock)
12021             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12022           else
12023             numtext[0] = NULLCHAR;
12024
12025         numlen = strlen(numtext);
12026         newblock = FALSE;
12027
12028         /* Print move number */
12029         blank = linelen > 0 && numlen > 0;
12030         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12031             fprintf(f, "\n");
12032             linelen = 0;
12033             blank = 0;
12034         }
12035         if (blank) {
12036             fprintf(f, " ");
12037             linelen++;
12038         }
12039         fprintf(f, "%s", numtext);
12040         linelen += numlen;
12041
12042         /* Get move */
12043         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12044         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12045
12046         /* Print move */
12047         blank = linelen > 0 && movelen > 0;
12048         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12049             fprintf(f, "\n");
12050             linelen = 0;
12051             blank = 0;
12052         }
12053         if (blank) {
12054             fprintf(f, " ");
12055             linelen++;
12056         }
12057         fprintf(f, "%s", move_buffer);
12058         linelen += movelen;
12059
12060         /* [AS] Add PV info if present */
12061         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12062             /* [HGM] add time */
12063             char buf[MSG_SIZ]; int seconds;
12064
12065             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12066
12067             if( seconds <= 0)
12068               buf[0] = 0;
12069             else
12070               if( seconds < 30 )
12071                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12072               else
12073                 {
12074                   seconds = (seconds + 4)/10; // round to full seconds
12075                   if( seconds < 60 )
12076                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12077                   else
12078                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12079                 }
12080
12081             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12082                       pvInfoList[i].score >= 0 ? "+" : "",
12083                       pvInfoList[i].score / 100.0,
12084                       pvInfoList[i].depth,
12085                       buf );
12086
12087             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12088
12089             /* Print score/depth */
12090             blank = linelen > 0 && movelen > 0;
12091             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12092                 fprintf(f, "\n");
12093                 linelen = 0;
12094                 blank = 0;
12095             }
12096             if (blank) {
12097                 fprintf(f, " ");
12098                 linelen++;
12099             }
12100             fprintf(f, "%s", move_buffer);
12101             linelen += movelen;
12102         }
12103
12104         i++;
12105     }
12106
12107     /* Start a new line */
12108     if (linelen > 0) fprintf(f, "\n");
12109
12110     /* Print comments after last move */
12111     if (commentList[i] != NULL) {
12112         fprintf(f, "%s\n", commentList[i]);
12113     }
12114
12115     /* Print result */
12116     if (gameInfo.resultDetails != NULL &&
12117         gameInfo.resultDetails[0] != NULLCHAR) {
12118         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12119                 PGNResult(gameInfo.result));
12120     } else {
12121         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12122     }
12123
12124     fclose(f);
12125     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12126     return TRUE;
12127 }
12128
12129 /* Save game in old style and close the file */
12130 int
12131 SaveGameOldStyle(f)
12132      FILE *f;
12133 {
12134     int i, offset;
12135     time_t tm;
12136
12137     tm = time((time_t *) NULL);
12138
12139     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12140     PrintOpponents(f);
12141
12142     if (backwardMostMove > 0 || startedFromSetupPosition) {
12143         fprintf(f, "\n[--------------\n");
12144         PrintPosition(f, backwardMostMove);
12145         fprintf(f, "--------------]\n");
12146     } else {
12147         fprintf(f, "\n");
12148     }
12149
12150     i = backwardMostMove;
12151     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12152
12153     while (i < forwardMostMove) {
12154         if (commentList[i] != NULL) {
12155             fprintf(f, "[%s]\n", commentList[i]);
12156         }
12157
12158         if ((i % 2) == 1) {
12159             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12160             i++;
12161         } else {
12162             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12163             i++;
12164             if (commentList[i] != NULL) {
12165                 fprintf(f, "\n");
12166                 continue;
12167             }
12168             if (i >= forwardMostMove) {
12169                 fprintf(f, "\n");
12170                 break;
12171             }
12172             fprintf(f, "%s\n", parseList[i]);
12173             i++;
12174         }
12175     }
12176
12177     if (commentList[i] != NULL) {
12178         fprintf(f, "[%s]\n", commentList[i]);
12179     }
12180
12181     /* This isn't really the old style, but it's close enough */
12182     if (gameInfo.resultDetails != NULL &&
12183         gameInfo.resultDetails[0] != NULLCHAR) {
12184         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12185                 gameInfo.resultDetails);
12186     } else {
12187         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12188     }
12189
12190     fclose(f);
12191     return TRUE;
12192 }
12193
12194 /* Save the current game to open file f and close the file */
12195 int
12196 SaveGame(f, dummy, dummy2)
12197      FILE *f;
12198      int dummy;
12199      char *dummy2;
12200 {
12201     if (gameMode == EditPosition) EditPositionDone(TRUE);
12202     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12203     if (appData.oldSaveStyle)
12204       return SaveGameOldStyle(f);
12205     else
12206       return SaveGamePGN(f);
12207 }
12208
12209 /* Save the current position to the given file */
12210 int
12211 SavePositionToFile(filename)
12212      char *filename;
12213 {
12214     FILE *f;
12215     char buf[MSG_SIZ];
12216
12217     if (strcmp(filename, "-") == 0) {
12218         return SavePosition(stdout, 0, NULL);
12219     } else {
12220         f = fopen(filename, "a");
12221         if (f == NULL) {
12222             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12223             DisplayError(buf, errno);
12224             return FALSE;
12225         } else {
12226             safeStrCpy(buf, lastMsg, MSG_SIZ);
12227             DisplayMessage(_("Waiting for access to save file"), "");
12228             flock(fileno(f), LOCK_EX); // [HGM] lock
12229             DisplayMessage(_("Saving position"), "");
12230             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12231             SavePosition(f, 0, NULL);
12232             DisplayMessage(buf, "");
12233             return TRUE;
12234         }
12235     }
12236 }
12237
12238 /* Save the current position to the given open file and close the file */
12239 int
12240 SavePosition(f, dummy, dummy2)
12241      FILE *f;
12242      int dummy;
12243      char *dummy2;
12244 {
12245     time_t tm;
12246     char *fen;
12247
12248     if (gameMode == EditPosition) EditPositionDone(TRUE);
12249     if (appData.oldSaveStyle) {
12250         tm = time((time_t *) NULL);
12251
12252         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12253         PrintOpponents(f);
12254         fprintf(f, "[--------------\n");
12255         PrintPosition(f, currentMove);
12256         fprintf(f, "--------------]\n");
12257     } else {
12258         fen = PositionToFEN(currentMove, NULL);
12259         fprintf(f, "%s\n", fen);
12260         free(fen);
12261     }
12262     fclose(f);
12263     return TRUE;
12264 }
12265
12266 void
12267 ReloadCmailMsgEvent(unregister)
12268      int unregister;
12269 {
12270 #if !WIN32
12271     static char *inFilename = NULL;
12272     static char *outFilename;
12273     int i;
12274     struct stat inbuf, outbuf;
12275     int status;
12276
12277     /* Any registered moves are unregistered if unregister is set, */
12278     /* i.e. invoked by the signal handler */
12279     if (unregister) {
12280         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12281             cmailMoveRegistered[i] = FALSE;
12282             if (cmailCommentList[i] != NULL) {
12283                 free(cmailCommentList[i]);
12284                 cmailCommentList[i] = NULL;
12285             }
12286         }
12287         nCmailMovesRegistered = 0;
12288     }
12289
12290     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12291         cmailResult[i] = CMAIL_NOT_RESULT;
12292     }
12293     nCmailResults = 0;
12294
12295     if (inFilename == NULL) {
12296         /* Because the filenames are static they only get malloced once  */
12297         /* and they never get freed                                      */
12298         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12299         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12300
12301         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12302         sprintf(outFilename, "%s.out", appData.cmailGameName);
12303     }
12304
12305     status = stat(outFilename, &outbuf);
12306     if (status < 0) {
12307         cmailMailedMove = FALSE;
12308     } else {
12309         status = stat(inFilename, &inbuf);
12310         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12311     }
12312
12313     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12314        counts the games, notes how each one terminated, etc.
12315
12316        It would be nice to remove this kludge and instead gather all
12317        the information while building the game list.  (And to keep it
12318        in the game list nodes instead of having a bunch of fixed-size
12319        parallel arrays.)  Note this will require getting each game's
12320        termination from the PGN tags, as the game list builder does
12321        not process the game moves.  --mann
12322        */
12323     cmailMsgLoaded = TRUE;
12324     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12325
12326     /* Load first game in the file or popup game menu */
12327     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12328
12329 #endif /* !WIN32 */
12330     return;
12331 }
12332
12333 int
12334 RegisterMove()
12335 {
12336     FILE *f;
12337     char string[MSG_SIZ];
12338
12339     if (   cmailMailedMove
12340         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12341         return TRUE;            /* Allow free viewing  */
12342     }
12343
12344     /* Unregister move to ensure that we don't leave RegisterMove        */
12345     /* with the move registered when the conditions for registering no   */
12346     /* longer hold                                                       */
12347     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12348         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12349         nCmailMovesRegistered --;
12350
12351         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12352           {
12353               free(cmailCommentList[lastLoadGameNumber - 1]);
12354               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12355           }
12356     }
12357
12358     if (cmailOldMove == -1) {
12359         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12360         return FALSE;
12361     }
12362
12363     if (currentMove > cmailOldMove + 1) {
12364         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12365         return FALSE;
12366     }
12367
12368     if (currentMove < cmailOldMove) {
12369         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12370         return FALSE;
12371     }
12372
12373     if (forwardMostMove > currentMove) {
12374         /* Silently truncate extra moves */
12375         TruncateGame();
12376     }
12377
12378     if (   (currentMove == cmailOldMove + 1)
12379         || (   (currentMove == cmailOldMove)
12380             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12381                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12382         if (gameInfo.result != GameUnfinished) {
12383             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12384         }
12385
12386         if (commentList[currentMove] != NULL) {
12387             cmailCommentList[lastLoadGameNumber - 1]
12388               = StrSave(commentList[currentMove]);
12389         }
12390         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12391
12392         if (appData.debugMode)
12393           fprintf(debugFP, "Saving %s for game %d\n",
12394                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12395
12396         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12397
12398         f = fopen(string, "w");
12399         if (appData.oldSaveStyle) {
12400             SaveGameOldStyle(f); /* also closes the file */
12401
12402             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12403             f = fopen(string, "w");
12404             SavePosition(f, 0, NULL); /* also closes the file */
12405         } else {
12406             fprintf(f, "{--------------\n");
12407             PrintPosition(f, currentMove);
12408             fprintf(f, "--------------}\n\n");
12409
12410             SaveGame(f, 0, NULL); /* also closes the file*/
12411         }
12412
12413         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12414         nCmailMovesRegistered ++;
12415     } else if (nCmailGames == 1) {
12416         DisplayError(_("You have not made a move yet"), 0);
12417         return FALSE;
12418     }
12419
12420     return TRUE;
12421 }
12422
12423 void
12424 MailMoveEvent()
12425 {
12426 #if !WIN32
12427     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12428     FILE *commandOutput;
12429     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12430     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12431     int nBuffers;
12432     int i;
12433     int archived;
12434     char *arcDir;
12435
12436     if (! cmailMsgLoaded) {
12437         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12438         return;
12439     }
12440
12441     if (nCmailGames == nCmailResults) {
12442         DisplayError(_("No unfinished games"), 0);
12443         return;
12444     }
12445
12446 #if CMAIL_PROHIBIT_REMAIL
12447     if (cmailMailedMove) {
12448       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);
12449         DisplayError(msg, 0);
12450         return;
12451     }
12452 #endif
12453
12454     if (! (cmailMailedMove || RegisterMove())) return;
12455
12456     if (   cmailMailedMove
12457         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12458       snprintf(string, MSG_SIZ, partCommandString,
12459                appData.debugMode ? " -v" : "", appData.cmailGameName);
12460         commandOutput = popen(string, "r");
12461
12462         if (commandOutput == NULL) {
12463             DisplayError(_("Failed to invoke cmail"), 0);
12464         } else {
12465             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12466                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12467             }
12468             if (nBuffers > 1) {
12469                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12470                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12471                 nBytes = MSG_SIZ - 1;
12472             } else {
12473                 (void) memcpy(msg, buffer, nBytes);
12474             }
12475             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12476
12477             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12478                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12479
12480                 archived = TRUE;
12481                 for (i = 0; i < nCmailGames; i ++) {
12482                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12483                         archived = FALSE;
12484                     }
12485                 }
12486                 if (   archived
12487                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12488                         != NULL)) {
12489                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12490                            arcDir,
12491                            appData.cmailGameName,
12492                            gameInfo.date);
12493                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12494                     cmailMsgLoaded = FALSE;
12495                 }
12496             }
12497
12498             DisplayInformation(msg);
12499             pclose(commandOutput);
12500         }
12501     } else {
12502         if ((*cmailMsg) != '\0') {
12503             DisplayInformation(cmailMsg);
12504         }
12505     }
12506
12507     return;
12508 #endif /* !WIN32 */
12509 }
12510
12511 char *
12512 CmailMsg()
12513 {
12514 #if WIN32
12515     return NULL;
12516 #else
12517     int  prependComma = 0;
12518     char number[5];
12519     char string[MSG_SIZ];       /* Space for game-list */
12520     int  i;
12521
12522     if (!cmailMsgLoaded) return "";
12523
12524     if (cmailMailedMove) {
12525       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12526     } else {
12527         /* Create a list of games left */
12528       snprintf(string, MSG_SIZ, "[");
12529         for (i = 0; i < nCmailGames; i ++) {
12530             if (! (   cmailMoveRegistered[i]
12531                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12532                 if (prependComma) {
12533                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12534                 } else {
12535                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12536                     prependComma = 1;
12537                 }
12538
12539                 strcat(string, number);
12540             }
12541         }
12542         strcat(string, "]");
12543
12544         if (nCmailMovesRegistered + nCmailResults == 0) {
12545             switch (nCmailGames) {
12546               case 1:
12547                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12548                 break;
12549
12550               case 2:
12551                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12552                 break;
12553
12554               default:
12555                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12556                          nCmailGames);
12557                 break;
12558             }
12559         } else {
12560             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12561               case 1:
12562                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12563                          string);
12564                 break;
12565
12566               case 0:
12567                 if (nCmailResults == nCmailGames) {
12568                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12569                 } else {
12570                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12571                 }
12572                 break;
12573
12574               default:
12575                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12576                          string);
12577             }
12578         }
12579     }
12580     return cmailMsg;
12581 #endif /* WIN32 */
12582 }
12583
12584 void
12585 ResetGameEvent()
12586 {
12587     if (gameMode == Training)
12588       SetTrainingModeOff();
12589
12590     Reset(TRUE, TRUE);
12591     cmailMsgLoaded = FALSE;
12592     if (appData.icsActive) {
12593       SendToICS(ics_prefix);
12594       SendToICS("refresh\n");
12595     }
12596 }
12597
12598 void
12599 ExitEvent(status)
12600      int status;
12601 {
12602     exiting++;
12603     if (exiting > 2) {
12604       /* Give up on clean exit */
12605       exit(status);
12606     }
12607     if (exiting > 1) {
12608       /* Keep trying for clean exit */
12609       return;
12610     }
12611
12612     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12613
12614     if (telnetISR != NULL) {
12615       RemoveInputSource(telnetISR);
12616     }
12617     if (icsPR != NoProc) {
12618       DestroyChildProcess(icsPR, TRUE);
12619     }
12620
12621     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12622     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12623
12624     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12625     /* make sure this other one finishes before killing it!                  */
12626     if(endingGame) { int count = 0;
12627         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12628         while(endingGame && count++ < 10) DoSleep(1);
12629         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12630     }
12631
12632     /* Kill off chess programs */
12633     if (first.pr != NoProc) {
12634         ExitAnalyzeMode();
12635
12636         DoSleep( appData.delayBeforeQuit );
12637         SendToProgram("quit\n", &first);
12638         DoSleep( appData.delayAfterQuit );
12639         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12640     }
12641     if (second.pr != NoProc) {
12642         DoSleep( appData.delayBeforeQuit );
12643         SendToProgram("quit\n", &second);
12644         DoSleep( appData.delayAfterQuit );
12645         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12646     }
12647     if (first.isr != NULL) {
12648         RemoveInputSource(first.isr);
12649     }
12650     if (second.isr != NULL) {
12651         RemoveInputSource(second.isr);
12652     }
12653
12654     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12655     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12656
12657     ShutDownFrontEnd();
12658     exit(status);
12659 }
12660
12661 void
12662 PauseEvent()
12663 {
12664     if (appData.debugMode)
12665         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12666     if (pausing) {
12667         pausing = FALSE;
12668         ModeHighlight();
12669         if (gameMode == MachinePlaysWhite ||
12670             gameMode == MachinePlaysBlack) {
12671             StartClocks();
12672         } else {
12673             DisplayBothClocks();
12674         }
12675         if (gameMode == PlayFromGameFile) {
12676             if (appData.timeDelay >= 0)
12677                 AutoPlayGameLoop();
12678         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12679             Reset(FALSE, TRUE);
12680             SendToICS(ics_prefix);
12681             SendToICS("refresh\n");
12682         } else if (currentMove < forwardMostMove) {
12683             ForwardInner(forwardMostMove);
12684         }
12685         pauseExamInvalid = FALSE;
12686     } else {
12687         switch (gameMode) {
12688           default:
12689             return;
12690           case IcsExamining:
12691             pauseExamForwardMostMove = forwardMostMove;
12692             pauseExamInvalid = FALSE;
12693             /* fall through */
12694           case IcsObserving:
12695           case IcsPlayingWhite:
12696           case IcsPlayingBlack:
12697             pausing = TRUE;
12698             ModeHighlight();
12699             return;
12700           case PlayFromGameFile:
12701             (void) StopLoadGameTimer();
12702             pausing = TRUE;
12703             ModeHighlight();
12704             break;
12705           case BeginningOfGame:
12706             if (appData.icsActive) return;
12707             /* else fall through */
12708           case MachinePlaysWhite:
12709           case MachinePlaysBlack:
12710           case TwoMachinesPlay:
12711             if (forwardMostMove == 0)
12712               return;           /* don't pause if no one has moved */
12713             if ((gameMode == MachinePlaysWhite &&
12714                  !WhiteOnMove(forwardMostMove)) ||
12715                 (gameMode == MachinePlaysBlack &&
12716                  WhiteOnMove(forwardMostMove))) {
12717                 StopClocks();
12718             }
12719             pausing = TRUE;
12720             ModeHighlight();
12721             break;
12722         }
12723     }
12724 }
12725
12726 void
12727 EditCommentEvent()
12728 {
12729     char title[MSG_SIZ];
12730
12731     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12732       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12733     } else {
12734       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12735                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12736                parseList[currentMove - 1]);
12737     }
12738
12739     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12740 }
12741
12742
12743 void
12744 EditTagsEvent()
12745 {
12746     char *tags = PGNTags(&gameInfo);
12747     bookUp = FALSE;
12748     EditTagsPopUp(tags, NULL);
12749     free(tags);
12750 }
12751
12752 void
12753 AnalyzeModeEvent()
12754 {
12755     if (appData.noChessProgram || gameMode == AnalyzeMode)
12756       return;
12757
12758     if (gameMode != AnalyzeFile) {
12759         if (!appData.icsEngineAnalyze) {
12760                EditGameEvent();
12761                if (gameMode != EditGame) return;
12762         }
12763         ResurrectChessProgram();
12764         SendToProgram("analyze\n", &first);
12765         first.analyzing = TRUE;
12766         /*first.maybeThinking = TRUE;*/
12767         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12768         EngineOutputPopUp();
12769     }
12770     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12771     pausing = FALSE;
12772     ModeHighlight();
12773     SetGameInfo();
12774
12775     StartAnalysisClock();
12776     GetTimeMark(&lastNodeCountTime);
12777     lastNodeCount = 0;
12778 }
12779
12780 void
12781 AnalyzeFileEvent()
12782 {
12783     if (appData.noChessProgram || gameMode == AnalyzeFile)
12784       return;
12785
12786     if (gameMode != AnalyzeMode) {
12787         EditGameEvent();
12788         if (gameMode != EditGame) return;
12789         ResurrectChessProgram();
12790         SendToProgram("analyze\n", &first);
12791         first.analyzing = TRUE;
12792         /*first.maybeThinking = TRUE;*/
12793         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12794         EngineOutputPopUp();
12795     }
12796     gameMode = AnalyzeFile;
12797     pausing = FALSE;
12798     ModeHighlight();
12799     SetGameInfo();
12800
12801     StartAnalysisClock();
12802     GetTimeMark(&lastNodeCountTime);
12803     lastNodeCount = 0;
12804 }
12805
12806 void
12807 MachineWhiteEvent()
12808 {
12809     char buf[MSG_SIZ];
12810     char *bookHit = NULL;
12811
12812     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12813       return;
12814
12815
12816     if (gameMode == PlayFromGameFile ||
12817         gameMode == TwoMachinesPlay  ||
12818         gameMode == Training         ||
12819         gameMode == AnalyzeMode      ||
12820         gameMode == EndOfGame)
12821         EditGameEvent();
12822
12823     if (gameMode == EditPosition)
12824         EditPositionDone(TRUE);
12825
12826     if (!WhiteOnMove(currentMove)) {
12827         DisplayError(_("It is not White's turn"), 0);
12828         return;
12829     }
12830
12831     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12832       ExitAnalyzeMode();
12833
12834     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12835         gameMode == AnalyzeFile)
12836         TruncateGame();
12837
12838     ResurrectChessProgram();    /* in case it isn't running */
12839     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12840         gameMode = MachinePlaysWhite;
12841         ResetClocks();
12842     } else
12843     gameMode = MachinePlaysWhite;
12844     pausing = FALSE;
12845     ModeHighlight();
12846     SetGameInfo();
12847     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12848     DisplayTitle(buf);
12849     if (first.sendName) {
12850       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12851       SendToProgram(buf, &first);
12852     }
12853     if (first.sendTime) {
12854       if (first.useColors) {
12855         SendToProgram("black\n", &first); /*gnu kludge*/
12856       }
12857       SendTimeRemaining(&first, TRUE);
12858     }
12859     if (first.useColors) {
12860       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12861     }
12862     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12863     SetMachineThinkingEnables();
12864     first.maybeThinking = TRUE;
12865     StartClocks();
12866     firstMove = FALSE;
12867
12868     if (appData.autoFlipView && !flipView) {
12869       flipView = !flipView;
12870       DrawPosition(FALSE, NULL);
12871       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12872     }
12873
12874     if(bookHit) { // [HGM] book: simulate book reply
12875         static char bookMove[MSG_SIZ]; // a bit generous?
12876
12877         programStats.nodes = programStats.depth = programStats.time =
12878         programStats.score = programStats.got_only_move = 0;
12879         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12880
12881         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12882         strcat(bookMove, bookHit);
12883         HandleMachineMove(bookMove, &first);
12884     }
12885 }
12886
12887 void
12888 MachineBlackEvent()
12889 {
12890   char buf[MSG_SIZ];
12891   char *bookHit = NULL;
12892
12893     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12894         return;
12895
12896
12897     if (gameMode == PlayFromGameFile ||
12898         gameMode == TwoMachinesPlay  ||
12899         gameMode == Training         ||
12900         gameMode == AnalyzeMode      ||
12901         gameMode == EndOfGame)
12902         EditGameEvent();
12903
12904     if (gameMode == EditPosition)
12905         EditPositionDone(TRUE);
12906
12907     if (WhiteOnMove(currentMove)) {
12908         DisplayError(_("It is not Black's turn"), 0);
12909         return;
12910     }
12911
12912     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12913       ExitAnalyzeMode();
12914
12915     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12916         gameMode == AnalyzeFile)
12917         TruncateGame();
12918
12919     ResurrectChessProgram();    /* in case it isn't running */
12920     gameMode = MachinePlaysBlack;
12921     pausing = FALSE;
12922     ModeHighlight();
12923     SetGameInfo();
12924     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12925     DisplayTitle(buf);
12926     if (first.sendName) {
12927       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12928       SendToProgram(buf, &first);
12929     }
12930     if (first.sendTime) {
12931       if (first.useColors) {
12932         SendToProgram("white\n", &first); /*gnu kludge*/
12933       }
12934       SendTimeRemaining(&first, FALSE);
12935     }
12936     if (first.useColors) {
12937       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12938     }
12939     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12940     SetMachineThinkingEnables();
12941     first.maybeThinking = TRUE;
12942     StartClocks();
12943
12944     if (appData.autoFlipView && flipView) {
12945       flipView = !flipView;
12946       DrawPosition(FALSE, NULL);
12947       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12948     }
12949     if(bookHit) { // [HGM] book: simulate book reply
12950         static char bookMove[MSG_SIZ]; // a bit generous?
12951
12952         programStats.nodes = programStats.depth = programStats.time =
12953         programStats.score = programStats.got_only_move = 0;
12954         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12955
12956         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12957         strcat(bookMove, bookHit);
12958         HandleMachineMove(bookMove, &first);
12959     }
12960 }
12961
12962
12963 void
12964 DisplayTwoMachinesTitle()
12965 {
12966     char buf[MSG_SIZ];
12967     if (appData.matchGames > 0) {
12968         if(appData.tourneyFile[0]) {
12969           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12970                    gameInfo.white, gameInfo.black,
12971                    nextGame+1, appData.matchGames+1,
12972                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12973         } else 
12974         if (first.twoMachinesColor[0] == 'w') {
12975           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12976                    gameInfo.white, gameInfo.black,
12977                    first.matchWins, second.matchWins,
12978                    matchGame - 1 - (first.matchWins + second.matchWins));
12979         } else {
12980           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12981                    gameInfo.white, gameInfo.black,
12982                    second.matchWins, first.matchWins,
12983                    matchGame - 1 - (first.matchWins + second.matchWins));
12984         }
12985     } else {
12986       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12987     }
12988     DisplayTitle(buf);
12989 }
12990
12991 void
12992 SettingsMenuIfReady()
12993 {
12994   if (second.lastPing != second.lastPong) {
12995     DisplayMessage("", _("Waiting for second chess program"));
12996     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12997     return;
12998   }
12999   ThawUI();
13000   DisplayMessage("", "");
13001   SettingsPopUp(&second);
13002 }
13003
13004 int
13005 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13006 {
13007     char buf[MSG_SIZ];
13008     if (cps->pr == NULL) {
13009         StartChessProgram(cps);
13010         if (cps->protocolVersion == 1) {
13011           retry();
13012         } else {
13013           /* kludge: allow timeout for initial "feature" command */
13014           FreezeUI();
13015           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13016           DisplayMessage("", buf);
13017           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13018         }
13019         return 1;
13020     }
13021     return 0;
13022 }
13023
13024 void
13025 TwoMachinesEvent P((void))
13026 {
13027     int i;
13028     char buf[MSG_SIZ];
13029     ChessProgramState *onmove;
13030     char *bookHit = NULL;
13031     static int stalling = 0;
13032     TimeMark now;
13033     long wait;
13034
13035     if (appData.noChessProgram) return;
13036
13037     switch (gameMode) {
13038       case TwoMachinesPlay:
13039         return;
13040       case MachinePlaysWhite:
13041       case MachinePlaysBlack:
13042         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13043             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13044             return;
13045         }
13046         /* fall through */
13047       case BeginningOfGame:
13048       case PlayFromGameFile:
13049       case EndOfGame:
13050         EditGameEvent();
13051         if (gameMode != EditGame) return;
13052         break;
13053       case EditPosition:
13054         EditPositionDone(TRUE);
13055         break;
13056       case AnalyzeMode:
13057       case AnalyzeFile:
13058         ExitAnalyzeMode();
13059         break;
13060       case EditGame:
13061       default:
13062         break;
13063     }
13064
13065 //    forwardMostMove = currentMove;
13066     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13067
13068     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13069
13070     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13071     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13072       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13073       return;
13074     }
13075     if(!stalling) {
13076       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13077       SendToProgram("force\n", &second);
13078       stalling = 1;
13079       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13080       return;
13081     }
13082     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13083     if(appData.matchPause>10000 || appData.matchPause<10)
13084                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13085     wait = SubtractTimeMarks(&now, &pauseStart);
13086     if(wait < appData.matchPause) {
13087         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13088         return;
13089     }
13090     stalling = 0;
13091     DisplayMessage("", "");
13092     if (startedFromSetupPosition) {
13093         SendBoard(&second, backwardMostMove);
13094     if (appData.debugMode) {
13095         fprintf(debugFP, "Two Machines\n");
13096     }
13097     }
13098     for (i = backwardMostMove; i < forwardMostMove; i++) {
13099         SendMoveToProgram(i, &second);
13100     }
13101
13102     gameMode = TwoMachinesPlay;
13103     pausing = FALSE;
13104     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13105     SetGameInfo();
13106     DisplayTwoMachinesTitle();
13107     firstMove = TRUE;
13108     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13109         onmove = &first;
13110     } else {
13111         onmove = &second;
13112     }
13113     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13114     SendToProgram(first.computerString, &first);
13115     if (first.sendName) {
13116       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13117       SendToProgram(buf, &first);
13118     }
13119     SendToProgram(second.computerString, &second);
13120     if (second.sendName) {
13121       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13122       SendToProgram(buf, &second);
13123     }
13124
13125     ResetClocks();
13126     if (!first.sendTime || !second.sendTime) {
13127         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13128         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13129     }
13130     if (onmove->sendTime) {
13131       if (onmove->useColors) {
13132         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13133       }
13134       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13135     }
13136     if (onmove->useColors) {
13137       SendToProgram(onmove->twoMachinesColor, onmove);
13138     }
13139     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13140 //    SendToProgram("go\n", onmove);
13141     onmove->maybeThinking = TRUE;
13142     SetMachineThinkingEnables();
13143
13144     StartClocks();
13145
13146     if(bookHit) { // [HGM] book: simulate book reply
13147         static char bookMove[MSG_SIZ]; // a bit generous?
13148
13149         programStats.nodes = programStats.depth = programStats.time =
13150         programStats.score = programStats.got_only_move = 0;
13151         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13152
13153         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13154         strcat(bookMove, bookHit);
13155         savedMessage = bookMove; // args for deferred call
13156         savedState = onmove;
13157         ScheduleDelayedEvent(DeferredBookMove, 1);
13158     }
13159 }
13160
13161 void
13162 TrainingEvent()
13163 {
13164     if (gameMode == Training) {
13165       SetTrainingModeOff();
13166       gameMode = PlayFromGameFile;
13167       DisplayMessage("", _("Training mode off"));
13168     } else {
13169       gameMode = Training;
13170       animateTraining = appData.animate;
13171
13172       /* make sure we are not already at the end of the game */
13173       if (currentMove < forwardMostMove) {
13174         SetTrainingModeOn();
13175         DisplayMessage("", _("Training mode on"));
13176       } else {
13177         gameMode = PlayFromGameFile;
13178         DisplayError(_("Already at end of game"), 0);
13179       }
13180     }
13181     ModeHighlight();
13182 }
13183
13184 void
13185 IcsClientEvent()
13186 {
13187     if (!appData.icsActive) return;
13188     switch (gameMode) {
13189       case IcsPlayingWhite:
13190       case IcsPlayingBlack:
13191       case IcsObserving:
13192       case IcsIdle:
13193       case BeginningOfGame:
13194       case IcsExamining:
13195         return;
13196
13197       case EditGame:
13198         break;
13199
13200       case EditPosition:
13201         EditPositionDone(TRUE);
13202         break;
13203
13204       case AnalyzeMode:
13205       case AnalyzeFile:
13206         ExitAnalyzeMode();
13207         break;
13208
13209       default:
13210         EditGameEvent();
13211         break;
13212     }
13213
13214     gameMode = IcsIdle;
13215     ModeHighlight();
13216     return;
13217 }
13218
13219
13220 void
13221 EditGameEvent()
13222 {
13223     int i;
13224
13225     switch (gameMode) {
13226       case Training:
13227         SetTrainingModeOff();
13228         break;
13229       case MachinePlaysWhite:
13230       case MachinePlaysBlack:
13231       case BeginningOfGame:
13232         SendToProgram("force\n", &first);
13233         SetUserThinkingEnables();
13234         break;
13235       case PlayFromGameFile:
13236         (void) StopLoadGameTimer();
13237         if (gameFileFP != NULL) {
13238             gameFileFP = NULL;
13239         }
13240         break;
13241       case EditPosition:
13242         EditPositionDone(TRUE);
13243         break;
13244       case AnalyzeMode:
13245       case AnalyzeFile:
13246         ExitAnalyzeMode();
13247         SendToProgram("force\n", &first);
13248         break;
13249       case TwoMachinesPlay:
13250         GameEnds(EndOfFile, NULL, GE_PLAYER);
13251         ResurrectChessProgram();
13252         SetUserThinkingEnables();
13253         break;
13254       case EndOfGame:
13255         ResurrectChessProgram();
13256         break;
13257       case IcsPlayingBlack:
13258       case IcsPlayingWhite:
13259         DisplayError(_("Warning: You are still playing a game"), 0);
13260         break;
13261       case IcsObserving:
13262         DisplayError(_("Warning: You are still observing a game"), 0);
13263         break;
13264       case IcsExamining:
13265         DisplayError(_("Warning: You are still examining a game"), 0);
13266         break;
13267       case IcsIdle:
13268         break;
13269       case EditGame:
13270       default:
13271         return;
13272     }
13273
13274     pausing = FALSE;
13275     StopClocks();
13276     first.offeredDraw = second.offeredDraw = 0;
13277
13278     if (gameMode == PlayFromGameFile) {
13279         whiteTimeRemaining = timeRemaining[0][currentMove];
13280         blackTimeRemaining = timeRemaining[1][currentMove];
13281         DisplayTitle("");
13282     }
13283
13284     if (gameMode == MachinePlaysWhite ||
13285         gameMode == MachinePlaysBlack ||
13286         gameMode == TwoMachinesPlay ||
13287         gameMode == EndOfGame) {
13288         i = forwardMostMove;
13289         while (i > currentMove) {
13290             SendToProgram("undo\n", &first);
13291             i--;
13292         }
13293         whiteTimeRemaining = timeRemaining[0][currentMove];
13294         blackTimeRemaining = timeRemaining[1][currentMove];
13295         DisplayBothClocks();
13296         if (whiteFlag || blackFlag) {
13297             whiteFlag = blackFlag = 0;
13298         }
13299         DisplayTitle("");
13300     }
13301
13302     gameMode = EditGame;
13303     ModeHighlight();
13304     SetGameInfo();
13305 }
13306
13307
13308 void
13309 EditPositionEvent()
13310 {
13311     if (gameMode == EditPosition) {
13312         EditGameEvent();
13313         return;
13314     }
13315
13316     EditGameEvent();
13317     if (gameMode != EditGame) return;
13318
13319     gameMode = EditPosition;
13320     ModeHighlight();
13321     SetGameInfo();
13322     if (currentMove > 0)
13323       CopyBoard(boards[0], boards[currentMove]);
13324
13325     blackPlaysFirst = !WhiteOnMove(currentMove);
13326     ResetClocks();
13327     currentMove = forwardMostMove = backwardMostMove = 0;
13328     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13329     DisplayMove(-1);
13330 }
13331
13332 void
13333 ExitAnalyzeMode()
13334 {
13335     /* [DM] icsEngineAnalyze - possible call from other functions */
13336     if (appData.icsEngineAnalyze) {
13337         appData.icsEngineAnalyze = FALSE;
13338
13339         DisplayMessage("",_("Close ICS engine analyze..."));
13340     }
13341     if (first.analysisSupport && first.analyzing) {
13342       SendToProgram("exit\n", &first);
13343       first.analyzing = FALSE;
13344     }
13345     thinkOutput[0] = NULLCHAR;
13346 }
13347
13348 void
13349 EditPositionDone(Boolean fakeRights)
13350 {
13351     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13352
13353     startedFromSetupPosition = TRUE;
13354     InitChessProgram(&first, FALSE);
13355     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13356       boards[0][EP_STATUS] = EP_NONE;
13357       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13358     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13359         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13360         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13361       } else boards[0][CASTLING][2] = NoRights;
13362     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13363         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13364         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13365       } else boards[0][CASTLING][5] = NoRights;
13366     }
13367     SendToProgram("force\n", &first);
13368     if (blackPlaysFirst) {
13369         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13370         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13371         currentMove = forwardMostMove = backwardMostMove = 1;
13372         CopyBoard(boards[1], boards[0]);
13373     } else {
13374         currentMove = forwardMostMove = backwardMostMove = 0;
13375     }
13376     SendBoard(&first, forwardMostMove);
13377     if (appData.debugMode) {
13378         fprintf(debugFP, "EditPosDone\n");
13379     }
13380     DisplayTitle("");
13381     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13382     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13383     gameMode = EditGame;
13384     ModeHighlight();
13385     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13386     ClearHighlights(); /* [AS] */
13387 }
13388
13389 /* Pause for `ms' milliseconds */
13390 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13391 void
13392 TimeDelay(ms)
13393      long ms;
13394 {
13395     TimeMark m1, m2;
13396
13397     GetTimeMark(&m1);
13398     do {
13399         GetTimeMark(&m2);
13400     } while (SubtractTimeMarks(&m2, &m1) < ms);
13401 }
13402
13403 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13404 void
13405 SendMultiLineToICS(buf)
13406      char *buf;
13407 {
13408     char temp[MSG_SIZ+1], *p;
13409     int len;
13410
13411     len = strlen(buf);
13412     if (len > MSG_SIZ)
13413       len = MSG_SIZ;
13414
13415     strncpy(temp, buf, len);
13416     temp[len] = 0;
13417
13418     p = temp;
13419     while (*p) {
13420         if (*p == '\n' || *p == '\r')
13421           *p = ' ';
13422         ++p;
13423     }
13424
13425     strcat(temp, "\n");
13426     SendToICS(temp);
13427     SendToPlayer(temp, strlen(temp));
13428 }
13429
13430 void
13431 SetWhiteToPlayEvent()
13432 {
13433     if (gameMode == EditPosition) {
13434         blackPlaysFirst = FALSE;
13435         DisplayBothClocks();    /* works because currentMove is 0 */
13436     } else if (gameMode == IcsExamining) {
13437         SendToICS(ics_prefix);
13438         SendToICS("tomove white\n");
13439     }
13440 }
13441
13442 void
13443 SetBlackToPlayEvent()
13444 {
13445     if (gameMode == EditPosition) {
13446         blackPlaysFirst = TRUE;
13447         currentMove = 1;        /* kludge */
13448         DisplayBothClocks();
13449         currentMove = 0;
13450     } else if (gameMode == IcsExamining) {
13451         SendToICS(ics_prefix);
13452         SendToICS("tomove black\n");
13453     }
13454 }
13455
13456 void
13457 EditPositionMenuEvent(selection, x, y)
13458      ChessSquare selection;
13459      int x, y;
13460 {
13461     char buf[MSG_SIZ];
13462     ChessSquare piece = boards[0][y][x];
13463
13464     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13465
13466     switch (selection) {
13467       case ClearBoard:
13468         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13469             SendToICS(ics_prefix);
13470             SendToICS("bsetup clear\n");
13471         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13472             SendToICS(ics_prefix);
13473             SendToICS("clearboard\n");
13474         } else {
13475             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13476                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13477                 for (y = 0; y < BOARD_HEIGHT; y++) {
13478                     if (gameMode == IcsExamining) {
13479                         if (boards[currentMove][y][x] != EmptySquare) {
13480                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13481                                     AAA + x, ONE + y);
13482                             SendToICS(buf);
13483                         }
13484                     } else {
13485                         boards[0][y][x] = p;
13486                     }
13487                 }
13488             }
13489         }
13490         if (gameMode == EditPosition) {
13491             DrawPosition(FALSE, boards[0]);
13492         }
13493         break;
13494
13495       case WhitePlay:
13496         SetWhiteToPlayEvent();
13497         break;
13498
13499       case BlackPlay:
13500         SetBlackToPlayEvent();
13501         break;
13502
13503       case EmptySquare:
13504         if (gameMode == IcsExamining) {
13505             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13506             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13507             SendToICS(buf);
13508         } else {
13509             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13510                 if(x == BOARD_LEFT-2) {
13511                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13512                     boards[0][y][1] = 0;
13513                 } else
13514                 if(x == BOARD_RGHT+1) {
13515                     if(y >= gameInfo.holdingsSize) break;
13516                     boards[0][y][BOARD_WIDTH-2] = 0;
13517                 } else break;
13518             }
13519             boards[0][y][x] = EmptySquare;
13520             DrawPosition(FALSE, boards[0]);
13521         }
13522         break;
13523
13524       case PromotePiece:
13525         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13526            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13527             selection = (ChessSquare) (PROMOTED piece);
13528         } else if(piece == EmptySquare) selection = WhiteSilver;
13529         else selection = (ChessSquare)((int)piece - 1);
13530         goto defaultlabel;
13531
13532       case DemotePiece:
13533         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13534            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13535             selection = (ChessSquare) (DEMOTED piece);
13536         } else if(piece == EmptySquare) selection = BlackSilver;
13537         else selection = (ChessSquare)((int)piece + 1);
13538         goto defaultlabel;
13539
13540       case WhiteQueen:
13541       case BlackQueen:
13542         if(gameInfo.variant == VariantShatranj ||
13543            gameInfo.variant == VariantXiangqi  ||
13544            gameInfo.variant == VariantCourier  ||
13545            gameInfo.variant == VariantMakruk     )
13546             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13547         goto defaultlabel;
13548
13549       case WhiteKing:
13550       case BlackKing:
13551         if(gameInfo.variant == VariantXiangqi)
13552             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13553         if(gameInfo.variant == VariantKnightmate)
13554             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13555       default:
13556         defaultlabel:
13557         if (gameMode == IcsExamining) {
13558             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13559             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13560                      PieceToChar(selection), AAA + x, ONE + y);
13561             SendToICS(buf);
13562         } else {
13563             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13564                 int n;
13565                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13566                     n = PieceToNumber(selection - BlackPawn);
13567                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13568                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13569                     boards[0][BOARD_HEIGHT-1-n][1]++;
13570                 } else
13571                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13572                     n = PieceToNumber(selection);
13573                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13574                     boards[0][n][BOARD_WIDTH-1] = selection;
13575                     boards[0][n][BOARD_WIDTH-2]++;
13576                 }
13577             } else
13578             boards[0][y][x] = selection;
13579             DrawPosition(TRUE, boards[0]);
13580         }
13581         break;
13582     }
13583 }
13584
13585
13586 void
13587 DropMenuEvent(selection, x, y)
13588      ChessSquare selection;
13589      int x, y;
13590 {
13591     ChessMove moveType;
13592
13593     switch (gameMode) {
13594       case IcsPlayingWhite:
13595       case MachinePlaysBlack:
13596         if (!WhiteOnMove(currentMove)) {
13597             DisplayMoveError(_("It is Black's turn"));
13598             return;
13599         }
13600         moveType = WhiteDrop;
13601         break;
13602       case IcsPlayingBlack:
13603       case MachinePlaysWhite:
13604         if (WhiteOnMove(currentMove)) {
13605             DisplayMoveError(_("It is White's turn"));
13606             return;
13607         }
13608         moveType = BlackDrop;
13609         break;
13610       case EditGame:
13611         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13612         break;
13613       default:
13614         return;
13615     }
13616
13617     if (moveType == BlackDrop && selection < BlackPawn) {
13618       selection = (ChessSquare) ((int) selection
13619                                  + (int) BlackPawn - (int) WhitePawn);
13620     }
13621     if (boards[currentMove][y][x] != EmptySquare) {
13622         DisplayMoveError(_("That square is occupied"));
13623         return;
13624     }
13625
13626     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13627 }
13628
13629 void
13630 AcceptEvent()
13631 {
13632     /* Accept a pending offer of any kind from opponent */
13633
13634     if (appData.icsActive) {
13635         SendToICS(ics_prefix);
13636         SendToICS("accept\n");
13637     } else if (cmailMsgLoaded) {
13638         if (currentMove == cmailOldMove &&
13639             commentList[cmailOldMove] != NULL &&
13640             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13641                    "Black offers a draw" : "White offers a draw")) {
13642             TruncateGame();
13643             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13644             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13645         } else {
13646             DisplayError(_("There is no pending offer on this move"), 0);
13647             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13648         }
13649     } else {
13650         /* Not used for offers from chess program */
13651     }
13652 }
13653
13654 void
13655 DeclineEvent()
13656 {
13657     /* Decline a pending offer of any kind from opponent */
13658
13659     if (appData.icsActive) {
13660         SendToICS(ics_prefix);
13661         SendToICS("decline\n");
13662     } else if (cmailMsgLoaded) {
13663         if (currentMove == cmailOldMove &&
13664             commentList[cmailOldMove] != NULL &&
13665             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13666                    "Black offers a draw" : "White offers a draw")) {
13667 #ifdef NOTDEF
13668             AppendComment(cmailOldMove, "Draw declined", TRUE);
13669             DisplayComment(cmailOldMove - 1, "Draw declined");
13670 #endif /*NOTDEF*/
13671         } else {
13672             DisplayError(_("There is no pending offer on this move"), 0);
13673         }
13674     } else {
13675         /* Not used for offers from chess program */
13676     }
13677 }
13678
13679 void
13680 RematchEvent()
13681 {
13682     /* Issue ICS rematch command */
13683     if (appData.icsActive) {
13684         SendToICS(ics_prefix);
13685         SendToICS("rematch\n");
13686     }
13687 }
13688
13689 void
13690 CallFlagEvent()
13691 {
13692     /* Call your opponent's flag (claim a win on time) */
13693     if (appData.icsActive) {
13694         SendToICS(ics_prefix);
13695         SendToICS("flag\n");
13696     } else {
13697         switch (gameMode) {
13698           default:
13699             return;
13700           case MachinePlaysWhite:
13701             if (whiteFlag) {
13702                 if (blackFlag)
13703                   GameEnds(GameIsDrawn, "Both players ran out of time",
13704                            GE_PLAYER);
13705                 else
13706                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13707             } else {
13708                 DisplayError(_("Your opponent is not out of time"), 0);
13709             }
13710             break;
13711           case MachinePlaysBlack:
13712             if (blackFlag) {
13713                 if (whiteFlag)
13714                   GameEnds(GameIsDrawn, "Both players ran out of time",
13715                            GE_PLAYER);
13716                 else
13717                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13718             } else {
13719                 DisplayError(_("Your opponent is not out of time"), 0);
13720             }
13721             break;
13722         }
13723     }
13724 }
13725
13726 void
13727 ClockClick(int which)
13728 {       // [HGM] code moved to back-end from winboard.c
13729         if(which) { // black clock
13730           if (gameMode == EditPosition || gameMode == IcsExamining) {
13731             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13732             SetBlackToPlayEvent();
13733           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13734           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13735           } else if (shiftKey) {
13736             AdjustClock(which, -1);
13737           } else if (gameMode == IcsPlayingWhite ||
13738                      gameMode == MachinePlaysBlack) {
13739             CallFlagEvent();
13740           }
13741         } else { // white clock
13742           if (gameMode == EditPosition || gameMode == IcsExamining) {
13743             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13744             SetWhiteToPlayEvent();
13745           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13746           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13747           } else if (shiftKey) {
13748             AdjustClock(which, -1);
13749           } else if (gameMode == IcsPlayingBlack ||
13750                    gameMode == MachinePlaysWhite) {
13751             CallFlagEvent();
13752           }
13753         }
13754 }
13755
13756 void
13757 DrawEvent()
13758 {
13759     /* Offer draw or accept pending draw offer from opponent */
13760
13761     if (appData.icsActive) {
13762         /* Note: tournament rules require draw offers to be
13763            made after you make your move but before you punch
13764            your clock.  Currently ICS doesn't let you do that;
13765            instead, you immediately punch your clock after making
13766            a move, but you can offer a draw at any time. */
13767
13768         SendToICS(ics_prefix);
13769         SendToICS("draw\n");
13770         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13771     } else if (cmailMsgLoaded) {
13772         if (currentMove == cmailOldMove &&
13773             commentList[cmailOldMove] != NULL &&
13774             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13775                    "Black offers a draw" : "White offers a draw")) {
13776             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13777             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13778         } else if (currentMove == cmailOldMove + 1) {
13779             char *offer = WhiteOnMove(cmailOldMove) ?
13780               "White offers a draw" : "Black offers a draw";
13781             AppendComment(currentMove, offer, TRUE);
13782             DisplayComment(currentMove - 1, offer);
13783             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13784         } else {
13785             DisplayError(_("You must make your move before offering a draw"), 0);
13786             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13787         }
13788     } else if (first.offeredDraw) {
13789         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13790     } else {
13791         if (first.sendDrawOffers) {
13792             SendToProgram("draw\n", &first);
13793             userOfferedDraw = TRUE;
13794         }
13795     }
13796 }
13797
13798 void
13799 AdjournEvent()
13800 {
13801     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13802
13803     if (appData.icsActive) {
13804         SendToICS(ics_prefix);
13805         SendToICS("adjourn\n");
13806     } else {
13807         /* Currently GNU Chess doesn't offer or accept Adjourns */
13808     }
13809 }
13810
13811
13812 void
13813 AbortEvent()
13814 {
13815     /* Offer Abort or accept pending Abort offer from opponent */
13816
13817     if (appData.icsActive) {
13818         SendToICS(ics_prefix);
13819         SendToICS("abort\n");
13820     } else {
13821         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13822     }
13823 }
13824
13825 void
13826 ResignEvent()
13827 {
13828     /* Resign.  You can do this even if it's not your turn. */
13829
13830     if (appData.icsActive) {
13831         SendToICS(ics_prefix);
13832         SendToICS("resign\n");
13833     } else {
13834         switch (gameMode) {
13835           case MachinePlaysWhite:
13836             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13837             break;
13838           case MachinePlaysBlack:
13839             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13840             break;
13841           case EditGame:
13842             if (cmailMsgLoaded) {
13843                 TruncateGame();
13844                 if (WhiteOnMove(cmailOldMove)) {
13845                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13846                 } else {
13847                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13848                 }
13849                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13850             }
13851             break;
13852           default:
13853             break;
13854         }
13855     }
13856 }
13857
13858
13859 void
13860 StopObservingEvent()
13861 {
13862     /* Stop observing current games */
13863     SendToICS(ics_prefix);
13864     SendToICS("unobserve\n");
13865 }
13866
13867 void
13868 StopExaminingEvent()
13869 {
13870     /* Stop observing current game */
13871     SendToICS(ics_prefix);
13872     SendToICS("unexamine\n");
13873 }
13874
13875 void
13876 ForwardInner(target)
13877      int target;
13878 {
13879     int limit;
13880
13881     if (appData.debugMode)
13882         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13883                 target, currentMove, forwardMostMove);
13884
13885     if (gameMode == EditPosition)
13886       return;
13887
13888     if (gameMode == PlayFromGameFile && !pausing)
13889       PauseEvent();
13890
13891     if (gameMode == IcsExamining && pausing)
13892       limit = pauseExamForwardMostMove;
13893     else
13894       limit = forwardMostMove;
13895
13896     if (target > limit) target = limit;
13897
13898     if (target > 0 && moveList[target - 1][0]) {
13899         int fromX, fromY, toX, toY;
13900         toX = moveList[target - 1][2] - AAA;
13901         toY = moveList[target - 1][3] - ONE;
13902         if (moveList[target - 1][1] == '@') {
13903             if (appData.highlightLastMove) {
13904                 SetHighlights(-1, -1, toX, toY);
13905             }
13906         } else {
13907             fromX = moveList[target - 1][0] - AAA;
13908             fromY = moveList[target - 1][1] - ONE;
13909             if (target == currentMove + 1) {
13910                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13911             }
13912             if (appData.highlightLastMove) {
13913                 SetHighlights(fromX, fromY, toX, toY);
13914             }
13915         }
13916     }
13917     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13918         gameMode == Training || gameMode == PlayFromGameFile ||
13919         gameMode == AnalyzeFile) {
13920         while (currentMove < target) {
13921             SendMoveToProgram(currentMove++, &first);
13922         }
13923     } else {
13924         currentMove = target;
13925     }
13926
13927     if (gameMode == EditGame || gameMode == EndOfGame) {
13928         whiteTimeRemaining = timeRemaining[0][currentMove];
13929         blackTimeRemaining = timeRemaining[1][currentMove];
13930     }
13931     DisplayBothClocks();
13932     DisplayMove(currentMove - 1);
13933     DrawPosition(FALSE, boards[currentMove]);
13934     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13935     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13936         DisplayComment(currentMove - 1, commentList[currentMove]);
13937     }
13938     DisplayBook(currentMove);
13939 }
13940
13941
13942 void
13943 ForwardEvent()
13944 {
13945     if (gameMode == IcsExamining && !pausing) {
13946         SendToICS(ics_prefix);
13947         SendToICS("forward\n");
13948     } else {
13949         ForwardInner(currentMove + 1);
13950     }
13951 }
13952
13953 void
13954 ToEndEvent()
13955 {
13956     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13957         /* to optimze, we temporarily turn off analysis mode while we feed
13958          * the remaining moves to the engine. Otherwise we get analysis output
13959          * after each move.
13960          */
13961         if (first.analysisSupport) {
13962           SendToProgram("exit\nforce\n", &first);
13963           first.analyzing = FALSE;
13964         }
13965     }
13966
13967     if (gameMode == IcsExamining && !pausing) {
13968         SendToICS(ics_prefix);
13969         SendToICS("forward 999999\n");
13970     } else {
13971         ForwardInner(forwardMostMove);
13972     }
13973
13974     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13975         /* we have fed all the moves, so reactivate analysis mode */
13976         SendToProgram("analyze\n", &first);
13977         first.analyzing = TRUE;
13978         /*first.maybeThinking = TRUE;*/
13979         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13980     }
13981 }
13982
13983 void
13984 BackwardInner(target)
13985      int target;
13986 {
13987     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13988
13989     if (appData.debugMode)
13990         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13991                 target, currentMove, forwardMostMove);
13992
13993     if (gameMode == EditPosition) return;
13994     if (currentMove <= backwardMostMove) {
13995         ClearHighlights();
13996         DrawPosition(full_redraw, boards[currentMove]);
13997         return;
13998     }
13999     if (gameMode == PlayFromGameFile && !pausing)
14000       PauseEvent();
14001
14002     if (moveList[target][0]) {
14003         int fromX, fromY, toX, toY;
14004         toX = moveList[target][2] - AAA;
14005         toY = moveList[target][3] - ONE;
14006         if (moveList[target][1] == '@') {
14007             if (appData.highlightLastMove) {
14008                 SetHighlights(-1, -1, toX, toY);
14009             }
14010         } else {
14011             fromX = moveList[target][0] - AAA;
14012             fromY = moveList[target][1] - ONE;
14013             if (target == currentMove - 1) {
14014                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14015             }
14016             if (appData.highlightLastMove) {
14017                 SetHighlights(fromX, fromY, toX, toY);
14018             }
14019         }
14020     }
14021     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14022         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14023         while (currentMove > target) {
14024             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14025                 // null move cannot be undone. Reload program with move history before it.
14026                 int i;
14027                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14028                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14029                 }
14030                 SendBoard(&first, i); 
14031                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14032                 break;
14033             }
14034             SendToProgram("undo\n", &first);
14035             currentMove--;
14036         }
14037     } else {
14038         currentMove = target;
14039     }
14040
14041     if (gameMode == EditGame || gameMode == EndOfGame) {
14042         whiteTimeRemaining = timeRemaining[0][currentMove];
14043         blackTimeRemaining = timeRemaining[1][currentMove];
14044     }
14045     DisplayBothClocks();
14046     DisplayMove(currentMove - 1);
14047     DrawPosition(full_redraw, boards[currentMove]);
14048     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14049     // [HGM] PV info: routine tests if comment empty
14050     DisplayComment(currentMove - 1, commentList[currentMove]);
14051     DisplayBook(currentMove);
14052 }
14053
14054 void
14055 BackwardEvent()
14056 {
14057     if (gameMode == IcsExamining && !pausing) {
14058         SendToICS(ics_prefix);
14059         SendToICS("backward\n");
14060     } else {
14061         BackwardInner(currentMove - 1);
14062     }
14063 }
14064
14065 void
14066 ToStartEvent()
14067 {
14068     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14069         /* to optimize, we temporarily turn off analysis mode while we undo
14070          * all the moves. Otherwise we get analysis output after each undo.
14071          */
14072         if (first.analysisSupport) {
14073           SendToProgram("exit\nforce\n", &first);
14074           first.analyzing = FALSE;
14075         }
14076     }
14077
14078     if (gameMode == IcsExamining && !pausing) {
14079         SendToICS(ics_prefix);
14080         SendToICS("backward 999999\n");
14081     } else {
14082         BackwardInner(backwardMostMove);
14083     }
14084
14085     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14086         /* we have fed all the moves, so reactivate analysis mode */
14087         SendToProgram("analyze\n", &first);
14088         first.analyzing = TRUE;
14089         /*first.maybeThinking = TRUE;*/
14090         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14091     }
14092 }
14093
14094 void
14095 ToNrEvent(int to)
14096 {
14097   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14098   if (to >= forwardMostMove) to = forwardMostMove;
14099   if (to <= backwardMostMove) to = backwardMostMove;
14100   if (to < currentMove) {
14101     BackwardInner(to);
14102   } else {
14103     ForwardInner(to);
14104   }
14105 }
14106
14107 void
14108 RevertEvent(Boolean annotate)
14109 {
14110     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14111         return;
14112     }
14113     if (gameMode != IcsExamining) {
14114         DisplayError(_("You are not examining a game"), 0);
14115         return;
14116     }
14117     if (pausing) {
14118         DisplayError(_("You can't revert while pausing"), 0);
14119         return;
14120     }
14121     SendToICS(ics_prefix);
14122     SendToICS("revert\n");
14123 }
14124
14125 void
14126 RetractMoveEvent()
14127 {
14128     switch (gameMode) {
14129       case MachinePlaysWhite:
14130       case MachinePlaysBlack:
14131         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14132             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14133             return;
14134         }
14135         if (forwardMostMove < 2) return;
14136         currentMove = forwardMostMove = forwardMostMove - 2;
14137         whiteTimeRemaining = timeRemaining[0][currentMove];
14138         blackTimeRemaining = timeRemaining[1][currentMove];
14139         DisplayBothClocks();
14140         DisplayMove(currentMove - 1);
14141         ClearHighlights();/*!! could figure this out*/
14142         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14143         SendToProgram("remove\n", &first);
14144         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14145         break;
14146
14147       case BeginningOfGame:
14148       default:
14149         break;
14150
14151       case IcsPlayingWhite:
14152       case IcsPlayingBlack:
14153         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14154             SendToICS(ics_prefix);
14155             SendToICS("takeback 2\n");
14156         } else {
14157             SendToICS(ics_prefix);
14158             SendToICS("takeback 1\n");
14159         }
14160         break;
14161     }
14162 }
14163
14164 void
14165 MoveNowEvent()
14166 {
14167     ChessProgramState *cps;
14168
14169     switch (gameMode) {
14170       case MachinePlaysWhite:
14171         if (!WhiteOnMove(forwardMostMove)) {
14172             DisplayError(_("It is your turn"), 0);
14173             return;
14174         }
14175         cps = &first;
14176         break;
14177       case MachinePlaysBlack:
14178         if (WhiteOnMove(forwardMostMove)) {
14179             DisplayError(_("It is your turn"), 0);
14180             return;
14181         }
14182         cps = &first;
14183         break;
14184       case TwoMachinesPlay:
14185         if (WhiteOnMove(forwardMostMove) ==
14186             (first.twoMachinesColor[0] == 'w')) {
14187             cps = &first;
14188         } else {
14189             cps = &second;
14190         }
14191         break;
14192       case BeginningOfGame:
14193       default:
14194         return;
14195     }
14196     SendToProgram("?\n", cps);
14197 }
14198
14199 void
14200 TruncateGameEvent()
14201 {
14202     EditGameEvent();
14203     if (gameMode != EditGame) return;
14204     TruncateGame();
14205 }
14206
14207 void
14208 TruncateGame()
14209 {
14210     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14211     if (forwardMostMove > currentMove) {
14212         if (gameInfo.resultDetails != NULL) {
14213             free(gameInfo.resultDetails);
14214             gameInfo.resultDetails = NULL;
14215             gameInfo.result = GameUnfinished;
14216         }
14217         forwardMostMove = currentMove;
14218         HistorySet(parseList, backwardMostMove, forwardMostMove,
14219                    currentMove-1);
14220     }
14221 }
14222
14223 void
14224 HintEvent()
14225 {
14226     if (appData.noChessProgram) return;
14227     switch (gameMode) {
14228       case MachinePlaysWhite:
14229         if (WhiteOnMove(forwardMostMove)) {
14230             DisplayError(_("Wait until your turn"), 0);
14231             return;
14232         }
14233         break;
14234       case BeginningOfGame:
14235       case MachinePlaysBlack:
14236         if (!WhiteOnMove(forwardMostMove)) {
14237             DisplayError(_("Wait until your turn"), 0);
14238             return;
14239         }
14240         break;
14241       default:
14242         DisplayError(_("No hint available"), 0);
14243         return;
14244     }
14245     SendToProgram("hint\n", &first);
14246     hintRequested = TRUE;
14247 }
14248
14249 void
14250 BookEvent()
14251 {
14252     if (appData.noChessProgram) return;
14253     switch (gameMode) {
14254       case MachinePlaysWhite:
14255         if (WhiteOnMove(forwardMostMove)) {
14256             DisplayError(_("Wait until your turn"), 0);
14257             return;
14258         }
14259         break;
14260       case BeginningOfGame:
14261       case MachinePlaysBlack:
14262         if (!WhiteOnMove(forwardMostMove)) {
14263             DisplayError(_("Wait until your turn"), 0);
14264             return;
14265         }
14266         break;
14267       case EditPosition:
14268         EditPositionDone(TRUE);
14269         break;
14270       case TwoMachinesPlay:
14271         return;
14272       default:
14273         break;
14274     }
14275     SendToProgram("bk\n", &first);
14276     bookOutput[0] = NULLCHAR;
14277     bookRequested = TRUE;
14278 }
14279
14280 void
14281 AboutGameEvent()
14282 {
14283     char *tags = PGNTags(&gameInfo);
14284     TagsPopUp(tags, CmailMsg());
14285     free(tags);
14286 }
14287
14288 /* end button procedures */
14289
14290 void
14291 PrintPosition(fp, move)
14292      FILE *fp;
14293      int move;
14294 {
14295     int i, j;
14296
14297     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14298         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14299             char c = PieceToChar(boards[move][i][j]);
14300             fputc(c == 'x' ? '.' : c, fp);
14301             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14302         }
14303     }
14304     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14305       fprintf(fp, "white to play\n");
14306     else
14307       fprintf(fp, "black to play\n");
14308 }
14309
14310 void
14311 PrintOpponents(fp)
14312      FILE *fp;
14313 {
14314     if (gameInfo.white != NULL) {
14315         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14316     } else {
14317         fprintf(fp, "\n");
14318     }
14319 }
14320
14321 /* Find last component of program's own name, using some heuristics */
14322 void
14323 TidyProgramName(prog, host, buf)
14324      char *prog, *host, buf[MSG_SIZ];
14325 {
14326     char *p, *q;
14327     int local = (strcmp(host, "localhost") == 0);
14328     while (!local && (p = strchr(prog, ';')) != NULL) {
14329         p++;
14330         while (*p == ' ') p++;
14331         prog = p;
14332     }
14333     if (*prog == '"' || *prog == '\'') {
14334         q = strchr(prog + 1, *prog);
14335     } else {
14336         q = strchr(prog, ' ');
14337     }
14338     if (q == NULL) q = prog + strlen(prog);
14339     p = q;
14340     while (p >= prog && *p != '/' && *p != '\\') p--;
14341     p++;
14342     if(p == prog && *p == '"') p++;
14343     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14344     memcpy(buf, p, q - p);
14345     buf[q - p] = NULLCHAR;
14346     if (!local) {
14347         strcat(buf, "@");
14348         strcat(buf, host);
14349     }
14350 }
14351
14352 char *
14353 TimeControlTagValue()
14354 {
14355     char buf[MSG_SIZ];
14356     if (!appData.clockMode) {
14357       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14358     } else if (movesPerSession > 0) {
14359       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14360     } else if (timeIncrement == 0) {
14361       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14362     } else {
14363       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14364     }
14365     return StrSave(buf);
14366 }
14367
14368 void
14369 SetGameInfo()
14370 {
14371     /* This routine is used only for certain modes */
14372     VariantClass v = gameInfo.variant;
14373     ChessMove r = GameUnfinished;
14374     char *p = NULL;
14375
14376     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14377         r = gameInfo.result;
14378         p = gameInfo.resultDetails;
14379         gameInfo.resultDetails = NULL;
14380     }
14381     ClearGameInfo(&gameInfo);
14382     gameInfo.variant = v;
14383
14384     switch (gameMode) {
14385       case MachinePlaysWhite:
14386         gameInfo.event = StrSave( appData.pgnEventHeader );
14387         gameInfo.site = StrSave(HostName());
14388         gameInfo.date = PGNDate();
14389         gameInfo.round = StrSave("-");
14390         gameInfo.white = StrSave(first.tidy);
14391         gameInfo.black = StrSave(UserName());
14392         gameInfo.timeControl = TimeControlTagValue();
14393         break;
14394
14395       case MachinePlaysBlack:
14396         gameInfo.event = StrSave( appData.pgnEventHeader );
14397         gameInfo.site = StrSave(HostName());
14398         gameInfo.date = PGNDate();
14399         gameInfo.round = StrSave("-");
14400         gameInfo.white = StrSave(UserName());
14401         gameInfo.black = StrSave(first.tidy);
14402         gameInfo.timeControl = TimeControlTagValue();
14403         break;
14404
14405       case TwoMachinesPlay:
14406         gameInfo.event = StrSave( appData.pgnEventHeader );
14407         gameInfo.site = StrSave(HostName());
14408         gameInfo.date = PGNDate();
14409         if (roundNr > 0) {
14410             char buf[MSG_SIZ];
14411             snprintf(buf, MSG_SIZ, "%d", roundNr);
14412             gameInfo.round = StrSave(buf);
14413         } else {
14414             gameInfo.round = StrSave("-");
14415         }
14416         if (first.twoMachinesColor[0] == 'w') {
14417             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14418             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14419         } else {
14420             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14421             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14422         }
14423         gameInfo.timeControl = TimeControlTagValue();
14424         break;
14425
14426       case EditGame:
14427         gameInfo.event = StrSave("Edited game");
14428         gameInfo.site = StrSave(HostName());
14429         gameInfo.date = PGNDate();
14430         gameInfo.round = StrSave("-");
14431         gameInfo.white = StrSave("-");
14432         gameInfo.black = StrSave("-");
14433         gameInfo.result = r;
14434         gameInfo.resultDetails = p;
14435         break;
14436
14437       case EditPosition:
14438         gameInfo.event = StrSave("Edited position");
14439         gameInfo.site = StrSave(HostName());
14440         gameInfo.date = PGNDate();
14441         gameInfo.round = StrSave("-");
14442         gameInfo.white = StrSave("-");
14443         gameInfo.black = StrSave("-");
14444         break;
14445
14446       case IcsPlayingWhite:
14447       case IcsPlayingBlack:
14448       case IcsObserving:
14449       case IcsExamining:
14450         break;
14451
14452       case PlayFromGameFile:
14453         gameInfo.event = StrSave("Game from non-PGN file");
14454         gameInfo.site = StrSave(HostName());
14455         gameInfo.date = PGNDate();
14456         gameInfo.round = StrSave("-");
14457         gameInfo.white = StrSave("?");
14458         gameInfo.black = StrSave("?");
14459         break;
14460
14461       default:
14462         break;
14463     }
14464 }
14465
14466 void
14467 ReplaceComment(index, text)
14468      int index;
14469      char *text;
14470 {
14471     int len;
14472     char *p;
14473     float score;
14474
14475     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14476        pvInfoList[index-1].depth == len &&
14477        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14478        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14479     while (*text == '\n') text++;
14480     len = strlen(text);
14481     while (len > 0 && text[len - 1] == '\n') len--;
14482
14483     if (commentList[index] != NULL)
14484       free(commentList[index]);
14485
14486     if (len == 0) {
14487         commentList[index] = NULL;
14488         return;
14489     }
14490   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14491       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14492       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14493     commentList[index] = (char *) malloc(len + 2);
14494     strncpy(commentList[index], text, len);
14495     commentList[index][len] = '\n';
14496     commentList[index][len + 1] = NULLCHAR;
14497   } else {
14498     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14499     char *p;
14500     commentList[index] = (char *) malloc(len + 7);
14501     safeStrCpy(commentList[index], "{\n", 3);
14502     safeStrCpy(commentList[index]+2, text, len+1);
14503     commentList[index][len+2] = NULLCHAR;
14504     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14505     strcat(commentList[index], "\n}\n");
14506   }
14507 }
14508
14509 void
14510 CrushCRs(text)
14511      char *text;
14512 {
14513   char *p = text;
14514   char *q = text;
14515   char ch;
14516
14517   do {
14518     ch = *p++;
14519     if (ch == '\r') continue;
14520     *q++ = ch;
14521   } while (ch != '\0');
14522 }
14523
14524 void
14525 AppendComment(index, text, addBraces)
14526      int index;
14527      char *text;
14528      Boolean addBraces; // [HGM] braces: tells if we should add {}
14529 {
14530     int oldlen, len;
14531     char *old;
14532
14533 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14534     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14535
14536     CrushCRs(text);
14537     while (*text == '\n') text++;
14538     len = strlen(text);
14539     while (len > 0 && text[len - 1] == '\n') len--;
14540
14541     if (len == 0) return;
14542
14543     if (commentList[index] != NULL) {
14544         old = commentList[index];
14545         oldlen = strlen(old);
14546         while(commentList[index][oldlen-1] ==  '\n')
14547           commentList[index][--oldlen] = NULLCHAR;
14548         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14549         safeStrCpy(commentList[index], old, oldlen + len + 6);
14550         free(old);
14551         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14552         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14553           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14554           while (*text == '\n') { text++; len--; }
14555           commentList[index][--oldlen] = NULLCHAR;
14556       }
14557         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14558         else          strcat(commentList[index], "\n");
14559         strcat(commentList[index], text);
14560         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14561         else          strcat(commentList[index], "\n");
14562     } else {
14563         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14564         if(addBraces)
14565           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14566         else commentList[index][0] = NULLCHAR;
14567         strcat(commentList[index], text);
14568         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14569         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14570     }
14571 }
14572
14573 static char * FindStr( char * text, char * sub_text )
14574 {
14575     char * result = strstr( text, sub_text );
14576
14577     if( result != NULL ) {
14578         result += strlen( sub_text );
14579     }
14580
14581     return result;
14582 }
14583
14584 /* [AS] Try to extract PV info from PGN comment */
14585 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14586 char *GetInfoFromComment( int index, char * text )
14587 {
14588     char * sep = text, *p;
14589
14590     if( text != NULL && index > 0 ) {
14591         int score = 0;
14592         int depth = 0;
14593         int time = -1, sec = 0, deci;
14594         char * s_eval = FindStr( text, "[%eval " );
14595         char * s_emt = FindStr( text, "[%emt " );
14596
14597         if( s_eval != NULL || s_emt != NULL ) {
14598             /* New style */
14599             char delim;
14600
14601             if( s_eval != NULL ) {
14602                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14603                     return text;
14604                 }
14605
14606                 if( delim != ']' ) {
14607                     return text;
14608                 }
14609             }
14610
14611             if( s_emt != NULL ) {
14612             }
14613                 return text;
14614         }
14615         else {
14616             /* We expect something like: [+|-]nnn.nn/dd */
14617             int score_lo = 0;
14618
14619             if(*text != '{') return text; // [HGM] braces: must be normal comment
14620
14621             sep = strchr( text, '/' );
14622             if( sep == NULL || sep < (text+4) ) {
14623                 return text;
14624             }
14625
14626             p = text;
14627             if(p[1] == '(') { // comment starts with PV
14628                p = strchr(p, ')'); // locate end of PV
14629                if(p == NULL || sep < p+5) return text;
14630                // at this point we have something like "{(.*) +0.23/6 ..."
14631                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14632                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14633                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14634             }
14635             time = -1; sec = -1; deci = -1;
14636             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14637                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14638                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14639                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14640                 return text;
14641             }
14642
14643             if( score_lo < 0 || score_lo >= 100 ) {
14644                 return text;
14645             }
14646
14647             if(sec >= 0) time = 600*time + 10*sec; else
14648             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14649
14650             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14651
14652             /* [HGM] PV time: now locate end of PV info */
14653             while( *++sep >= '0' && *sep <= '9'); // strip depth
14654             if(time >= 0)
14655             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14656             if(sec >= 0)
14657             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14658             if(deci >= 0)
14659             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14660             while(*sep == ' ') sep++;
14661         }
14662
14663         if( depth <= 0 ) {
14664             return text;
14665         }
14666
14667         if( time < 0 ) {
14668             time = -1;
14669         }
14670
14671         pvInfoList[index-1].depth = depth;
14672         pvInfoList[index-1].score = score;
14673         pvInfoList[index-1].time  = 10*time; // centi-sec
14674         if(*sep == '}') *sep = 0; else *--sep = '{';
14675         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14676     }
14677     return sep;
14678 }
14679
14680 void
14681 SendToProgram(message, cps)
14682      char *message;
14683      ChessProgramState *cps;
14684 {
14685     int count, outCount, error;
14686     char buf[MSG_SIZ];
14687
14688     if (cps->pr == NULL) return;
14689     Attention(cps);
14690
14691     if (appData.debugMode) {
14692         TimeMark now;
14693         GetTimeMark(&now);
14694         fprintf(debugFP, "%ld >%-6s: %s",
14695                 SubtractTimeMarks(&now, &programStartTime),
14696                 cps->which, message);
14697     }
14698
14699     count = strlen(message);
14700     outCount = OutputToProcess(cps->pr, message, count, &error);
14701     if (outCount < count && !exiting
14702                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14703       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14704       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14705         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14706             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14707                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14708                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14709                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14710             } else {
14711                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14712                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14713                 gameInfo.result = res;
14714             }
14715             gameInfo.resultDetails = StrSave(buf);
14716         }
14717         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14718         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14719     }
14720 }
14721
14722 void
14723 ReceiveFromProgram(isr, closure, message, count, error)
14724      InputSourceRef isr;
14725      VOIDSTAR closure;
14726      char *message;
14727      int count;
14728      int error;
14729 {
14730     char *end_str;
14731     char buf[MSG_SIZ];
14732     ChessProgramState *cps = (ChessProgramState *)closure;
14733
14734     if (isr != cps->isr) return; /* Killed intentionally */
14735     if (count <= 0) {
14736         if (count == 0) {
14737             RemoveInputSource(cps->isr);
14738             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14739             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14740                     _(cps->which), cps->program);
14741         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14742                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14743                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14744                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14745                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14746                 } else {
14747                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14748                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14749                     gameInfo.result = res;
14750                 }
14751                 gameInfo.resultDetails = StrSave(buf);
14752             }
14753             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14754             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14755         } else {
14756             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14757                     _(cps->which), cps->program);
14758             RemoveInputSource(cps->isr);
14759
14760             /* [AS] Program is misbehaving badly... kill it */
14761             if( count == -2 ) {
14762                 DestroyChildProcess( cps->pr, 9 );
14763                 cps->pr = NoProc;
14764             }
14765
14766             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14767         }
14768         return;
14769     }
14770
14771     if ((end_str = strchr(message, '\r')) != NULL)
14772       *end_str = NULLCHAR;
14773     if ((end_str = strchr(message, '\n')) != NULL)
14774       *end_str = NULLCHAR;
14775
14776     if (appData.debugMode) {
14777         TimeMark now; int print = 1;
14778         char *quote = ""; char c; int i;
14779
14780         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14781                 char start = message[0];
14782                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14783                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14784                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14785                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14786                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14787                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14788                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14789                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14790                    sscanf(message, "hint: %c", &c)!=1 && 
14791                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14792                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14793                     print = (appData.engineComments >= 2);
14794                 }
14795                 message[0] = start; // restore original message
14796         }
14797         if(print) {
14798                 GetTimeMark(&now);
14799                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14800                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14801                         quote,
14802                         message);
14803         }
14804     }
14805
14806     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14807     if (appData.icsEngineAnalyze) {
14808         if (strstr(message, "whisper") != NULL ||
14809              strstr(message, "kibitz") != NULL ||
14810             strstr(message, "tellics") != NULL) return;
14811     }
14812
14813     HandleMachineMove(message, cps);
14814 }
14815
14816
14817 void
14818 SendTimeControl(cps, mps, tc, inc, sd, st)
14819      ChessProgramState *cps;
14820      int mps, inc, sd, st;
14821      long tc;
14822 {
14823     char buf[MSG_SIZ];
14824     int seconds;
14825
14826     if( timeControl_2 > 0 ) {
14827         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14828             tc = timeControl_2;
14829         }
14830     }
14831     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14832     inc /= cps->timeOdds;
14833     st  /= cps->timeOdds;
14834
14835     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14836
14837     if (st > 0) {
14838       /* Set exact time per move, normally using st command */
14839       if (cps->stKludge) {
14840         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14841         seconds = st % 60;
14842         if (seconds == 0) {
14843           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14844         } else {
14845           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14846         }
14847       } else {
14848         snprintf(buf, MSG_SIZ, "st %d\n", st);
14849       }
14850     } else {
14851       /* Set conventional or incremental time control, using level command */
14852       if (seconds == 0) {
14853         /* Note old gnuchess bug -- minutes:seconds used to not work.
14854            Fixed in later versions, but still avoid :seconds
14855            when seconds is 0. */
14856         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14857       } else {
14858         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14859                  seconds, inc/1000.);
14860       }
14861     }
14862     SendToProgram(buf, cps);
14863
14864     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14865     /* Orthogonally, limit search to given depth */
14866     if (sd > 0) {
14867       if (cps->sdKludge) {
14868         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14869       } else {
14870         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14871       }
14872       SendToProgram(buf, cps);
14873     }
14874
14875     if(cps->nps >= 0) { /* [HGM] nps */
14876         if(cps->supportsNPS == FALSE)
14877           cps->nps = -1; // don't use if engine explicitly says not supported!
14878         else {
14879           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14880           SendToProgram(buf, cps);
14881         }
14882     }
14883 }
14884
14885 ChessProgramState *WhitePlayer()
14886 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14887 {
14888     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14889        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14890         return &second;
14891     return &first;
14892 }
14893
14894 void
14895 SendTimeRemaining(cps, machineWhite)
14896      ChessProgramState *cps;
14897      int /*boolean*/ machineWhite;
14898 {
14899     char message[MSG_SIZ];
14900     long time, otime;
14901
14902     /* Note: this routine must be called when the clocks are stopped
14903        or when they have *just* been set or switched; otherwise
14904        it will be off by the time since the current tick started.
14905     */
14906     if (machineWhite) {
14907         time = whiteTimeRemaining / 10;
14908         otime = blackTimeRemaining / 10;
14909     } else {
14910         time = blackTimeRemaining / 10;
14911         otime = whiteTimeRemaining / 10;
14912     }
14913     /* [HGM] translate opponent's time by time-odds factor */
14914     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14915     if (appData.debugMode) {
14916         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14917     }
14918
14919     if (time <= 0) time = 1;
14920     if (otime <= 0) otime = 1;
14921
14922     snprintf(message, MSG_SIZ, "time %ld\n", time);
14923     SendToProgram(message, cps);
14924
14925     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14926     SendToProgram(message, cps);
14927 }
14928
14929 int
14930 BoolFeature(p, name, loc, cps)
14931      char **p;
14932      char *name;
14933      int *loc;
14934      ChessProgramState *cps;
14935 {
14936   char buf[MSG_SIZ];
14937   int len = strlen(name);
14938   int val;
14939
14940   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14941     (*p) += len + 1;
14942     sscanf(*p, "%d", &val);
14943     *loc = (val != 0);
14944     while (**p && **p != ' ')
14945       (*p)++;
14946     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14947     SendToProgram(buf, cps);
14948     return TRUE;
14949   }
14950   return FALSE;
14951 }
14952
14953 int
14954 IntFeature(p, name, loc, cps)
14955      char **p;
14956      char *name;
14957      int *loc;
14958      ChessProgramState *cps;
14959 {
14960   char buf[MSG_SIZ];
14961   int len = strlen(name);
14962   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14963     (*p) += len + 1;
14964     sscanf(*p, "%d", loc);
14965     while (**p && **p != ' ') (*p)++;
14966     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14967     SendToProgram(buf, cps);
14968     return TRUE;
14969   }
14970   return FALSE;
14971 }
14972
14973 int
14974 StringFeature(p, name, loc, cps)
14975      char **p;
14976      char *name;
14977      char loc[];
14978      ChessProgramState *cps;
14979 {
14980   char buf[MSG_SIZ];
14981   int len = strlen(name);
14982   if (strncmp((*p), name, len) == 0
14983       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14984     (*p) += len + 2;
14985     sscanf(*p, "%[^\"]", loc);
14986     while (**p && **p != '\"') (*p)++;
14987     if (**p == '\"') (*p)++;
14988     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14989     SendToProgram(buf, cps);
14990     return TRUE;
14991   }
14992   return FALSE;
14993 }
14994
14995 int
14996 ParseOption(Option *opt, ChessProgramState *cps)
14997 // [HGM] options: process the string that defines an engine option, and determine
14998 // name, type, default value, and allowed value range
14999 {
15000         char *p, *q, buf[MSG_SIZ];
15001         int n, min = (-1)<<31, max = 1<<31, def;
15002
15003         if(p = strstr(opt->name, " -spin ")) {
15004             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15005             if(max < min) max = min; // enforce consistency
15006             if(def < min) def = min;
15007             if(def > max) def = max;
15008             opt->value = def;
15009             opt->min = min;
15010             opt->max = max;
15011             opt->type = Spin;
15012         } else if((p = strstr(opt->name, " -slider "))) {
15013             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15014             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15015             if(max < min) max = min; // enforce consistency
15016             if(def < min) def = min;
15017             if(def > max) def = max;
15018             opt->value = def;
15019             opt->min = min;
15020             opt->max = max;
15021             opt->type = Spin; // Slider;
15022         } else if((p = strstr(opt->name, " -string "))) {
15023             opt->textValue = p+9;
15024             opt->type = TextBox;
15025         } else if((p = strstr(opt->name, " -file "))) {
15026             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15027             opt->textValue = p+7;
15028             opt->type = FileName; // FileName;
15029         } else if((p = strstr(opt->name, " -path "))) {
15030             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15031             opt->textValue = p+7;
15032             opt->type = PathName; // PathName;
15033         } else if(p = strstr(opt->name, " -check ")) {
15034             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15035             opt->value = (def != 0);
15036             opt->type = CheckBox;
15037         } else if(p = strstr(opt->name, " -combo ")) {
15038             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15039             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15040             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15041             opt->value = n = 0;
15042             while(q = StrStr(q, " /// ")) {
15043                 n++; *q = 0;    // count choices, and null-terminate each of them
15044                 q += 5;
15045                 if(*q == '*') { // remember default, which is marked with * prefix
15046                     q++;
15047                     opt->value = n;
15048                 }
15049                 cps->comboList[cps->comboCnt++] = q;
15050             }
15051             cps->comboList[cps->comboCnt++] = NULL;
15052             opt->max = n + 1;
15053             opt->type = ComboBox;
15054         } else if(p = strstr(opt->name, " -button")) {
15055             opt->type = Button;
15056         } else if(p = strstr(opt->name, " -save")) {
15057             opt->type = SaveButton;
15058         } else return FALSE;
15059         *p = 0; // terminate option name
15060         // now look if the command-line options define a setting for this engine option.
15061         if(cps->optionSettings && cps->optionSettings[0])
15062             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15063         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15064           snprintf(buf, MSG_SIZ, "option %s", p);
15065                 if(p = strstr(buf, ",")) *p = 0;
15066                 if(q = strchr(buf, '=')) switch(opt->type) {
15067                     case ComboBox:
15068                         for(n=0; n<opt->max; n++)
15069                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15070                         break;
15071                     case TextBox:
15072                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15073                         break;
15074                     case Spin:
15075                     case CheckBox:
15076                         opt->value = atoi(q+1);
15077                     default:
15078                         break;
15079                 }
15080                 strcat(buf, "\n");
15081                 SendToProgram(buf, cps);
15082         }
15083         return TRUE;
15084 }
15085
15086 void
15087 FeatureDone(cps, val)
15088      ChessProgramState* cps;
15089      int val;
15090 {
15091   DelayedEventCallback cb = GetDelayedEvent();
15092   if ((cb == InitBackEnd3 && cps == &first) ||
15093       (cb == SettingsMenuIfReady && cps == &second) ||
15094       (cb == LoadEngine) ||
15095       (cb == TwoMachinesEventIfReady)) {
15096     CancelDelayedEvent();
15097     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15098   }
15099   cps->initDone = val;
15100 }
15101
15102 /* Parse feature command from engine */
15103 void
15104 ParseFeatures(args, cps)
15105      char* args;
15106      ChessProgramState *cps;
15107 {
15108   char *p = args;
15109   char *q;
15110   int val;
15111   char buf[MSG_SIZ];
15112
15113   for (;;) {
15114     while (*p == ' ') p++;
15115     if (*p == NULLCHAR) return;
15116
15117     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15118     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15119     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15120     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15121     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15122     if (BoolFeature(&p, "reuse", &val, cps)) {
15123       /* Engine can disable reuse, but can't enable it if user said no */
15124       if (!val) cps->reuse = FALSE;
15125       continue;
15126     }
15127     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15128     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15129       if (gameMode == TwoMachinesPlay) {
15130         DisplayTwoMachinesTitle();
15131       } else {
15132         DisplayTitle("");
15133       }
15134       continue;
15135     }
15136     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15137     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15138     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15139     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15140     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15141     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15142     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15143     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15144     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15145     if (IntFeature(&p, "done", &val, cps)) {
15146       FeatureDone(cps, val);
15147       continue;
15148     }
15149     /* Added by Tord: */
15150     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15151     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15152     /* End of additions by Tord */
15153
15154     /* [HGM] added features: */
15155     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15156     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15157     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15158     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15159     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15160     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15161     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15162         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15163           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15164             SendToProgram(buf, cps);
15165             continue;
15166         }
15167         if(cps->nrOptions >= MAX_OPTIONS) {
15168             cps->nrOptions--;
15169             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15170             DisplayError(buf, 0);
15171         }
15172         continue;
15173     }
15174     /* End of additions by HGM */
15175
15176     /* unknown feature: complain and skip */
15177     q = p;
15178     while (*q && *q != '=') q++;
15179     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15180     SendToProgram(buf, cps);
15181     p = q;
15182     if (*p == '=') {
15183       p++;
15184       if (*p == '\"') {
15185         p++;
15186         while (*p && *p != '\"') p++;
15187         if (*p == '\"') p++;
15188       } else {
15189         while (*p && *p != ' ') p++;
15190       }
15191     }
15192   }
15193
15194 }
15195
15196 void
15197 PeriodicUpdatesEvent(newState)
15198      int newState;
15199 {
15200     if (newState == appData.periodicUpdates)
15201       return;
15202
15203     appData.periodicUpdates=newState;
15204
15205     /* Display type changes, so update it now */
15206 //    DisplayAnalysis();
15207
15208     /* Get the ball rolling again... */
15209     if (newState) {
15210         AnalysisPeriodicEvent(1);
15211         StartAnalysisClock();
15212     }
15213 }
15214
15215 void
15216 PonderNextMoveEvent(newState)
15217      int newState;
15218 {
15219     if (newState == appData.ponderNextMove) return;
15220     if (gameMode == EditPosition) EditPositionDone(TRUE);
15221     if (newState) {
15222         SendToProgram("hard\n", &first);
15223         if (gameMode == TwoMachinesPlay) {
15224             SendToProgram("hard\n", &second);
15225         }
15226     } else {
15227         SendToProgram("easy\n", &first);
15228         thinkOutput[0] = NULLCHAR;
15229         if (gameMode == TwoMachinesPlay) {
15230             SendToProgram("easy\n", &second);
15231         }
15232     }
15233     appData.ponderNextMove = newState;
15234 }
15235
15236 void
15237 NewSettingEvent(option, feature, command, value)
15238      char *command;
15239      int option, value, *feature;
15240 {
15241     char buf[MSG_SIZ];
15242
15243     if (gameMode == EditPosition) EditPositionDone(TRUE);
15244     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15245     if(feature == NULL || *feature) SendToProgram(buf, &first);
15246     if (gameMode == TwoMachinesPlay) {
15247         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15248     }
15249 }
15250
15251 void
15252 ShowThinkingEvent()
15253 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15254 {
15255     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15256     int newState = appData.showThinking
15257         // [HGM] thinking: other features now need thinking output as well
15258         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15259
15260     if (oldState == newState) return;
15261     oldState = newState;
15262     if (gameMode == EditPosition) EditPositionDone(TRUE);
15263     if (oldState) {
15264         SendToProgram("post\n", &first);
15265         if (gameMode == TwoMachinesPlay) {
15266             SendToProgram("post\n", &second);
15267         }
15268     } else {
15269         SendToProgram("nopost\n", &first);
15270         thinkOutput[0] = NULLCHAR;
15271         if (gameMode == TwoMachinesPlay) {
15272             SendToProgram("nopost\n", &second);
15273         }
15274     }
15275 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15276 }
15277
15278 void
15279 AskQuestionEvent(title, question, replyPrefix, which)
15280      char *title; char *question; char *replyPrefix; char *which;
15281 {
15282   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15283   if (pr == NoProc) return;
15284   AskQuestion(title, question, replyPrefix, pr);
15285 }
15286
15287 void
15288 TypeInEvent(char firstChar)
15289 {
15290     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15291         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15292         gameMode == AnalyzeMode || gameMode == EditGame || 
15293         gameMode == EditPosition || gameMode == IcsExamining ||
15294         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15295         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15296                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15297                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15298         gameMode == Training) PopUpMoveDialog(firstChar);
15299 }
15300
15301 void
15302 TypeInDoneEvent(char *move)
15303 {
15304         Board board;
15305         int n, fromX, fromY, toX, toY;
15306         char promoChar;
15307         ChessMove moveType;
15308
15309         // [HGM] FENedit
15310         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15311                 EditPositionPasteFEN(move);
15312                 return;
15313         }
15314         // [HGM] movenum: allow move number to be typed in any mode
15315         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15316           ToNrEvent(2*n-1);
15317           return;
15318         }
15319
15320       if (gameMode != EditGame && currentMove != forwardMostMove && 
15321         gameMode != Training) {
15322         DisplayMoveError(_("Displayed move is not current"));
15323       } else {
15324         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15325           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15326         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15327         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15328           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15329           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15330         } else {
15331           DisplayMoveError(_("Could not parse move"));
15332         }
15333       }
15334 }
15335
15336 void
15337 DisplayMove(moveNumber)
15338      int moveNumber;
15339 {
15340     char message[MSG_SIZ];
15341     char res[MSG_SIZ];
15342     char cpThinkOutput[MSG_SIZ];
15343
15344     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15345
15346     if (moveNumber == forwardMostMove - 1 ||
15347         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15348
15349         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15350
15351         if (strchr(cpThinkOutput, '\n')) {
15352             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15353         }
15354     } else {
15355         *cpThinkOutput = NULLCHAR;
15356     }
15357
15358     /* [AS] Hide thinking from human user */
15359     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15360         *cpThinkOutput = NULLCHAR;
15361         if( thinkOutput[0] != NULLCHAR ) {
15362             int i;
15363
15364             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15365                 cpThinkOutput[i] = '.';
15366             }
15367             cpThinkOutput[i] = NULLCHAR;
15368             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15369         }
15370     }
15371
15372     if (moveNumber == forwardMostMove - 1 &&
15373         gameInfo.resultDetails != NULL) {
15374         if (gameInfo.resultDetails[0] == NULLCHAR) {
15375           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15376         } else {
15377           snprintf(res, MSG_SIZ, " {%s} %s",
15378                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15379         }
15380     } else {
15381         res[0] = NULLCHAR;
15382     }
15383
15384     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15385         DisplayMessage(res, cpThinkOutput);
15386     } else {
15387       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15388                 WhiteOnMove(moveNumber) ? " " : ".. ",
15389                 parseList[moveNumber], res);
15390         DisplayMessage(message, cpThinkOutput);
15391     }
15392 }
15393
15394 void
15395 DisplayComment(moveNumber, text)
15396      int moveNumber;
15397      char *text;
15398 {
15399     char title[MSG_SIZ];
15400
15401     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15402       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15403     } else {
15404       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15405               WhiteOnMove(moveNumber) ? " " : ".. ",
15406               parseList[moveNumber]);
15407     }
15408     if (text != NULL && (appData.autoDisplayComment || commentUp))
15409         CommentPopUp(title, text);
15410 }
15411
15412 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15413  * might be busy thinking or pondering.  It can be omitted if your
15414  * gnuchess is configured to stop thinking immediately on any user
15415  * input.  However, that gnuchess feature depends on the FIONREAD
15416  * ioctl, which does not work properly on some flavors of Unix.
15417  */
15418 void
15419 Attention(cps)
15420      ChessProgramState *cps;
15421 {
15422 #if ATTENTION
15423     if (!cps->useSigint) return;
15424     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15425     switch (gameMode) {
15426       case MachinePlaysWhite:
15427       case MachinePlaysBlack:
15428       case TwoMachinesPlay:
15429       case IcsPlayingWhite:
15430       case IcsPlayingBlack:
15431       case AnalyzeMode:
15432       case AnalyzeFile:
15433         /* Skip if we know it isn't thinking */
15434         if (!cps->maybeThinking) return;
15435         if (appData.debugMode)
15436           fprintf(debugFP, "Interrupting %s\n", cps->which);
15437         InterruptChildProcess(cps->pr);
15438         cps->maybeThinking = FALSE;
15439         break;
15440       default:
15441         break;
15442     }
15443 #endif /*ATTENTION*/
15444 }
15445
15446 int
15447 CheckFlags()
15448 {
15449     if (whiteTimeRemaining <= 0) {
15450         if (!whiteFlag) {
15451             whiteFlag = TRUE;
15452             if (appData.icsActive) {
15453                 if (appData.autoCallFlag &&
15454                     gameMode == IcsPlayingBlack && !blackFlag) {
15455                   SendToICS(ics_prefix);
15456                   SendToICS("flag\n");
15457                 }
15458             } else {
15459                 if (blackFlag) {
15460                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15461                 } else {
15462                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15463                     if (appData.autoCallFlag) {
15464                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15465                         return TRUE;
15466                     }
15467                 }
15468             }
15469         }
15470     }
15471     if (blackTimeRemaining <= 0) {
15472         if (!blackFlag) {
15473             blackFlag = TRUE;
15474             if (appData.icsActive) {
15475                 if (appData.autoCallFlag &&
15476                     gameMode == IcsPlayingWhite && !whiteFlag) {
15477                   SendToICS(ics_prefix);
15478                   SendToICS("flag\n");
15479                 }
15480             } else {
15481                 if (whiteFlag) {
15482                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15483                 } else {
15484                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15485                     if (appData.autoCallFlag) {
15486                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15487                         return TRUE;
15488                     }
15489                 }
15490             }
15491         }
15492     }
15493     return FALSE;
15494 }
15495
15496 void
15497 CheckTimeControl()
15498 {
15499     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15500         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15501
15502     /*
15503      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15504      */
15505     if ( !WhiteOnMove(forwardMostMove) ) {
15506         /* White made time control */
15507         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15508         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15509         /* [HGM] time odds: correct new time quota for time odds! */
15510                                             / WhitePlayer()->timeOdds;
15511         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15512     } else {
15513         lastBlack -= blackTimeRemaining;
15514         /* Black made time control */
15515         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15516                                             / WhitePlayer()->other->timeOdds;
15517         lastWhite = whiteTimeRemaining;
15518     }
15519 }
15520
15521 void
15522 DisplayBothClocks()
15523 {
15524     int wom = gameMode == EditPosition ?
15525       !blackPlaysFirst : WhiteOnMove(currentMove);
15526     DisplayWhiteClock(whiteTimeRemaining, wom);
15527     DisplayBlackClock(blackTimeRemaining, !wom);
15528 }
15529
15530
15531 /* Timekeeping seems to be a portability nightmare.  I think everyone
15532    has ftime(), but I'm really not sure, so I'm including some ifdefs
15533    to use other calls if you don't.  Clocks will be less accurate if
15534    you have neither ftime nor gettimeofday.
15535 */
15536
15537 /* VS 2008 requires the #include outside of the function */
15538 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15539 #include <sys/timeb.h>
15540 #endif
15541
15542 /* Get the current time as a TimeMark */
15543 void
15544 GetTimeMark(tm)
15545      TimeMark *tm;
15546 {
15547 #if HAVE_GETTIMEOFDAY
15548
15549     struct timeval timeVal;
15550     struct timezone timeZone;
15551
15552     gettimeofday(&timeVal, &timeZone);
15553     tm->sec = (long) timeVal.tv_sec;
15554     tm->ms = (int) (timeVal.tv_usec / 1000L);
15555
15556 #else /*!HAVE_GETTIMEOFDAY*/
15557 #if HAVE_FTIME
15558
15559 // include <sys/timeb.h> / moved to just above start of function
15560     struct timeb timeB;
15561
15562     ftime(&timeB);
15563     tm->sec = (long) timeB.time;
15564     tm->ms = (int) timeB.millitm;
15565
15566 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15567     tm->sec = (long) time(NULL);
15568     tm->ms = 0;
15569 #endif
15570 #endif
15571 }
15572
15573 /* Return the difference in milliseconds between two
15574    time marks.  We assume the difference will fit in a long!
15575 */
15576 long
15577 SubtractTimeMarks(tm2, tm1)
15578      TimeMark *tm2, *tm1;
15579 {
15580     return 1000L*(tm2->sec - tm1->sec) +
15581            (long) (tm2->ms - tm1->ms);
15582 }
15583
15584
15585 /*
15586  * Code to manage the game clocks.
15587  *
15588  * In tournament play, black starts the clock and then white makes a move.
15589  * We give the human user a slight advantage if he is playing white---the
15590  * clocks don't run until he makes his first move, so it takes zero time.
15591  * Also, we don't account for network lag, so we could get out of sync
15592  * with GNU Chess's clock -- but then, referees are always right.
15593  */
15594
15595 static TimeMark tickStartTM;
15596 static long intendedTickLength;
15597
15598 long
15599 NextTickLength(timeRemaining)
15600      long timeRemaining;
15601 {
15602     long nominalTickLength, nextTickLength;
15603
15604     if (timeRemaining > 0L && timeRemaining <= 10000L)
15605       nominalTickLength = 100L;
15606     else
15607       nominalTickLength = 1000L;
15608     nextTickLength = timeRemaining % nominalTickLength;
15609     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15610
15611     return nextTickLength;
15612 }
15613
15614 /* Adjust clock one minute up or down */
15615 void
15616 AdjustClock(Boolean which, int dir)
15617 {
15618     if(which) blackTimeRemaining += 60000*dir;
15619     else      whiteTimeRemaining += 60000*dir;
15620     DisplayBothClocks();
15621 }
15622
15623 /* Stop clocks and reset to a fresh time control */
15624 void
15625 ResetClocks()
15626 {
15627     (void) StopClockTimer();
15628     if (appData.icsActive) {
15629         whiteTimeRemaining = blackTimeRemaining = 0;
15630     } else if (searchTime) {
15631         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15632         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15633     } else { /* [HGM] correct new time quote for time odds */
15634         whiteTC = blackTC = fullTimeControlString;
15635         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15636         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15637     }
15638     if (whiteFlag || blackFlag) {
15639         DisplayTitle("");
15640         whiteFlag = blackFlag = FALSE;
15641     }
15642     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15643     DisplayBothClocks();
15644 }
15645
15646 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15647
15648 /* Decrement running clock by amount of time that has passed */
15649 void
15650 DecrementClocks()
15651 {
15652     long timeRemaining;
15653     long lastTickLength, fudge;
15654     TimeMark now;
15655
15656     if (!appData.clockMode) return;
15657     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15658
15659     GetTimeMark(&now);
15660
15661     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15662
15663     /* Fudge if we woke up a little too soon */
15664     fudge = intendedTickLength - lastTickLength;
15665     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15666
15667     if (WhiteOnMove(forwardMostMove)) {
15668         if(whiteNPS >= 0) lastTickLength = 0;
15669         timeRemaining = whiteTimeRemaining -= lastTickLength;
15670         if(timeRemaining < 0 && !appData.icsActive) {
15671             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15672             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15673                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15674                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15675             }
15676         }
15677         DisplayWhiteClock(whiteTimeRemaining - fudge,
15678                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15679     } else {
15680         if(blackNPS >= 0) lastTickLength = 0;
15681         timeRemaining = blackTimeRemaining -= lastTickLength;
15682         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15683             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15684             if(suddenDeath) {
15685                 blackStartMove = forwardMostMove;
15686                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15687             }
15688         }
15689         DisplayBlackClock(blackTimeRemaining - fudge,
15690                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15691     }
15692     if (CheckFlags()) return;
15693
15694     tickStartTM = now;
15695     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15696     StartClockTimer(intendedTickLength);
15697
15698     /* if the time remaining has fallen below the alarm threshold, sound the
15699      * alarm. if the alarm has sounded and (due to a takeback or time control
15700      * with increment) the time remaining has increased to a level above the
15701      * threshold, reset the alarm so it can sound again.
15702      */
15703
15704     if (appData.icsActive && appData.icsAlarm) {
15705
15706         /* make sure we are dealing with the user's clock */
15707         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15708                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15709            )) return;
15710
15711         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15712             alarmSounded = FALSE;
15713         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15714             PlayAlarmSound();
15715             alarmSounded = TRUE;
15716         }
15717     }
15718 }
15719
15720
15721 /* A player has just moved, so stop the previously running
15722    clock and (if in clock mode) start the other one.
15723    We redisplay both clocks in case we're in ICS mode, because
15724    ICS gives us an update to both clocks after every move.
15725    Note that this routine is called *after* forwardMostMove
15726    is updated, so the last fractional tick must be subtracted
15727    from the color that is *not* on move now.
15728 */
15729 void
15730 SwitchClocks(int newMoveNr)
15731 {
15732     long lastTickLength;
15733     TimeMark now;
15734     int flagged = FALSE;
15735
15736     GetTimeMark(&now);
15737
15738     if (StopClockTimer() && appData.clockMode) {
15739         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15740         if (!WhiteOnMove(forwardMostMove)) {
15741             if(blackNPS >= 0) lastTickLength = 0;
15742             blackTimeRemaining -= lastTickLength;
15743            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15744 //         if(pvInfoList[forwardMostMove].time == -1)
15745                  pvInfoList[forwardMostMove].time =               // use GUI time
15746                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15747         } else {
15748            if(whiteNPS >= 0) lastTickLength = 0;
15749            whiteTimeRemaining -= lastTickLength;
15750            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15751 //         if(pvInfoList[forwardMostMove].time == -1)
15752                  pvInfoList[forwardMostMove].time =
15753                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15754         }
15755         flagged = CheckFlags();
15756     }
15757     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15758     CheckTimeControl();
15759
15760     if (flagged || !appData.clockMode) return;
15761
15762     switch (gameMode) {
15763       case MachinePlaysBlack:
15764       case MachinePlaysWhite:
15765       case BeginningOfGame:
15766         if (pausing) return;
15767         break;
15768
15769       case EditGame:
15770       case PlayFromGameFile:
15771       case IcsExamining:
15772         return;
15773
15774       default:
15775         break;
15776     }
15777
15778     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15779         if(WhiteOnMove(forwardMostMove))
15780              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15781         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15782     }
15783
15784     tickStartTM = now;
15785     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15786       whiteTimeRemaining : blackTimeRemaining);
15787     StartClockTimer(intendedTickLength);
15788 }
15789
15790
15791 /* Stop both clocks */
15792 void
15793 StopClocks()
15794 {
15795     long lastTickLength;
15796     TimeMark now;
15797
15798     if (!StopClockTimer()) return;
15799     if (!appData.clockMode) return;
15800
15801     GetTimeMark(&now);
15802
15803     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15804     if (WhiteOnMove(forwardMostMove)) {
15805         if(whiteNPS >= 0) lastTickLength = 0;
15806         whiteTimeRemaining -= lastTickLength;
15807         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15808     } else {
15809         if(blackNPS >= 0) lastTickLength = 0;
15810         blackTimeRemaining -= lastTickLength;
15811         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15812     }
15813     CheckFlags();
15814 }
15815
15816 /* Start clock of player on move.  Time may have been reset, so
15817    if clock is already running, stop and restart it. */
15818 void
15819 StartClocks()
15820 {
15821     (void) StopClockTimer(); /* in case it was running already */
15822     DisplayBothClocks();
15823     if (CheckFlags()) return;
15824
15825     if (!appData.clockMode) return;
15826     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15827
15828     GetTimeMark(&tickStartTM);
15829     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15830       whiteTimeRemaining : blackTimeRemaining);
15831
15832    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15833     whiteNPS = blackNPS = -1;
15834     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15835        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15836         whiteNPS = first.nps;
15837     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15838        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15839         blackNPS = first.nps;
15840     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15841         whiteNPS = second.nps;
15842     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15843         blackNPS = second.nps;
15844     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15845
15846     StartClockTimer(intendedTickLength);
15847 }
15848
15849 char *
15850 TimeString(ms)
15851      long ms;
15852 {
15853     long second, minute, hour, day;
15854     char *sign = "";
15855     static char buf[32];
15856
15857     if (ms > 0 && ms <= 9900) {
15858       /* convert milliseconds to tenths, rounding up */
15859       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15860
15861       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15862       return buf;
15863     }
15864
15865     /* convert milliseconds to seconds, rounding up */
15866     /* use floating point to avoid strangeness of integer division
15867        with negative dividends on many machines */
15868     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15869
15870     if (second < 0) {
15871         sign = "-";
15872         second = -second;
15873     }
15874
15875     day = second / (60 * 60 * 24);
15876     second = second % (60 * 60 * 24);
15877     hour = second / (60 * 60);
15878     second = second % (60 * 60);
15879     minute = second / 60;
15880     second = second % 60;
15881
15882     if (day > 0)
15883       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15884               sign, day, hour, minute, second);
15885     else if (hour > 0)
15886       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15887     else
15888       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15889
15890     return buf;
15891 }
15892
15893
15894 /*
15895  * This is necessary because some C libraries aren't ANSI C compliant yet.
15896  */
15897 char *
15898 StrStr(string, match)
15899      char *string, *match;
15900 {
15901     int i, length;
15902
15903     length = strlen(match);
15904
15905     for (i = strlen(string) - length; i >= 0; i--, string++)
15906       if (!strncmp(match, string, length))
15907         return string;
15908
15909     return NULL;
15910 }
15911
15912 char *
15913 StrCaseStr(string, match)
15914      char *string, *match;
15915 {
15916     int i, j, length;
15917
15918     length = strlen(match);
15919
15920     for (i = strlen(string) - length; i >= 0; i--, string++) {
15921         for (j = 0; j < length; j++) {
15922             if (ToLower(match[j]) != ToLower(string[j]))
15923               break;
15924         }
15925         if (j == length) return string;
15926     }
15927
15928     return NULL;
15929 }
15930
15931 #ifndef _amigados
15932 int
15933 StrCaseCmp(s1, s2)
15934      char *s1, *s2;
15935 {
15936     char c1, c2;
15937
15938     for (;;) {
15939         c1 = ToLower(*s1++);
15940         c2 = ToLower(*s2++);
15941         if (c1 > c2) return 1;
15942         if (c1 < c2) return -1;
15943         if (c1 == NULLCHAR) return 0;
15944     }
15945 }
15946
15947
15948 int
15949 ToLower(c)
15950      int c;
15951 {
15952     return isupper(c) ? tolower(c) : c;
15953 }
15954
15955
15956 int
15957 ToUpper(c)
15958      int c;
15959 {
15960     return islower(c) ? toupper(c) : c;
15961 }
15962 #endif /* !_amigados    */
15963
15964 char *
15965 StrSave(s)
15966      char *s;
15967 {
15968   char *ret;
15969
15970   if ((ret = (char *) malloc(strlen(s) + 1)))
15971     {
15972       safeStrCpy(ret, s, strlen(s)+1);
15973     }
15974   return ret;
15975 }
15976
15977 char *
15978 StrSavePtr(s, savePtr)
15979      char *s, **savePtr;
15980 {
15981     if (*savePtr) {
15982         free(*savePtr);
15983     }
15984     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15985       safeStrCpy(*savePtr, s, strlen(s)+1);
15986     }
15987     return(*savePtr);
15988 }
15989
15990 char *
15991 PGNDate()
15992 {
15993     time_t clock;
15994     struct tm *tm;
15995     char buf[MSG_SIZ];
15996
15997     clock = time((time_t *)NULL);
15998     tm = localtime(&clock);
15999     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16000             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16001     return StrSave(buf);
16002 }
16003
16004
16005 char *
16006 PositionToFEN(move, overrideCastling)
16007      int move;
16008      char *overrideCastling;
16009 {
16010     int i, j, fromX, fromY, toX, toY;
16011     int whiteToPlay;
16012     char buf[MSG_SIZ];
16013     char *p, *q;
16014     int emptycount;
16015     ChessSquare piece;
16016
16017     whiteToPlay = (gameMode == EditPosition) ?
16018       !blackPlaysFirst : (move % 2 == 0);
16019     p = buf;
16020
16021     /* Piece placement data */
16022     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16023         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16024         emptycount = 0;
16025         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16026             if (boards[move][i][j] == EmptySquare) {
16027                 emptycount++;
16028             } else { ChessSquare piece = boards[move][i][j];
16029                 if (emptycount > 0) {
16030                     if(emptycount<10) /* [HGM] can be >= 10 */
16031                         *p++ = '0' + emptycount;
16032                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16033                     emptycount = 0;
16034                 }
16035                 if(PieceToChar(piece) == '+') {
16036                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16037                     *p++ = '+';
16038                     piece = (ChessSquare)(DEMOTED piece);
16039                 }
16040                 *p++ = PieceToChar(piece);
16041                 if(p[-1] == '~') {
16042                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16043                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16044                     *p++ = '~';
16045                 }
16046             }
16047         }
16048         if (emptycount > 0) {
16049             if(emptycount<10) /* [HGM] can be >= 10 */
16050                 *p++ = '0' + emptycount;
16051             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16052             emptycount = 0;
16053         }
16054         *p++ = '/';
16055     }
16056     *(p - 1) = ' ';
16057
16058     /* [HGM] print Crazyhouse or Shogi holdings */
16059     if( gameInfo.holdingsWidth ) {
16060         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16061         q = p;
16062         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16063             piece = boards[move][i][BOARD_WIDTH-1];
16064             if( piece != EmptySquare )
16065               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16066                   *p++ = PieceToChar(piece);
16067         }
16068         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16069             piece = boards[move][BOARD_HEIGHT-i-1][0];
16070             if( piece != EmptySquare )
16071               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16072                   *p++ = PieceToChar(piece);
16073         }
16074
16075         if( q == p ) *p++ = '-';
16076         *p++ = ']';
16077         *p++ = ' ';
16078     }
16079
16080     /* Active color */
16081     *p++ = whiteToPlay ? 'w' : 'b';
16082     *p++ = ' ';
16083
16084   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16085     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16086   } else {
16087   if(nrCastlingRights) {
16088      q = p;
16089      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16090        /* [HGM] write directly from rights */
16091            if(boards[move][CASTLING][2] != NoRights &&
16092               boards[move][CASTLING][0] != NoRights   )
16093                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16094            if(boards[move][CASTLING][2] != NoRights &&
16095               boards[move][CASTLING][1] != NoRights   )
16096                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16097            if(boards[move][CASTLING][5] != NoRights &&
16098               boards[move][CASTLING][3] != NoRights   )
16099                 *p++ = boards[move][CASTLING][3] + AAA;
16100            if(boards[move][CASTLING][5] != NoRights &&
16101               boards[move][CASTLING][4] != NoRights   )
16102                 *p++ = boards[move][CASTLING][4] + AAA;
16103      } else {
16104
16105         /* [HGM] write true castling rights */
16106         if( nrCastlingRights == 6 ) {
16107             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16108                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16109             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16110                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16111             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16112                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16113             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16114                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16115         }
16116      }
16117      if (q == p) *p++ = '-'; /* No castling rights */
16118      *p++ = ' ';
16119   }
16120
16121   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16122      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16123     /* En passant target square */
16124     if (move > backwardMostMove) {
16125         fromX = moveList[move - 1][0] - AAA;
16126         fromY = moveList[move - 1][1] - ONE;
16127         toX = moveList[move - 1][2] - AAA;
16128         toY = moveList[move - 1][3] - ONE;
16129         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16130             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16131             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16132             fromX == toX) {
16133             /* 2-square pawn move just happened */
16134             *p++ = toX + AAA;
16135             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16136         } else {
16137             *p++ = '-';
16138         }
16139     } else if(move == backwardMostMove) {
16140         // [HGM] perhaps we should always do it like this, and forget the above?
16141         if((signed char)boards[move][EP_STATUS] >= 0) {
16142             *p++ = boards[move][EP_STATUS] + AAA;
16143             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16144         } else {
16145             *p++ = '-';
16146         }
16147     } else {
16148         *p++ = '-';
16149     }
16150     *p++ = ' ';
16151   }
16152   }
16153
16154     /* [HGM] find reversible plies */
16155     {   int i = 0, j=move;
16156
16157         if (appData.debugMode) { int k;
16158             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16159             for(k=backwardMostMove; k<=forwardMostMove; k++)
16160                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16161
16162         }
16163
16164         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16165         if( j == backwardMostMove ) i += initialRulePlies;
16166         sprintf(p, "%d ", i);
16167         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16168     }
16169     /* Fullmove number */
16170     sprintf(p, "%d", (move / 2) + 1);
16171
16172     return StrSave(buf);
16173 }
16174
16175 Boolean
16176 ParseFEN(board, blackPlaysFirst, fen)
16177     Board board;
16178      int *blackPlaysFirst;
16179      char *fen;
16180 {
16181     int i, j;
16182     char *p, c;
16183     int emptycount;
16184     ChessSquare piece;
16185
16186     p = fen;
16187
16188     /* [HGM] by default clear Crazyhouse holdings, if present */
16189     if(gameInfo.holdingsWidth) {
16190        for(i=0; i<BOARD_HEIGHT; i++) {
16191            board[i][0]             = EmptySquare; /* black holdings */
16192            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16193            board[i][1]             = (ChessSquare) 0; /* black counts */
16194            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16195        }
16196     }
16197
16198     /* Piece placement data */
16199     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16200         j = 0;
16201         for (;;) {
16202             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16203                 if (*p == '/') p++;
16204                 emptycount = gameInfo.boardWidth - j;
16205                 while (emptycount--)
16206                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16207                 break;
16208 #if(BOARD_FILES >= 10)
16209             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16210                 p++; emptycount=10;
16211                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16212                 while (emptycount--)
16213                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16214 #endif
16215             } else if (isdigit(*p)) {
16216                 emptycount = *p++ - '0';
16217                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16218                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16219                 while (emptycount--)
16220                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16221             } else if (*p == '+' || isalpha(*p)) {
16222                 if (j >= gameInfo.boardWidth) return FALSE;
16223                 if(*p=='+') {
16224                     piece = CharToPiece(*++p);
16225                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16226                     piece = (ChessSquare) (PROMOTED piece ); p++;
16227                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16228                 } else piece = CharToPiece(*p++);
16229
16230                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16231                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16232                     piece = (ChessSquare) (PROMOTED piece);
16233                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16234                     p++;
16235                 }
16236                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16237             } else {
16238                 return FALSE;
16239             }
16240         }
16241     }
16242     while (*p == '/' || *p == ' ') p++;
16243
16244     /* [HGM] look for Crazyhouse holdings here */
16245     while(*p==' ') p++;
16246     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16247         if(*p == '[') p++;
16248         if(*p == '-' ) p++; /* empty holdings */ else {
16249             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16250             /* if we would allow FEN reading to set board size, we would   */
16251             /* have to add holdings and shift the board read so far here   */
16252             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16253                 p++;
16254                 if((int) piece >= (int) BlackPawn ) {
16255                     i = (int)piece - (int)BlackPawn;
16256                     i = PieceToNumber((ChessSquare)i);
16257                     if( i >= gameInfo.holdingsSize ) return FALSE;
16258                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16259                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16260                 } else {
16261                     i = (int)piece - (int)WhitePawn;
16262                     i = PieceToNumber((ChessSquare)i);
16263                     if( i >= gameInfo.holdingsSize ) return FALSE;
16264                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16265                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16266                 }
16267             }
16268         }
16269         if(*p == ']') p++;
16270     }
16271
16272     while(*p == ' ') p++;
16273
16274     /* Active color */
16275     c = *p++;
16276     if(appData.colorNickNames) {
16277       if( c == appData.colorNickNames[0] ) c = 'w'; else
16278       if( c == appData.colorNickNames[1] ) c = 'b';
16279     }
16280     switch (c) {
16281       case 'w':
16282         *blackPlaysFirst = FALSE;
16283         break;
16284       case 'b':
16285         *blackPlaysFirst = TRUE;
16286         break;
16287       default:
16288         return FALSE;
16289     }
16290
16291     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16292     /* return the extra info in global variiables             */
16293
16294     /* set defaults in case FEN is incomplete */
16295     board[EP_STATUS] = EP_UNKNOWN;
16296     for(i=0; i<nrCastlingRights; i++ ) {
16297         board[CASTLING][i] =
16298             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16299     }   /* assume possible unless obviously impossible */
16300     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16301     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16302     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16303                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16304     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16305     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16306     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16307                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16308     FENrulePlies = 0;
16309
16310     while(*p==' ') p++;
16311     if(nrCastlingRights) {
16312       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16313           /* castling indicator present, so default becomes no castlings */
16314           for(i=0; i<nrCastlingRights; i++ ) {
16315                  board[CASTLING][i] = NoRights;
16316           }
16317       }
16318       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16319              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16320              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16321              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16322         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16323
16324         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16325             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16326             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16327         }
16328         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16329             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16330         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16331                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16332         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16333                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16334         switch(c) {
16335           case'K':
16336               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16337               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16338               board[CASTLING][2] = whiteKingFile;
16339               break;
16340           case'Q':
16341               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16342               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16343               board[CASTLING][2] = whiteKingFile;
16344               break;
16345           case'k':
16346               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16347               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16348               board[CASTLING][5] = blackKingFile;
16349               break;
16350           case'q':
16351               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16352               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16353               board[CASTLING][5] = blackKingFile;
16354           case '-':
16355               break;
16356           default: /* FRC castlings */
16357               if(c >= 'a') { /* black rights */
16358                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16359                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16360                   if(i == BOARD_RGHT) break;
16361                   board[CASTLING][5] = i;
16362                   c -= AAA;
16363                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16364                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16365                   if(c > i)
16366                       board[CASTLING][3] = c;
16367                   else
16368                       board[CASTLING][4] = c;
16369               } else { /* white rights */
16370                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16371                     if(board[0][i] == WhiteKing) break;
16372                   if(i == BOARD_RGHT) break;
16373                   board[CASTLING][2] = i;
16374                   c -= AAA - 'a' + 'A';
16375                   if(board[0][c] >= WhiteKing) break;
16376                   if(c > i)
16377                       board[CASTLING][0] = c;
16378                   else
16379                       board[CASTLING][1] = c;
16380               }
16381         }
16382       }
16383       for(i=0; i<nrCastlingRights; i++)
16384         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16385     if (appData.debugMode) {
16386         fprintf(debugFP, "FEN castling rights:");
16387         for(i=0; i<nrCastlingRights; i++)
16388         fprintf(debugFP, " %d", board[CASTLING][i]);
16389         fprintf(debugFP, "\n");
16390     }
16391
16392       while(*p==' ') p++;
16393     }
16394
16395     /* read e.p. field in games that know e.p. capture */
16396     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16397        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16398       if(*p=='-') {
16399         p++; board[EP_STATUS] = EP_NONE;
16400       } else {
16401          char c = *p++ - AAA;
16402
16403          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16404          if(*p >= '0' && *p <='9') p++;
16405          board[EP_STATUS] = c;
16406       }
16407     }
16408
16409
16410     if(sscanf(p, "%d", &i) == 1) {
16411         FENrulePlies = i; /* 50-move ply counter */
16412         /* (The move number is still ignored)    */
16413     }
16414
16415     return TRUE;
16416 }
16417
16418 void
16419 EditPositionPasteFEN(char *fen)
16420 {
16421   if (fen != NULL) {
16422     Board initial_position;
16423
16424     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16425       DisplayError(_("Bad FEN position in clipboard"), 0);
16426       return ;
16427     } else {
16428       int savedBlackPlaysFirst = blackPlaysFirst;
16429       EditPositionEvent();
16430       blackPlaysFirst = savedBlackPlaysFirst;
16431       CopyBoard(boards[0], initial_position);
16432       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16433       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16434       DisplayBothClocks();
16435       DrawPosition(FALSE, boards[currentMove]);
16436     }
16437   }
16438 }
16439
16440 static char cseq[12] = "\\   ";
16441
16442 Boolean set_cont_sequence(char *new_seq)
16443 {
16444     int len;
16445     Boolean ret;
16446
16447     // handle bad attempts to set the sequence
16448         if (!new_seq)
16449                 return 0; // acceptable error - no debug
16450
16451     len = strlen(new_seq);
16452     ret = (len > 0) && (len < sizeof(cseq));
16453     if (ret)
16454       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16455     else if (appData.debugMode)
16456       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16457     return ret;
16458 }
16459
16460 /*
16461     reformat a source message so words don't cross the width boundary.  internal
16462     newlines are not removed.  returns the wrapped size (no null character unless
16463     included in source message).  If dest is NULL, only calculate the size required
16464     for the dest buffer.  lp argument indicats line position upon entry, and it's
16465     passed back upon exit.
16466 */
16467 int wrap(char *dest, char *src, int count, int width, int *lp)
16468 {
16469     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16470
16471     cseq_len = strlen(cseq);
16472     old_line = line = *lp;
16473     ansi = len = clen = 0;
16474
16475     for (i=0; i < count; i++)
16476     {
16477         if (src[i] == '\033')
16478             ansi = 1;
16479
16480         // if we hit the width, back up
16481         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16482         {
16483             // store i & len in case the word is too long
16484             old_i = i, old_len = len;
16485
16486             // find the end of the last word
16487             while (i && src[i] != ' ' && src[i] != '\n')
16488             {
16489                 i--;
16490                 len--;
16491             }
16492
16493             // word too long?  restore i & len before splitting it
16494             if ((old_i-i+clen) >= width)
16495             {
16496                 i = old_i;
16497                 len = old_len;
16498             }
16499
16500             // extra space?
16501             if (i && src[i-1] == ' ')
16502                 len--;
16503
16504             if (src[i] != ' ' && src[i] != '\n')
16505             {
16506                 i--;
16507                 if (len)
16508                     len--;
16509             }
16510
16511             // now append the newline and continuation sequence
16512             if (dest)
16513                 dest[len] = '\n';
16514             len++;
16515             if (dest)
16516                 strncpy(dest+len, cseq, cseq_len);
16517             len += cseq_len;
16518             line = cseq_len;
16519             clen = cseq_len;
16520             continue;
16521         }
16522
16523         if (dest)
16524             dest[len] = src[i];
16525         len++;
16526         if (!ansi)
16527             line++;
16528         if (src[i] == '\n')
16529             line = 0;
16530         if (src[i] == 'm')
16531             ansi = 0;
16532     }
16533     if (dest && appData.debugMode)
16534     {
16535         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16536             count, width, line, len, *lp);
16537         show_bytes(debugFP, src, count);
16538         fprintf(debugFP, "\ndest: ");
16539         show_bytes(debugFP, dest, len);
16540         fprintf(debugFP, "\n");
16541     }
16542     *lp = dest ? line : old_line;
16543
16544     return len;
16545 }
16546
16547 // [HGM] vari: routines for shelving variations
16548 Boolean modeRestore = FALSE;
16549
16550 void
16551 PushInner(int firstMove, int lastMove)
16552 {
16553         int i, j, nrMoves = lastMove - firstMove;
16554
16555         // push current tail of game on stack
16556         savedResult[storedGames] = gameInfo.result;
16557         savedDetails[storedGames] = gameInfo.resultDetails;
16558         gameInfo.resultDetails = NULL;
16559         savedFirst[storedGames] = firstMove;
16560         savedLast [storedGames] = lastMove;
16561         savedFramePtr[storedGames] = framePtr;
16562         framePtr -= nrMoves; // reserve space for the boards
16563         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16564             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16565             for(j=0; j<MOVE_LEN; j++)
16566                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16567             for(j=0; j<2*MOVE_LEN; j++)
16568                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16569             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16570             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16571             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16572             pvInfoList[firstMove+i-1].depth = 0;
16573             commentList[framePtr+i] = commentList[firstMove+i];
16574             commentList[firstMove+i] = NULL;
16575         }
16576
16577         storedGames++;
16578         forwardMostMove = firstMove; // truncate game so we can start variation
16579 }
16580
16581 void
16582 PushTail(int firstMove, int lastMove)
16583 {
16584         if(appData.icsActive) { // only in local mode
16585                 forwardMostMove = currentMove; // mimic old ICS behavior
16586                 return;
16587         }
16588         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16589
16590         PushInner(firstMove, lastMove);
16591         if(storedGames == 1) GreyRevert(FALSE);
16592         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16593 }
16594
16595 void
16596 PopInner(Boolean annotate)
16597 {
16598         int i, j, nrMoves;
16599         char buf[8000], moveBuf[20];
16600
16601         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16602         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16603         nrMoves = savedLast[storedGames] - currentMove;
16604         if(annotate) {
16605                 int cnt = 10;
16606                 if(!WhiteOnMove(currentMove))
16607                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16608                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16609                 for(i=currentMove; i<forwardMostMove; i++) {
16610                         if(WhiteOnMove(i))
16611                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16612                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16613                         strcat(buf, moveBuf);
16614                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16615                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16616                 }
16617                 strcat(buf, ")");
16618         }
16619         for(i=1; i<=nrMoves; i++) { // copy last variation back
16620             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16621             for(j=0; j<MOVE_LEN; j++)
16622                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16623             for(j=0; j<2*MOVE_LEN; j++)
16624                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16625             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16626             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16627             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16628             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16629             commentList[currentMove+i] = commentList[framePtr+i];
16630             commentList[framePtr+i] = NULL;
16631         }
16632         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16633         framePtr = savedFramePtr[storedGames];
16634         gameInfo.result = savedResult[storedGames];
16635         if(gameInfo.resultDetails != NULL) {
16636             free(gameInfo.resultDetails);
16637       }
16638         gameInfo.resultDetails = savedDetails[storedGames];
16639         forwardMostMove = currentMove + nrMoves;
16640 }
16641
16642 Boolean
16643 PopTail(Boolean annotate)
16644 {
16645         if(appData.icsActive) return FALSE; // only in local mode
16646         if(!storedGames) return FALSE; // sanity
16647         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16648
16649         PopInner(annotate);
16650         if(currentMove < forwardMostMove) ForwardEvent(); else
16651         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16652
16653         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16654         return TRUE;
16655 }
16656
16657 void
16658 CleanupTail()
16659 {       // remove all shelved variations
16660         int i;
16661         for(i=0; i<storedGames; i++) {
16662             if(savedDetails[i])
16663                 free(savedDetails[i]);
16664             savedDetails[i] = NULL;
16665         }
16666         for(i=framePtr; i<MAX_MOVES; i++) {
16667                 if(commentList[i]) free(commentList[i]);
16668                 commentList[i] = NULL;
16669         }
16670         framePtr = MAX_MOVES-1;
16671         storedGames = 0;
16672 }
16673
16674 void
16675 LoadVariation(int index, char *text)
16676 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16677         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16678         int level = 0, move;
16679
16680         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16681         // first find outermost bracketing variation
16682         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16683             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16684                 if(*p == '{') wait = '}'; else
16685                 if(*p == '[') wait = ']'; else
16686                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16687                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16688             }
16689             if(*p == wait) wait = NULLCHAR; // closing ]} found
16690             p++;
16691         }
16692         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16693         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16694         end[1] = NULLCHAR; // clip off comment beyond variation
16695         ToNrEvent(currentMove-1);
16696         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16697         // kludge: use ParsePV() to append variation to game
16698         move = currentMove;
16699         ParsePV(start, TRUE, TRUE);
16700         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16701         ClearPremoveHighlights();
16702         CommentPopDown();
16703         ToNrEvent(currentMove+1);
16704 }
16705