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