f0289bc1b6614871dd44f0a08d316c43a147cb86
[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 (cps->useUsermove) {
4865       SendToProgram("usermove ", cps);
4866     }
4867     if (cps->useSAN) {
4868       char *space;
4869       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4870         int len = space - parseList[moveNum];
4871         memcpy(buf, parseList[moveNum], len);
4872         buf[len++] = '\n';
4873         buf[len] = NULLCHAR;
4874       } else {
4875         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4876       }
4877       SendToProgram(buf, cps);
4878     } else {
4879       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4880         AlphaRank(moveList[moveNum], 4);
4881         SendToProgram(moveList[moveNum], cps);
4882         AlphaRank(moveList[moveNum], 4); // and back
4883       } else
4884       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4885        * the engine. It would be nice to have a better way to identify castle
4886        * moves here. */
4887       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4888                                                                          && cps->useOOCastle) {
4889         int fromX = moveList[moveNum][0] - AAA;
4890         int fromY = moveList[moveNum][1] - ONE;
4891         int toX = moveList[moveNum][2] - AAA;
4892         int toY = moveList[moveNum][3] - ONE;
4893         if((boards[moveNum][fromY][fromX] == WhiteKing
4894             && boards[moveNum][toY][toX] == WhiteRook)
4895            || (boards[moveNum][fromY][fromX] == BlackKing
4896                && boards[moveNum][toY][toX] == BlackRook)) {
4897           if(toX > fromX) SendToProgram("O-O\n", cps);
4898           else SendToProgram("O-O-O\n", cps);
4899         }
4900         else SendToProgram(moveList[moveNum], cps);
4901       } else
4902       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4903         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4904           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4905                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4906         } else
4907           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4908                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4909         SendToProgram(buf, cps);
4910       }
4911       else SendToProgram(moveList[moveNum], cps);
4912       /* End of additions by Tord */
4913     }
4914
4915     /* [HGM] setting up the opening has brought engine in force mode! */
4916     /*       Send 'go' if we are in a mode where machine should play. */
4917     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4918         (gameMode == TwoMachinesPlay   ||
4919 #if ZIPPY
4920          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4921 #endif
4922          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4923         SendToProgram("go\n", cps);
4924   if (appData.debugMode) {
4925     fprintf(debugFP, "(extra)\n");
4926   }
4927     }
4928     setboardSpoiledMachineBlack = 0;
4929 }
4930
4931 void
4932 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4933      ChessMove moveType;
4934      int fromX, fromY, toX, toY;
4935      char promoChar;
4936 {
4937     char user_move[MSG_SIZ];
4938
4939     switch (moveType) {
4940       default:
4941         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4942                 (int)moveType, fromX, fromY, toX, toY);
4943         DisplayError(user_move + strlen("say "), 0);
4944         break;
4945       case WhiteKingSideCastle:
4946       case BlackKingSideCastle:
4947       case WhiteQueenSideCastleWild:
4948       case BlackQueenSideCastleWild:
4949       /* PUSH Fabien */
4950       case WhiteHSideCastleFR:
4951       case BlackHSideCastleFR:
4952       /* POP Fabien */
4953         snprintf(user_move, MSG_SIZ, "o-o\n");
4954         break;
4955       case WhiteQueenSideCastle:
4956       case BlackQueenSideCastle:
4957       case WhiteKingSideCastleWild:
4958       case BlackKingSideCastleWild:
4959       /* PUSH Fabien */
4960       case WhiteASideCastleFR:
4961       case BlackASideCastleFR:
4962       /* POP Fabien */
4963         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4964         break;
4965       case WhiteNonPromotion:
4966       case BlackNonPromotion:
4967         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4968         break;
4969       case WhitePromotion:
4970       case BlackPromotion:
4971         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4972           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4973                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4974                 PieceToChar(WhiteFerz));
4975         else if(gameInfo.variant == VariantGreat)
4976           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4977                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4978                 PieceToChar(WhiteMan));
4979         else
4980           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4981                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4982                 promoChar);
4983         break;
4984       case WhiteDrop:
4985       case BlackDrop:
4986       drop:
4987         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4988                  ToUpper(PieceToChar((ChessSquare) fromX)),
4989                  AAA + toX, ONE + toY);
4990         break;
4991       case IllegalMove:  /* could be a variant we don't quite understand */
4992         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4993       case NormalMove:
4994       case WhiteCapturesEnPassant:
4995       case BlackCapturesEnPassant:
4996         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4997                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4998         break;
4999     }
5000     SendToICS(user_move);
5001     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5002         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5003 }
5004
5005 void
5006 UploadGameEvent()
5007 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5008     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5009     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5010     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5011         DisplayError("You cannot do this while you are playing or observing", 0);
5012         return;
5013     }
5014     if(gameMode != IcsExamining) { // is this ever not the case?
5015         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5016
5017         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5018           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5019         } else { // on FICS we must first go to general examine mode
5020           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5021         }
5022         if(gameInfo.variant != VariantNormal) {
5023             // try figure out wild number, as xboard names are not always valid on ICS
5024             for(i=1; i<=36; i++) {
5025               snprintf(buf, MSG_SIZ, "wild/%d", i);
5026                 if(StringToVariant(buf) == gameInfo.variant) break;
5027             }
5028             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5029             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5030             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5031         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5032         SendToICS(ics_prefix);
5033         SendToICS(buf);
5034         if(startedFromSetupPosition || backwardMostMove != 0) {
5035           fen = PositionToFEN(backwardMostMove, NULL);
5036           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5037             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5038             SendToICS(buf);
5039           } else { // FICS: everything has to set by separate bsetup commands
5040             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5041             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5042             SendToICS(buf);
5043             if(!WhiteOnMove(backwardMostMove)) {
5044                 SendToICS("bsetup tomove black\n");
5045             }
5046             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5047             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5048             SendToICS(buf);
5049             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5050             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5051             SendToICS(buf);
5052             i = boards[backwardMostMove][EP_STATUS];
5053             if(i >= 0) { // set e.p.
5054               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5055                 SendToICS(buf);
5056             }
5057             bsetup++;
5058           }
5059         }
5060       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5061             SendToICS("bsetup done\n"); // switch to normal examining.
5062     }
5063     for(i = backwardMostMove; i<last; i++) {
5064         char buf[20];
5065         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5066         SendToICS(buf);
5067     }
5068     SendToICS(ics_prefix);
5069     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5070 }
5071
5072 void
5073 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5074      int rf, ff, rt, ft;
5075      char promoChar;
5076      char move[7];
5077 {
5078     if (rf == DROP_RANK) {
5079       sprintf(move, "%c@%c%c\n",
5080                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5081     } else {
5082         if (promoChar == 'x' || promoChar == NULLCHAR) {
5083           sprintf(move, "%c%c%c%c\n",
5084                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5085         } else {
5086             sprintf(move, "%c%c%c%c%c\n",
5087                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5088         }
5089     }
5090 }
5091
5092 void
5093 ProcessICSInitScript(f)
5094      FILE *f;
5095 {
5096     char buf[MSG_SIZ];
5097
5098     while (fgets(buf, MSG_SIZ, f)) {
5099         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5100     }
5101
5102     fclose(f);
5103 }
5104
5105
5106 static int lastX, lastY, selectFlag, dragging;
5107
5108 void
5109 Sweep(int step)
5110 {
5111     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5112     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5113     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5114     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5115     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5116     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5117     do {
5118         promoSweep -= step;
5119         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5120         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5121         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5122         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5123         if(!step) step = 1;
5124     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5125             appData.testLegality && (promoSweep == king ||
5126             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5127     ChangeDragPiece(promoSweep);
5128 }
5129
5130 int PromoScroll(int x, int y)
5131 {
5132   int step = 0;
5133
5134   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5135   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5136   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5137   if(!step) return FALSE;
5138   lastX = x; lastY = y;
5139   if((promoSweep < BlackPawn) == flipView) step = -step;
5140   if(step > 0) selectFlag = 1;
5141   if(!selectFlag) Sweep(step);
5142   return FALSE;
5143 }
5144
5145 void
5146 NextPiece(int step)
5147 {
5148     ChessSquare piece = boards[currentMove][toY][toX];
5149     do {
5150         pieceSweep -= step;
5151         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5152         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5153         if(!step) step = -1;
5154     } while(PieceToChar(pieceSweep) == '.');
5155     boards[currentMove][toY][toX] = pieceSweep;
5156     DrawPosition(FALSE, boards[currentMove]);
5157     boards[currentMove][toY][toX] = piece;
5158 }
5159 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5160 void
5161 AlphaRank(char *move, int n)
5162 {
5163 //    char *p = move, c; int x, y;
5164
5165     if (appData.debugMode) {
5166         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5167     }
5168
5169     if(move[1]=='*' &&
5170        move[2]>='0' && move[2]<='9' &&
5171        move[3]>='a' && move[3]<='x'    ) {
5172         move[1] = '@';
5173         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5174         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5175     } else
5176     if(move[0]>='0' && move[0]<='9' &&
5177        move[1]>='a' && move[1]<='x' &&
5178        move[2]>='0' && move[2]<='9' &&
5179        move[3]>='a' && move[3]<='x'    ) {
5180         /* input move, Shogi -> normal */
5181         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5182         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5183         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5184         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5185     } else
5186     if(move[1]=='@' &&
5187        move[3]>='0' && move[3]<='9' &&
5188        move[2]>='a' && move[2]<='x'    ) {
5189         move[1] = '*';
5190         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5191         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5192     } else
5193     if(
5194        move[0]>='a' && move[0]<='x' &&
5195        move[3]>='0' && move[3]<='9' &&
5196        move[2]>='a' && move[2]<='x'    ) {
5197          /* output move, normal -> Shogi */
5198         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5199         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5200         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5201         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5202         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5203     }
5204     if (appData.debugMode) {
5205         fprintf(debugFP, "   out = '%s'\n", move);
5206     }
5207 }
5208
5209 char yy_textstr[8000];
5210
5211 /* Parser for moves from gnuchess, ICS, or user typein box */
5212 Boolean
5213 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5214      char *move;
5215      int moveNum;
5216      ChessMove *moveType;
5217      int *fromX, *fromY, *toX, *toY;
5218      char *promoChar;
5219 {
5220     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5221
5222     switch (*moveType) {
5223       case WhitePromotion:
5224       case BlackPromotion:
5225       case WhiteNonPromotion:
5226       case BlackNonPromotion:
5227       case NormalMove:
5228       case WhiteCapturesEnPassant:
5229       case BlackCapturesEnPassant:
5230       case WhiteKingSideCastle:
5231       case WhiteQueenSideCastle:
5232       case BlackKingSideCastle:
5233       case BlackQueenSideCastle:
5234       case WhiteKingSideCastleWild:
5235       case WhiteQueenSideCastleWild:
5236       case BlackKingSideCastleWild:
5237       case BlackQueenSideCastleWild:
5238       /* Code added by Tord: */
5239       case WhiteHSideCastleFR:
5240       case WhiteASideCastleFR:
5241       case BlackHSideCastleFR:
5242       case BlackASideCastleFR:
5243       /* End of code added by Tord */
5244       case IllegalMove:         /* bug or odd chess variant */
5245         *fromX = currentMoveString[0] - AAA;
5246         *fromY = currentMoveString[1] - ONE;
5247         *toX = currentMoveString[2] - AAA;
5248         *toY = currentMoveString[3] - ONE;
5249         *promoChar = currentMoveString[4];
5250         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5251             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5252     if (appData.debugMode) {
5253         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5254     }
5255             *fromX = *fromY = *toX = *toY = 0;
5256             return FALSE;
5257         }
5258         if (appData.testLegality) {
5259           return (*moveType != IllegalMove);
5260         } else {
5261           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5262                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5263         }
5264
5265       case WhiteDrop:
5266       case BlackDrop:
5267         *fromX = *moveType == WhiteDrop ?
5268           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5269           (int) CharToPiece(ToLower(currentMoveString[0]));
5270         *fromY = DROP_RANK;
5271         *toX = currentMoveString[2] - AAA;
5272         *toY = currentMoveString[3] - ONE;
5273         *promoChar = NULLCHAR;
5274         return TRUE;
5275
5276       case AmbiguousMove:
5277       case ImpossibleMove:
5278       case EndOfFile:
5279       case ElapsedTime:
5280       case Comment:
5281       case PGNTag:
5282       case NAG:
5283       case WhiteWins:
5284       case BlackWins:
5285       case GameIsDrawn:
5286       default:
5287     if (appData.debugMode) {
5288         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5289     }
5290         /* bug? */
5291         *fromX = *fromY = *toX = *toY = 0;
5292         *promoChar = NULLCHAR;
5293         return FALSE;
5294     }
5295 }
5296
5297 Boolean pushed = FALSE;
5298 char *lastParseAttempt;
5299
5300 void
5301 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5302 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5303   int fromX, fromY, toX, toY; char promoChar;
5304   ChessMove moveType;
5305   Boolean valid;
5306   int nr = 0;
5307
5308   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5309     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5310     pushed = TRUE;
5311   }
5312   endPV = forwardMostMove;
5313   do {
5314     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5315     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5316     lastParseAttempt = pv;
5317     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5318 if(appData.debugMode){
5319 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);
5320 }
5321     if(!valid && nr == 0 &&
5322        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5323         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5324         // Hande case where played move is different from leading PV move
5325         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5326         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5327         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5328         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5329           endPV += 2; // if position different, keep this
5330           moveList[endPV-1][0] = fromX + AAA;
5331           moveList[endPV-1][1] = fromY + ONE;
5332           moveList[endPV-1][2] = toX + AAA;
5333           moveList[endPV-1][3] = toY + ONE;
5334           parseList[endPV-1][0] = NULLCHAR;
5335           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5336         }
5337       }
5338     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5339     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5340     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5341     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5342         valid++; // allow comments in PV
5343         continue;
5344     }
5345     nr++;
5346     if(endPV+1 > framePtr) break; // no space, truncate
5347     if(!valid) break;
5348     endPV++;
5349     CopyBoard(boards[endPV], boards[endPV-1]);
5350     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5351     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5352     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5353     CoordsToAlgebraic(boards[endPV - 1],
5354                              PosFlags(endPV - 1),
5355                              fromY, fromX, toY, toX, promoChar,
5356                              parseList[endPV - 1]);
5357   } while(valid);
5358   if(atEnd == 2) return; // used hidden, for PV conversion
5359   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5360   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5361   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5362                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5363   DrawPosition(TRUE, boards[currentMove]);
5364 }
5365
5366 int
5367 MultiPV(ChessProgramState *cps)
5368 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5369         int i;
5370         for(i=0; i<cps->nrOptions; i++)
5371             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5372                 return i;
5373         return -1;
5374 }
5375
5376 Boolean
5377 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5378 {
5379         int startPV, multi, lineStart, origIndex = index;
5380         char *p, buf2[MSG_SIZ];
5381
5382         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5383         lastX = x; lastY = y;
5384         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5385         lineStart = startPV = index;
5386         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5387         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5388         index = startPV;
5389         do{ while(buf[index] && buf[index] != '\n') index++;
5390         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5391         buf[index] = 0;
5392         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5393                 int n = first.option[multi].value;
5394                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5395                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5396                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5397                 first.option[multi].value = n;
5398                 *start = *end = 0;
5399                 return FALSE;
5400         }
5401         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5402         *start = startPV; *end = index-1;
5403         return TRUE;
5404 }
5405
5406 char *
5407 PvToSAN(char *pv)
5408 {
5409         static char buf[10*MSG_SIZ];
5410         int i, k=0, savedEnd=endPV;
5411         *buf = NULLCHAR;
5412         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5413         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5414         for(i = forwardMostMove; i<endPV; i++){
5415             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5416             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5417             k += strlen(buf+k);
5418         }
5419         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5420         if(forwardMostMove < savedEnd) PopInner(0);
5421         endPV = savedEnd;
5422         return buf;
5423 }
5424
5425 Boolean
5426 LoadPV(int x, int y)
5427 { // called on right mouse click to load PV
5428   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5429   lastX = x; lastY = y;
5430   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5431   return TRUE;
5432 }
5433
5434 void
5435 UnLoadPV()
5436 {
5437   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5438   if(endPV < 0) return;
5439   endPV = -1;
5440   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5441         Boolean saveAnimate = appData.animate;
5442         if(pushed) {
5443             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5444                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5445             } else storedGames--; // abandon shelved tail of original game
5446         }
5447         pushed = FALSE;
5448         forwardMostMove = currentMove;
5449         currentMove = oldFMM;
5450         appData.animate = FALSE;
5451         ToNrEvent(forwardMostMove);
5452         appData.animate = saveAnimate;
5453   }
5454   currentMove = forwardMostMove;
5455   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5456   ClearPremoveHighlights();
5457   DrawPosition(TRUE, boards[currentMove]);
5458 }
5459
5460 void
5461 MovePV(int x, int y, int h)
5462 { // step through PV based on mouse coordinates (called on mouse move)
5463   int margin = h>>3, step = 0;
5464
5465   // we must somehow check if right button is still down (might be released off board!)
5466   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5467   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5468   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5469   if(!step) return;
5470   lastX = x; lastY = y;
5471
5472   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5473   if(endPV < 0) return;
5474   if(y < margin) step = 1; else
5475   if(y > h - margin) step = -1;
5476   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5477   currentMove += step;
5478   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5479   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5480                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5481   DrawPosition(FALSE, boards[currentMove]);
5482 }
5483
5484
5485 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5486 // All positions will have equal probability, but the current method will not provide a unique
5487 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5488 #define DARK 1
5489 #define LITE 2
5490 #define ANY 3
5491
5492 int squaresLeft[4];
5493 int piecesLeft[(int)BlackPawn];
5494 int seed, nrOfShuffles;
5495
5496 void GetPositionNumber()
5497 {       // sets global variable seed
5498         int i;
5499
5500         seed = appData.defaultFrcPosition;
5501         if(seed < 0) { // randomize based on time for negative FRC position numbers
5502                 for(i=0; i<50; i++) seed += random();
5503                 seed = random() ^ random() >> 8 ^ random() << 8;
5504                 if(seed<0) seed = -seed;
5505         }
5506 }
5507
5508 int put(Board board, int pieceType, int rank, int n, int shade)
5509 // put the piece on the (n-1)-th empty squares of the given shade
5510 {
5511         int i;
5512
5513         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5514                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5515                         board[rank][i] = (ChessSquare) pieceType;
5516                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5517                         squaresLeft[ANY]--;
5518                         piecesLeft[pieceType]--;
5519                         return i;
5520                 }
5521         }
5522         return -1;
5523 }
5524
5525
5526 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5527 // calculate where the next piece goes, (any empty square), and put it there
5528 {
5529         int i;
5530
5531         i = seed % squaresLeft[shade];
5532         nrOfShuffles *= squaresLeft[shade];
5533         seed /= squaresLeft[shade];
5534         put(board, pieceType, rank, i, shade);
5535 }
5536
5537 void AddTwoPieces(Board board, int pieceType, int rank)
5538 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5539 {
5540         int i, n=squaresLeft[ANY], j=n-1, k;
5541
5542         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5543         i = seed % k;  // pick one
5544         nrOfShuffles *= k;
5545         seed /= k;
5546         while(i >= j) i -= j--;
5547         j = n - 1 - j; i += j;
5548         put(board, pieceType, rank, j, ANY);
5549         put(board, pieceType, rank, i, ANY);
5550 }
5551
5552 void SetUpShuffle(Board board, int number)
5553 {
5554         int i, p, first=1;
5555
5556         GetPositionNumber(); nrOfShuffles = 1;
5557
5558         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5559         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5560         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5561
5562         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5563
5564         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5565             p = (int) board[0][i];
5566             if(p < (int) BlackPawn) piecesLeft[p] ++;
5567             board[0][i] = EmptySquare;
5568         }
5569
5570         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5571             // shuffles restricted to allow normal castling put KRR first
5572             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5573                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5574             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5575                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5576             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5577                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5578             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5579                 put(board, WhiteRook, 0, 0, ANY);
5580             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5581         }
5582
5583         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5584             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5585             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5586                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5587                 while(piecesLeft[p] >= 2) {
5588                     AddOnePiece(board, p, 0, LITE);
5589                     AddOnePiece(board, p, 0, DARK);
5590                 }
5591                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5592             }
5593
5594         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5595             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5596             // but we leave King and Rooks for last, to possibly obey FRC restriction
5597             if(p == (int)WhiteRook) continue;
5598             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5599             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5600         }
5601
5602         // now everything is placed, except perhaps King (Unicorn) and Rooks
5603
5604         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5605             // Last King gets castling rights
5606             while(piecesLeft[(int)WhiteUnicorn]) {
5607                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5608                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5609             }
5610
5611             while(piecesLeft[(int)WhiteKing]) {
5612                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5613                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5614             }
5615
5616
5617         } else {
5618             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5619             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5620         }
5621
5622         // Only Rooks can be left; simply place them all
5623         while(piecesLeft[(int)WhiteRook]) {
5624                 i = put(board, WhiteRook, 0, 0, ANY);
5625                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5626                         if(first) {
5627                                 first=0;
5628                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5629                         }
5630                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5631                 }
5632         }
5633         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5634             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5635         }
5636
5637         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5638 }
5639
5640 int SetCharTable( char *table, const char * map )
5641 /* [HGM] moved here from winboard.c because of its general usefulness */
5642 /*       Basically a safe strcpy that uses the last character as King */
5643 {
5644     int result = FALSE; int NrPieces;
5645
5646     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5647                     && NrPieces >= 12 && !(NrPieces&1)) {
5648         int i; /* [HGM] Accept even length from 12 to 34 */
5649
5650         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5651         for( i=0; i<NrPieces/2-1; i++ ) {
5652             table[i] = map[i];
5653             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5654         }
5655         table[(int) WhiteKing]  = map[NrPieces/2-1];
5656         table[(int) BlackKing]  = map[NrPieces-1];
5657
5658         result = TRUE;
5659     }
5660
5661     return result;
5662 }
5663
5664 void Prelude(Board board)
5665 {       // [HGM] superchess: random selection of exo-pieces
5666         int i, j, k; ChessSquare p;
5667         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5668
5669         GetPositionNumber(); // use FRC position number
5670
5671         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5672             SetCharTable(pieceToChar, appData.pieceToCharTable);
5673             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5674                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5675         }
5676
5677         j = seed%4;                 seed /= 4;
5678         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5679         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5680         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5681         j = seed%3 + (seed%3 >= j); seed /= 3;
5682         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5683         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5684         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5685         j = seed%3;                 seed /= 3;
5686         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5687         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5688         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5689         j = seed%2 + (seed%2 >= j); seed /= 2;
5690         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5691         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5692         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5693         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5694         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5695         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5696         put(board, exoPieces[0],    0, 0, ANY);
5697         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5698 }
5699
5700 void
5701 InitPosition(redraw)
5702      int redraw;
5703 {
5704     ChessSquare (* pieces)[BOARD_FILES];
5705     int i, j, pawnRow, overrule,
5706     oldx = gameInfo.boardWidth,
5707     oldy = gameInfo.boardHeight,
5708     oldh = gameInfo.holdingsWidth;
5709     static int oldv;
5710
5711     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5712
5713     /* [AS] Initialize pv info list [HGM] and game status */
5714     {
5715         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5716             pvInfoList[i].depth = 0;
5717             boards[i][EP_STATUS] = EP_NONE;
5718             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5719         }
5720
5721         initialRulePlies = 0; /* 50-move counter start */
5722
5723         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5724         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5725     }
5726
5727
5728     /* [HGM] logic here is completely changed. In stead of full positions */
5729     /* the initialized data only consist of the two backranks. The switch */
5730     /* selects which one we will use, which is than copied to the Board   */
5731     /* initialPosition, which for the rest is initialized by Pawns and    */
5732     /* empty squares. This initial position is then copied to boards[0],  */
5733     /* possibly after shuffling, so that it remains available.            */
5734
5735     gameInfo.holdingsWidth = 0; /* default board sizes */
5736     gameInfo.boardWidth    = 8;
5737     gameInfo.boardHeight   = 8;
5738     gameInfo.holdingsSize  = 0;
5739     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5740     for(i=0; i<BOARD_FILES-2; i++)
5741       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5742     initialPosition[EP_STATUS] = EP_NONE;
5743     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5744     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5745          SetCharTable(pieceNickName, appData.pieceNickNames);
5746     else SetCharTable(pieceNickName, "............");
5747     pieces = FIDEArray;
5748
5749     switch (gameInfo.variant) {
5750     case VariantFischeRandom:
5751       shuffleOpenings = TRUE;
5752     default:
5753       break;
5754     case VariantShatranj:
5755       pieces = ShatranjArray;
5756       nrCastlingRights = 0;
5757       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5758       break;
5759     case VariantMakruk:
5760       pieces = makrukArray;
5761       nrCastlingRights = 0;
5762       startedFromSetupPosition = TRUE;
5763       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5764       break;
5765     case VariantTwoKings:
5766       pieces = twoKingsArray;
5767       break;
5768     case VariantGrand:
5769       pieces = GrandArray;
5770       nrCastlingRights = 0;
5771       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5772       gameInfo.boardWidth = 10;
5773       gameInfo.boardHeight = 10;
5774       gameInfo.holdingsSize = 7;
5775       break;
5776     case VariantCapaRandom:
5777       shuffleOpenings = TRUE;
5778     case VariantCapablanca:
5779       pieces = CapablancaArray;
5780       gameInfo.boardWidth = 10;
5781       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5782       break;
5783     case VariantGothic:
5784       pieces = GothicArray;
5785       gameInfo.boardWidth = 10;
5786       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5787       break;
5788     case VariantSChess:
5789       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5790       gameInfo.holdingsSize = 7;
5791       break;
5792     case VariantJanus:
5793       pieces = JanusArray;
5794       gameInfo.boardWidth = 10;
5795       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5796       nrCastlingRights = 6;
5797         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5798         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5799         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5800         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5801         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5802         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5803       break;
5804     case VariantFalcon:
5805       pieces = FalconArray;
5806       gameInfo.boardWidth = 10;
5807       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5808       break;
5809     case VariantXiangqi:
5810       pieces = XiangqiArray;
5811       gameInfo.boardWidth  = 9;
5812       gameInfo.boardHeight = 10;
5813       nrCastlingRights = 0;
5814       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5815       break;
5816     case VariantShogi:
5817       pieces = ShogiArray;
5818       gameInfo.boardWidth  = 9;
5819       gameInfo.boardHeight = 9;
5820       gameInfo.holdingsSize = 7;
5821       nrCastlingRights = 0;
5822       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5823       break;
5824     case VariantCourier:
5825       pieces = CourierArray;
5826       gameInfo.boardWidth  = 12;
5827       nrCastlingRights = 0;
5828       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5829       break;
5830     case VariantKnightmate:
5831       pieces = KnightmateArray;
5832       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5833       break;
5834     case VariantSpartan:
5835       pieces = SpartanArray;
5836       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5837       break;
5838     case VariantFairy:
5839       pieces = fairyArray;
5840       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5841       break;
5842     case VariantGreat:
5843       pieces = GreatArray;
5844       gameInfo.boardWidth = 10;
5845       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5846       gameInfo.holdingsSize = 8;
5847       break;
5848     case VariantSuper:
5849       pieces = FIDEArray;
5850       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5851       gameInfo.holdingsSize = 8;
5852       startedFromSetupPosition = TRUE;
5853       break;
5854     case VariantCrazyhouse:
5855     case VariantBughouse:
5856       pieces = FIDEArray;
5857       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5858       gameInfo.holdingsSize = 5;
5859       break;
5860     case VariantWildCastle:
5861       pieces = FIDEArray;
5862       /* !!?shuffle with kings guaranteed to be on d or e file */
5863       shuffleOpenings = 1;
5864       break;
5865     case VariantNoCastle:
5866       pieces = FIDEArray;
5867       nrCastlingRights = 0;
5868       /* !!?unconstrained back-rank shuffle */
5869       shuffleOpenings = 1;
5870       break;
5871     }
5872
5873     overrule = 0;
5874     if(appData.NrFiles >= 0) {
5875         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5876         gameInfo.boardWidth = appData.NrFiles;
5877     }
5878     if(appData.NrRanks >= 0) {
5879         gameInfo.boardHeight = appData.NrRanks;
5880     }
5881     if(appData.holdingsSize >= 0) {
5882         i = appData.holdingsSize;
5883         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5884         gameInfo.holdingsSize = i;
5885     }
5886     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5887     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5888         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5889
5890     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5891     if(pawnRow < 1) pawnRow = 1;
5892     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5893
5894     /* User pieceToChar list overrules defaults */
5895     if(appData.pieceToCharTable != NULL)
5896         SetCharTable(pieceToChar, appData.pieceToCharTable);
5897
5898     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5899
5900         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5901             s = (ChessSquare) 0; /* account holding counts in guard band */
5902         for( i=0; i<BOARD_HEIGHT; i++ )
5903             initialPosition[i][j] = s;
5904
5905         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5906         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5907         initialPosition[pawnRow][j] = WhitePawn;
5908         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5909         if(gameInfo.variant == VariantXiangqi) {
5910             if(j&1) {
5911                 initialPosition[pawnRow][j] =
5912                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5913                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5914                    initialPosition[2][j] = WhiteCannon;
5915                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5916                 }
5917             }
5918         }
5919         if(gameInfo.variant == VariantGrand) {
5920             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5921                initialPosition[0][j] = WhiteRook;
5922                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5923             }
5924         }
5925         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5926     }
5927     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5928
5929             j=BOARD_LEFT+1;
5930             initialPosition[1][j] = WhiteBishop;
5931             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5932             j=BOARD_RGHT-2;
5933             initialPosition[1][j] = WhiteRook;
5934             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5935     }
5936
5937     if( nrCastlingRights == -1) {
5938         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5939         /*       This sets default castling rights from none to normal corners   */
5940         /* Variants with other castling rights must set them themselves above    */
5941         nrCastlingRights = 6;
5942
5943         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5944         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5945         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5946         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5947         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5948         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5949      }
5950
5951      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5952      if(gameInfo.variant == VariantGreat) { // promotion commoners
5953         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5954         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5955         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5956         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5957      }
5958      if( gameInfo.variant == VariantSChess ) {
5959       initialPosition[1][0] = BlackMarshall;
5960       initialPosition[2][0] = BlackAngel;
5961       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5962       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5963       initialPosition[1][1] = initialPosition[2][1] = 
5964       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5965      }
5966   if (appData.debugMode) {
5967     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5968   }
5969     if(shuffleOpenings) {
5970         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5971         startedFromSetupPosition = TRUE;
5972     }
5973     if(startedFromPositionFile) {
5974       /* [HGM] loadPos: use PositionFile for every new game */
5975       CopyBoard(initialPosition, filePosition);
5976       for(i=0; i<nrCastlingRights; i++)
5977           initialRights[i] = filePosition[CASTLING][i];
5978       startedFromSetupPosition = TRUE;
5979     }
5980
5981     CopyBoard(boards[0], initialPosition);
5982
5983     if(oldx != gameInfo.boardWidth ||
5984        oldy != gameInfo.boardHeight ||
5985        oldv != gameInfo.variant ||
5986        oldh != gameInfo.holdingsWidth
5987                                          )
5988             InitDrawingSizes(-2 ,0);
5989
5990     oldv = gameInfo.variant;
5991     if (redraw)
5992       DrawPosition(TRUE, boards[currentMove]);
5993 }
5994
5995 void
5996 SendBoard(cps, moveNum)
5997      ChessProgramState *cps;
5998      int moveNum;
5999 {
6000     char message[MSG_SIZ];
6001
6002     if (cps->useSetboard) {
6003       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6004       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6005       SendToProgram(message, cps);
6006       free(fen);
6007
6008     } else {
6009       ChessSquare *bp;
6010       int i, j;
6011       /* Kludge to set black to move, avoiding the troublesome and now
6012        * deprecated "black" command.
6013        */
6014       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6015         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6016
6017       SendToProgram("edit\n", cps);
6018       SendToProgram("#\n", cps);
6019       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6020         bp = &boards[moveNum][i][BOARD_LEFT];
6021         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6022           if ((int) *bp < (int) BlackPawn) {
6023             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6024                     AAA + j, ONE + i);
6025             if(message[0] == '+' || message[0] == '~') {
6026               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6027                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6028                         AAA + j, ONE + i);
6029             }
6030             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6031                 message[1] = BOARD_RGHT   - 1 - j + '1';
6032                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6033             }
6034             SendToProgram(message, cps);
6035           }
6036         }
6037       }
6038
6039       SendToProgram("c\n", cps);
6040       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6041         bp = &boards[moveNum][i][BOARD_LEFT];
6042         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6043           if (((int) *bp != (int) EmptySquare)
6044               && ((int) *bp >= (int) BlackPawn)) {
6045             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6046                     AAA + j, ONE + i);
6047             if(message[0] == '+' || message[0] == '~') {
6048               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6049                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6050                         AAA + j, ONE + i);
6051             }
6052             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6053                 message[1] = BOARD_RGHT   - 1 - j + '1';
6054                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6055             }
6056             SendToProgram(message, cps);
6057           }
6058         }
6059       }
6060
6061       SendToProgram(".\n", cps);
6062     }
6063     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6064 }
6065
6066 ChessSquare
6067 DefaultPromoChoice(int white)
6068 {
6069     ChessSquare result;
6070     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6071         result = WhiteFerz; // no choice
6072     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6073         result= WhiteKing; // in Suicide Q is the last thing we want
6074     else if(gameInfo.variant == VariantSpartan)
6075         result = white ? WhiteQueen : WhiteAngel;
6076     else result = WhiteQueen;
6077     if(!white) result = WHITE_TO_BLACK result;
6078     return result;
6079 }
6080
6081 static int autoQueen; // [HGM] oneclick
6082
6083 int
6084 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6085 {
6086     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6087     /* [HGM] add Shogi promotions */
6088     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6089     ChessSquare piece;
6090     ChessMove moveType;
6091     Boolean premove;
6092
6093     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6094     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6095
6096     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6097       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6098         return FALSE;
6099
6100     piece = boards[currentMove][fromY][fromX];
6101     if(gameInfo.variant == VariantShogi) {
6102         promotionZoneSize = BOARD_HEIGHT/3;
6103         highestPromotingPiece = (int)WhiteFerz;
6104     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6105         promotionZoneSize = 3;
6106     }
6107
6108     // Treat Lance as Pawn when it is not representing Amazon
6109     if(gameInfo.variant != VariantSuper) {
6110         if(piece == WhiteLance) piece = WhitePawn; else
6111         if(piece == BlackLance) piece = BlackPawn;
6112     }
6113
6114     // next weed out all moves that do not touch the promotion zone at all
6115     if((int)piece >= BlackPawn) {
6116         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6117              return FALSE;
6118         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6119     } else {
6120         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6121            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6122     }
6123
6124     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6125
6126     // weed out mandatory Shogi promotions
6127     if(gameInfo.variant == VariantShogi) {
6128         if(piece >= BlackPawn) {
6129             if(toY == 0 && piece == BlackPawn ||
6130                toY == 0 && piece == BlackQueen ||
6131                toY <= 1 && piece == BlackKnight) {
6132                 *promoChoice = '+';
6133                 return FALSE;
6134             }
6135         } else {
6136             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6137                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6138                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6139                 *promoChoice = '+';
6140                 return FALSE;
6141             }
6142         }
6143     }
6144
6145     // weed out obviously illegal Pawn moves
6146     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6147         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6148         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6149         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6150         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6151         // note we are not allowed to test for valid (non-)capture, due to premove
6152     }
6153
6154     // we either have a choice what to promote to, or (in Shogi) whether to promote
6155     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6156         *promoChoice = PieceToChar(BlackFerz);  // no choice
6157         return FALSE;
6158     }
6159     // no sense asking what we must promote to if it is going to explode...
6160     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6161         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6162         return FALSE;
6163     }
6164     // give caller the default choice even if we will not make it
6165     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6166     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6167     if(        sweepSelect && gameInfo.variant != VariantGreat
6168                            && gameInfo.variant != VariantGrand
6169                            && gameInfo.variant != VariantSuper) return FALSE;
6170     if(autoQueen) return FALSE; // predetermined
6171
6172     // suppress promotion popup on illegal moves that are not premoves
6173     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6174               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6175     if(appData.testLegality && !premove) {
6176         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6177                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6178         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6179             return FALSE;
6180     }
6181
6182     return TRUE;
6183 }
6184
6185 int
6186 InPalace(row, column)
6187      int row, column;
6188 {   /* [HGM] for Xiangqi */
6189     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6190          column < (BOARD_WIDTH + 4)/2 &&
6191          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6192     return FALSE;
6193 }
6194
6195 int
6196 PieceForSquare (x, y)
6197      int x;
6198      int y;
6199 {
6200   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6201      return -1;
6202   else
6203      return boards[currentMove][y][x];
6204 }
6205
6206 int
6207 OKToStartUserMove(x, y)
6208      int x, y;
6209 {
6210     ChessSquare from_piece;
6211     int white_piece;
6212
6213     if (matchMode) return FALSE;
6214     if (gameMode == EditPosition) return TRUE;
6215
6216     if (x >= 0 && y >= 0)
6217       from_piece = boards[currentMove][y][x];
6218     else
6219       from_piece = EmptySquare;
6220
6221     if (from_piece == EmptySquare) return FALSE;
6222
6223     white_piece = (int)from_piece >= (int)WhitePawn &&
6224       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6225
6226     switch (gameMode) {
6227       case PlayFromGameFile:
6228       case AnalyzeFile:
6229       case TwoMachinesPlay:
6230       case EndOfGame:
6231         return FALSE;
6232
6233       case IcsObserving:
6234       case IcsIdle:
6235         return FALSE;
6236
6237       case MachinePlaysWhite:
6238       case IcsPlayingBlack:
6239         if (appData.zippyPlay) return FALSE;
6240         if (white_piece) {
6241             DisplayMoveError(_("You are playing Black"));
6242             return FALSE;
6243         }
6244         break;
6245
6246       case MachinePlaysBlack:
6247       case IcsPlayingWhite:
6248         if (appData.zippyPlay) return FALSE;
6249         if (!white_piece) {
6250             DisplayMoveError(_("You are playing White"));
6251             return FALSE;
6252         }
6253         break;
6254
6255       case EditGame:
6256         if (!white_piece && WhiteOnMove(currentMove)) {
6257             DisplayMoveError(_("It is White's turn"));
6258             return FALSE;
6259         }
6260         if (white_piece && !WhiteOnMove(currentMove)) {
6261             DisplayMoveError(_("It is Black's turn"));
6262             return FALSE;
6263         }
6264         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6265             /* Editing correspondence game history */
6266             /* Could disallow this or prompt for confirmation */
6267             cmailOldMove = -1;
6268         }
6269         break;
6270
6271       case BeginningOfGame:
6272         if (appData.icsActive) return FALSE;
6273         if (!appData.noChessProgram) {
6274             if (!white_piece) {
6275                 DisplayMoveError(_("You are playing White"));
6276                 return FALSE;
6277             }
6278         }
6279         break;
6280
6281       case Training:
6282         if (!white_piece && WhiteOnMove(currentMove)) {
6283             DisplayMoveError(_("It is White's turn"));
6284             return FALSE;
6285         }
6286         if (white_piece && !WhiteOnMove(currentMove)) {
6287             DisplayMoveError(_("It is Black's turn"));
6288             return FALSE;
6289         }
6290         break;
6291
6292       default:
6293       case IcsExamining:
6294         break;
6295     }
6296     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6297         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6298         && gameMode != AnalyzeFile && gameMode != Training) {
6299         DisplayMoveError(_("Displayed position is not current"));
6300         return FALSE;
6301     }
6302     return TRUE;
6303 }
6304
6305 Boolean
6306 OnlyMove(int *x, int *y, Boolean captures) {
6307     DisambiguateClosure cl;
6308     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6309     switch(gameMode) {
6310       case MachinePlaysBlack:
6311       case IcsPlayingWhite:
6312       case BeginningOfGame:
6313         if(!WhiteOnMove(currentMove)) return FALSE;
6314         break;
6315       case MachinePlaysWhite:
6316       case IcsPlayingBlack:
6317         if(WhiteOnMove(currentMove)) return FALSE;
6318         break;
6319       case EditGame:
6320         break;
6321       default:
6322         return FALSE;
6323     }
6324     cl.pieceIn = EmptySquare;
6325     cl.rfIn = *y;
6326     cl.ffIn = *x;
6327     cl.rtIn = -1;
6328     cl.ftIn = -1;
6329     cl.promoCharIn = NULLCHAR;
6330     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6331     if( cl.kind == NormalMove ||
6332         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6333         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6334         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6335       fromX = cl.ff;
6336       fromY = cl.rf;
6337       *x = cl.ft;
6338       *y = cl.rt;
6339       return TRUE;
6340     }
6341     if(cl.kind != ImpossibleMove) return FALSE;
6342     cl.pieceIn = EmptySquare;
6343     cl.rfIn = -1;
6344     cl.ffIn = -1;
6345     cl.rtIn = *y;
6346     cl.ftIn = *x;
6347     cl.promoCharIn = NULLCHAR;
6348     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6349     if( cl.kind == NormalMove ||
6350         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6351         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6352         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6353       fromX = cl.ff;
6354       fromY = cl.rf;
6355       *x = cl.ft;
6356       *y = cl.rt;
6357       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6358       return TRUE;
6359     }
6360     return FALSE;
6361 }
6362
6363 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6364 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6365 int lastLoadGameUseList = FALSE;
6366 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6367 ChessMove lastLoadGameStart = EndOfFile;
6368
6369 void
6370 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6371      int fromX, fromY, toX, toY;
6372      int promoChar;
6373 {
6374     ChessMove moveType;
6375     ChessSquare pdown, pup;
6376
6377     /* Check if the user is playing in turn.  This is complicated because we
6378        let the user "pick up" a piece before it is his turn.  So the piece he
6379        tried to pick up may have been captured by the time he puts it down!
6380        Therefore we use the color the user is supposed to be playing in this
6381        test, not the color of the piece that is currently on the starting
6382        square---except in EditGame mode, where the user is playing both
6383        sides; fortunately there the capture race can't happen.  (It can
6384        now happen in IcsExamining mode, but that's just too bad.  The user
6385        will get a somewhat confusing message in that case.)
6386        */
6387
6388     switch (gameMode) {
6389       case PlayFromGameFile:
6390       case AnalyzeFile:
6391       case TwoMachinesPlay:
6392       case EndOfGame:
6393       case IcsObserving:
6394       case IcsIdle:
6395         /* We switched into a game mode where moves are not accepted,
6396            perhaps while the mouse button was down. */
6397         return;
6398
6399       case MachinePlaysWhite:
6400         /* User is moving for Black */
6401         if (WhiteOnMove(currentMove)) {
6402             DisplayMoveError(_("It is White's turn"));
6403             return;
6404         }
6405         break;
6406
6407       case MachinePlaysBlack:
6408         /* User is moving for White */
6409         if (!WhiteOnMove(currentMove)) {
6410             DisplayMoveError(_("It is Black's turn"));
6411             return;
6412         }
6413         break;
6414
6415       case EditGame:
6416       case IcsExamining:
6417       case BeginningOfGame:
6418       case AnalyzeMode:
6419       case Training:
6420         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6421         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6422             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6423             /* User is moving for Black */
6424             if (WhiteOnMove(currentMove)) {
6425                 DisplayMoveError(_("It is White's turn"));
6426                 return;
6427             }
6428         } else {
6429             /* User is moving for White */
6430             if (!WhiteOnMove(currentMove)) {
6431                 DisplayMoveError(_("It is Black's turn"));
6432                 return;
6433             }
6434         }
6435         break;
6436
6437       case IcsPlayingBlack:
6438         /* User is moving for Black */
6439         if (WhiteOnMove(currentMove)) {
6440             if (!appData.premove) {
6441                 DisplayMoveError(_("It is White's turn"));
6442             } else if (toX >= 0 && toY >= 0) {
6443                 premoveToX = toX;
6444                 premoveToY = toY;
6445                 premoveFromX = fromX;
6446                 premoveFromY = fromY;
6447                 premovePromoChar = promoChar;
6448                 gotPremove = 1;
6449                 if (appData.debugMode)
6450                     fprintf(debugFP, "Got premove: fromX %d,"
6451                             "fromY %d, toX %d, toY %d\n",
6452                             fromX, fromY, toX, toY);
6453             }
6454             return;
6455         }
6456         break;
6457
6458       case IcsPlayingWhite:
6459         /* User is moving for White */
6460         if (!WhiteOnMove(currentMove)) {
6461             if (!appData.premove) {
6462                 DisplayMoveError(_("It is Black's turn"));
6463             } else if (toX >= 0 && toY >= 0) {
6464                 premoveToX = toX;
6465                 premoveToY = toY;
6466                 premoveFromX = fromX;
6467                 premoveFromY = fromY;
6468                 premovePromoChar = promoChar;
6469                 gotPremove = 1;
6470                 if (appData.debugMode)
6471                     fprintf(debugFP, "Got premove: fromX %d,"
6472                             "fromY %d, toX %d, toY %d\n",
6473                             fromX, fromY, toX, toY);
6474             }
6475             return;
6476         }
6477         break;
6478
6479       default:
6480         break;
6481
6482       case EditPosition:
6483         /* EditPosition, empty square, or different color piece;
6484            click-click move is possible */
6485         if (toX == -2 || toY == -2) {
6486             boards[0][fromY][fromX] = EmptySquare;
6487             DrawPosition(FALSE, boards[currentMove]);
6488             return;
6489         } else if (toX >= 0 && toY >= 0) {
6490             boards[0][toY][toX] = boards[0][fromY][fromX];
6491             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6492                 if(boards[0][fromY][0] != EmptySquare) {
6493                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6494                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6495                 }
6496             } else
6497             if(fromX == BOARD_RGHT+1) {
6498                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6499                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6500                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6501                 }
6502             } else
6503             boards[0][fromY][fromX] = EmptySquare;
6504             DrawPosition(FALSE, boards[currentMove]);
6505             return;
6506         }
6507         return;
6508     }
6509
6510     if(toX < 0 || toY < 0) return;
6511     pdown = boards[currentMove][fromY][fromX];
6512     pup = boards[currentMove][toY][toX];
6513
6514     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6515     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6516          if( pup != EmptySquare ) return;
6517          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6518            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6519                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6520            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6521            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6522            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6523            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6524          fromY = DROP_RANK;
6525     }
6526
6527     /* [HGM] always test for legality, to get promotion info */
6528     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6529                                          fromY, fromX, toY, toX, promoChar);
6530     /* [HGM] but possibly ignore an IllegalMove result */
6531     if (appData.testLegality) {
6532         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6533             DisplayMoveError(_("Illegal move"));
6534             return;
6535         }
6536     }
6537
6538     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6539 }
6540
6541 /* Common tail of UserMoveEvent and DropMenuEvent */
6542 int
6543 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6544      ChessMove moveType;
6545      int fromX, fromY, toX, toY;
6546      /*char*/int promoChar;
6547 {
6548     char *bookHit = 0;
6549
6550     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6551         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6552         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6553         if(WhiteOnMove(currentMove)) {
6554             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6555         } else {
6556             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6557         }
6558     }
6559
6560     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6561        move type in caller when we know the move is a legal promotion */
6562     if(moveType == NormalMove && promoChar)
6563         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6564
6565     /* [HGM] <popupFix> The following if has been moved here from
6566        UserMoveEvent(). Because it seemed to belong here (why not allow
6567        piece drops in training games?), and because it can only be
6568        performed after it is known to what we promote. */
6569     if (gameMode == Training) {
6570       /* compare the move played on the board to the next move in the
6571        * game. If they match, display the move and the opponent's response.
6572        * If they don't match, display an error message.
6573        */
6574       int saveAnimate;
6575       Board testBoard;
6576       CopyBoard(testBoard, boards[currentMove]);
6577       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6578
6579       if (CompareBoards(testBoard, boards[currentMove+1])) {
6580         ForwardInner(currentMove+1);
6581
6582         /* Autoplay the opponent's response.
6583          * if appData.animate was TRUE when Training mode was entered,
6584          * the response will be animated.
6585          */
6586         saveAnimate = appData.animate;
6587         appData.animate = animateTraining;
6588         ForwardInner(currentMove+1);
6589         appData.animate = saveAnimate;
6590
6591         /* check for the end of the game */
6592         if (currentMove >= forwardMostMove) {
6593           gameMode = PlayFromGameFile;
6594           ModeHighlight();
6595           SetTrainingModeOff();
6596           DisplayInformation(_("End of game"));
6597         }
6598       } else {
6599         DisplayError(_("Incorrect move"), 0);
6600       }
6601       return 1;
6602     }
6603
6604   /* Ok, now we know that the move is good, so we can kill
6605      the previous line in Analysis Mode */
6606   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6607                                 && currentMove < forwardMostMove) {
6608     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6609     else forwardMostMove = currentMove;
6610   }
6611
6612   /* If we need the chess program but it's dead, restart it */
6613   ResurrectChessProgram();
6614
6615   /* A user move restarts a paused game*/
6616   if (pausing)
6617     PauseEvent();
6618
6619   thinkOutput[0] = NULLCHAR;
6620
6621   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6622
6623   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6624     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6625     return 1;
6626   }
6627
6628   if (gameMode == BeginningOfGame) {
6629     if (appData.noChessProgram) {
6630       gameMode = EditGame;
6631       SetGameInfo();
6632     } else {
6633       char buf[MSG_SIZ];
6634       gameMode = MachinePlaysBlack;
6635       StartClocks();
6636       SetGameInfo();
6637       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6638       DisplayTitle(buf);
6639       if (first.sendName) {
6640         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6641         SendToProgram(buf, &first);
6642       }
6643       StartClocks();
6644     }
6645     ModeHighlight();
6646   }
6647
6648   /* Relay move to ICS or chess engine */
6649   if (appData.icsActive) {
6650     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6651         gameMode == IcsExamining) {
6652       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6653         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6654         SendToICS("draw ");
6655         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6656       }
6657       // also send plain move, in case ICS does not understand atomic claims
6658       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6659       ics_user_moved = 1;
6660     }
6661   } else {
6662     if (first.sendTime && (gameMode == BeginningOfGame ||
6663                            gameMode == MachinePlaysWhite ||
6664                            gameMode == MachinePlaysBlack)) {
6665       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6666     }
6667     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6668          // [HGM] book: if program might be playing, let it use book
6669         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6670         first.maybeThinking = TRUE;
6671     } else SendMoveToProgram(forwardMostMove-1, &first);
6672     if (currentMove == cmailOldMove + 1) {
6673       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6674     }
6675   }
6676
6677   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6678
6679   switch (gameMode) {
6680   case EditGame:
6681     if(appData.testLegality)
6682     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6683     case MT_NONE:
6684     case MT_CHECK:
6685       break;
6686     case MT_CHECKMATE:
6687     case MT_STAINMATE:
6688       if (WhiteOnMove(currentMove)) {
6689         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6690       } else {
6691         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6692       }
6693       break;
6694     case MT_STALEMATE:
6695       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6696       break;
6697     }
6698     break;
6699
6700   case MachinePlaysBlack:
6701   case MachinePlaysWhite:
6702     /* disable certain menu options while machine is thinking */
6703     SetMachineThinkingEnables();
6704     break;
6705
6706   default:
6707     break;
6708   }
6709
6710   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6711   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6712
6713   if(bookHit) { // [HGM] book: simulate book reply
6714         static char bookMove[MSG_SIZ]; // a bit generous?
6715
6716         programStats.nodes = programStats.depth = programStats.time =
6717         programStats.score = programStats.got_only_move = 0;
6718         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6719
6720         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6721         strcat(bookMove, bookHit);
6722         HandleMachineMove(bookMove, &first);
6723   }
6724   return 1;
6725 }
6726
6727 void
6728 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6729      Board board;
6730      int flags;
6731      ChessMove kind;
6732      int rf, ff, rt, ft;
6733      VOIDSTAR closure;
6734 {
6735     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6736     Markers *m = (Markers *) closure;
6737     if(rf == fromY && ff == fromX)
6738         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6739                          || kind == WhiteCapturesEnPassant
6740                          || kind == BlackCapturesEnPassant);
6741     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6742 }
6743
6744 void
6745 MarkTargetSquares(int clear)
6746 {
6747   int x, y;
6748   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6749      !appData.testLegality || gameMode == EditPosition) return;
6750   if(clear) {
6751     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6752   } else {
6753     int capt = 0;
6754     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6755     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6756       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6757       if(capt)
6758       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6759     }
6760   }
6761   DrawPosition(TRUE, NULL);
6762 }
6763
6764 int
6765 Explode(Board board, int fromX, int fromY, int toX, int toY)
6766 {
6767     if(gameInfo.variant == VariantAtomic &&
6768        (board[toY][toX] != EmptySquare ||                     // capture?
6769         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6770                          board[fromY][fromX] == BlackPawn   )
6771       )) {
6772         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6773         return TRUE;
6774     }
6775     return FALSE;
6776 }
6777
6778 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6779
6780 int CanPromote(ChessSquare piece, int y)
6781 {
6782         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6783         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6784         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6785            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6786            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6787                                                   gameInfo.variant == VariantMakruk) return FALSE;
6788         return (piece == BlackPawn && y == 1 ||
6789                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6790                 piece == BlackLance && y == 1 ||
6791                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6792 }
6793
6794 void LeftClick(ClickType clickType, int xPix, int yPix)
6795 {
6796     int x, y;
6797     Boolean saveAnimate;
6798     static int second = 0, promotionChoice = 0, clearFlag = 0;
6799     char promoChoice = NULLCHAR;
6800     ChessSquare piece;
6801
6802     if(appData.seekGraph && appData.icsActive && loggedOn &&
6803         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6804         SeekGraphClick(clickType, xPix, yPix, 0);
6805         return;
6806     }
6807
6808     if (clickType == Press) ErrorPopDown();
6809
6810     x = EventToSquare(xPix, BOARD_WIDTH);
6811     y = EventToSquare(yPix, BOARD_HEIGHT);
6812     if (!flipView && y >= 0) {
6813         y = BOARD_HEIGHT - 1 - y;
6814     }
6815     if (flipView && x >= 0) {
6816         x = BOARD_WIDTH - 1 - x;
6817     }
6818
6819     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6820         defaultPromoChoice = promoSweep;
6821         promoSweep = EmptySquare;   // terminate sweep
6822         promoDefaultAltered = TRUE;
6823         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6824     }
6825
6826     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6827         if(clickType == Release) return; // ignore upclick of click-click destination
6828         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6829         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6830         if(gameInfo.holdingsWidth &&
6831                 (WhiteOnMove(currentMove)
6832                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6833                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6834             // click in right holdings, for determining promotion piece
6835             ChessSquare p = boards[currentMove][y][x];
6836             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6837             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6838             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6839                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6840                 fromX = fromY = -1;
6841                 return;
6842             }
6843         }
6844         DrawPosition(FALSE, boards[currentMove]);
6845         return;
6846     }
6847
6848     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6849     if(clickType == Press
6850             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6851               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6852               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6853         return;
6854
6855     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6856         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6857
6858     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6859         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6860                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6861         defaultPromoChoice = DefaultPromoChoice(side);
6862     }
6863
6864     autoQueen = appData.alwaysPromoteToQueen;
6865
6866     if (fromX == -1) {
6867       int originalY = y;
6868       gatingPiece = EmptySquare;
6869       if (clickType != Press) {
6870         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6871             DragPieceEnd(xPix, yPix); dragging = 0;
6872             DrawPosition(FALSE, NULL);
6873         }
6874         return;
6875       }
6876       fromX = x; fromY = y; toX = toY = -1;
6877       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6878          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6879          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6880             /* First square */
6881             if (OKToStartUserMove(fromX, fromY)) {
6882                 second = 0;
6883                 MarkTargetSquares(0);
6884                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6885                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6886                     promoSweep = defaultPromoChoice;
6887                     selectFlag = 0; lastX = xPix; lastY = yPix;
6888                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6889                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6890                 }
6891                 if (appData.highlightDragging) {
6892                     SetHighlights(fromX, fromY, -1, -1);
6893                 }
6894             } else fromX = fromY = -1;
6895             return;
6896         }
6897     }
6898
6899     /* fromX != -1 */
6900     if (clickType == Press && gameMode != EditPosition) {
6901         ChessSquare fromP;
6902         ChessSquare toP;
6903         int frc;
6904
6905         // ignore off-board to clicks
6906         if(y < 0 || x < 0) return;
6907
6908         /* Check if clicking again on the same color piece */
6909         fromP = boards[currentMove][fromY][fromX];
6910         toP = boards[currentMove][y][x];
6911         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6912         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6913              WhitePawn <= toP && toP <= WhiteKing &&
6914              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6915              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6916             (BlackPawn <= fromP && fromP <= BlackKing &&
6917              BlackPawn <= toP && toP <= BlackKing &&
6918              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6919              !(fromP == BlackKing && toP == BlackRook && frc))) {
6920             /* Clicked again on same color piece -- changed his mind */
6921             second = (x == fromX && y == fromY);
6922             promoDefaultAltered = FALSE;
6923             MarkTargetSquares(1);
6924            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6925             if (appData.highlightDragging) {
6926                 SetHighlights(x, y, -1, -1);
6927             } else {
6928                 ClearHighlights();
6929             }
6930             if (OKToStartUserMove(x, y)) {
6931                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6932                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6933                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6934                  gatingPiece = boards[currentMove][fromY][fromX];
6935                 else gatingPiece = EmptySquare;
6936                 fromX = x;
6937                 fromY = y; dragging = 1;
6938                 MarkTargetSquares(0);
6939                 DragPieceBegin(xPix, yPix, FALSE);
6940                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6941                     promoSweep = defaultPromoChoice;
6942                     selectFlag = 0; lastX = xPix; lastY = yPix;
6943                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6944                 }
6945             }
6946            }
6947            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6948            second = FALSE; 
6949         }
6950         // ignore clicks on holdings
6951         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6952     }
6953
6954     if (clickType == Release && x == fromX && y == fromY) {
6955         DragPieceEnd(xPix, yPix); dragging = 0;
6956         if(clearFlag) {
6957             // a deferred attempt to click-click move an empty square on top of a piece
6958             boards[currentMove][y][x] = EmptySquare;
6959             ClearHighlights();
6960             DrawPosition(FALSE, boards[currentMove]);
6961             fromX = fromY = -1; clearFlag = 0;
6962             return;
6963         }
6964         if (appData.animateDragging) {
6965             /* Undo animation damage if any */
6966             DrawPosition(FALSE, NULL);
6967         }
6968         if (second) {
6969             /* Second up/down in same square; just abort move */
6970             second = 0;
6971             fromX = fromY = -1;
6972             gatingPiece = EmptySquare;
6973             ClearHighlights();
6974             gotPremove = 0;
6975             ClearPremoveHighlights();
6976         } else {
6977             /* First upclick in same square; start click-click mode */
6978             SetHighlights(x, y, -1, -1);
6979         }
6980         return;
6981     }
6982
6983     clearFlag = 0;
6984
6985     /* we now have a different from- and (possibly off-board) to-square */
6986     /* Completed move */
6987     toX = x;
6988     toY = y;
6989     saveAnimate = appData.animate;
6990     MarkTargetSquares(1);
6991     if (clickType == Press) {
6992         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6993             // must be Edit Position mode with empty-square selected
6994             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
6995             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6996             return;
6997         }
6998         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
6999             ChessSquare piece = boards[currentMove][fromY][fromX];
7000             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7001             promoSweep = defaultPromoChoice;
7002             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7003             selectFlag = 0; lastX = xPix; lastY = yPix;
7004             Sweep(0); // Pawn that is going to promote: preview promotion piece
7005             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7006             DrawPosition(FALSE, boards[currentMove]);
7007             return;
7008         }
7009         /* Finish clickclick move */
7010         if (appData.animate || appData.highlightLastMove) {
7011             SetHighlights(fromX, fromY, toX, toY);
7012         } else {
7013             ClearHighlights();
7014         }
7015     } else {
7016         /* Finish drag move */
7017         if (appData.highlightLastMove) {
7018             SetHighlights(fromX, fromY, toX, toY);
7019         } else {
7020             ClearHighlights();
7021         }
7022         DragPieceEnd(xPix, yPix); dragging = 0;
7023         /* Don't animate move and drag both */
7024         appData.animate = FALSE;
7025     }
7026
7027     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7028     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7029         ChessSquare piece = boards[currentMove][fromY][fromX];
7030         if(gameMode == EditPosition && piece != EmptySquare &&
7031            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7032             int n;
7033
7034             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7035                 n = PieceToNumber(piece - (int)BlackPawn);
7036                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7037                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7038                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7039             } else
7040             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7041                 n = PieceToNumber(piece);
7042                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7043                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7044                 boards[currentMove][n][BOARD_WIDTH-2]++;
7045             }
7046             boards[currentMove][fromY][fromX] = EmptySquare;
7047         }
7048         ClearHighlights();
7049         fromX = fromY = -1;
7050         DrawPosition(TRUE, boards[currentMove]);
7051         return;
7052     }
7053
7054     // off-board moves should not be highlighted
7055     if(x < 0 || y < 0) ClearHighlights();
7056
7057     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7058
7059     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7060         SetHighlights(fromX, fromY, toX, toY);
7061         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7062             // [HGM] super: promotion to captured piece selected from holdings
7063             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7064             promotionChoice = TRUE;
7065             // kludge follows to temporarily execute move on display, without promoting yet
7066             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7067             boards[currentMove][toY][toX] = p;
7068             DrawPosition(FALSE, boards[currentMove]);
7069             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7070             boards[currentMove][toY][toX] = q;
7071             DisplayMessage("Click in holdings to choose piece", "");
7072             return;
7073         }
7074         PromotionPopUp();
7075     } else {
7076         int oldMove = currentMove;
7077         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7078         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7079         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7080         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7081            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7082             DrawPosition(TRUE, boards[currentMove]);
7083         fromX = fromY = -1;
7084     }
7085     appData.animate = saveAnimate;
7086     if (appData.animate || appData.animateDragging) {
7087         /* Undo animation damage if needed */
7088         DrawPosition(FALSE, NULL);
7089     }
7090 }
7091
7092 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7093 {   // front-end-free part taken out of PieceMenuPopup
7094     int whichMenu; int xSqr, ySqr;
7095
7096     if(seekGraphUp) { // [HGM] seekgraph
7097         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7098         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7099         return -2;
7100     }
7101
7102     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7103          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7104         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7105         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7106         if(action == Press)   {
7107             originalFlip = flipView;
7108             flipView = !flipView; // temporarily flip board to see game from partners perspective
7109             DrawPosition(TRUE, partnerBoard);
7110             DisplayMessage(partnerStatus, "");
7111             partnerUp = TRUE;
7112         } else if(action == Release) {
7113             flipView = originalFlip;
7114             DrawPosition(TRUE, boards[currentMove]);
7115             partnerUp = FALSE;
7116         }
7117         return -2;
7118     }
7119
7120     xSqr = EventToSquare(x, BOARD_WIDTH);
7121     ySqr = EventToSquare(y, BOARD_HEIGHT);
7122     if (action == Release) {
7123         if(pieceSweep != EmptySquare) {
7124             EditPositionMenuEvent(pieceSweep, toX, toY);
7125             pieceSweep = EmptySquare;
7126         } else UnLoadPV(); // [HGM] pv
7127     }
7128     if (action != Press) return -2; // return code to be ignored
7129     switch (gameMode) {
7130       case IcsExamining:
7131         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7132       case EditPosition:
7133         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7134         if (xSqr < 0 || ySqr < 0) return -1;
7135         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7136         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7137         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7138         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7139         NextPiece(0);
7140         return 2; // grab
7141       case IcsObserving:
7142         if(!appData.icsEngineAnalyze) return -1;
7143       case IcsPlayingWhite:
7144       case IcsPlayingBlack:
7145         if(!appData.zippyPlay) goto noZip;
7146       case AnalyzeMode:
7147       case AnalyzeFile:
7148       case MachinePlaysWhite:
7149       case MachinePlaysBlack:
7150       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7151         if (!appData.dropMenu) {
7152           LoadPV(x, y);
7153           return 2; // flag front-end to grab mouse events
7154         }
7155         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7156            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7157       case EditGame:
7158       noZip:
7159         if (xSqr < 0 || ySqr < 0) return -1;
7160         if (!appData.dropMenu || appData.testLegality &&
7161             gameInfo.variant != VariantBughouse &&
7162             gameInfo.variant != VariantCrazyhouse) return -1;
7163         whichMenu = 1; // drop menu
7164         break;
7165       default:
7166         return -1;
7167     }
7168
7169     if (((*fromX = xSqr) < 0) ||
7170         ((*fromY = ySqr) < 0)) {
7171         *fromX = *fromY = -1;
7172         return -1;
7173     }
7174     if (flipView)
7175       *fromX = BOARD_WIDTH - 1 - *fromX;
7176     else
7177       *fromY = BOARD_HEIGHT - 1 - *fromY;
7178
7179     return whichMenu;
7180 }
7181
7182 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7183 {
7184 //    char * hint = lastHint;
7185     FrontEndProgramStats stats;
7186
7187     stats.which = cps == &first ? 0 : 1;
7188     stats.depth = cpstats->depth;
7189     stats.nodes = cpstats->nodes;
7190     stats.score = cpstats->score;
7191     stats.time = cpstats->time;
7192     stats.pv = cpstats->movelist;
7193     stats.hint = lastHint;
7194     stats.an_move_index = 0;
7195     stats.an_move_count = 0;
7196
7197     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7198         stats.hint = cpstats->move_name;
7199         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7200         stats.an_move_count = cpstats->nr_moves;
7201     }
7202
7203     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
7204
7205     SetProgramStats( &stats );
7206 }
7207
7208 void
7209 ClearEngineOutputPane(int which)
7210 {
7211     static FrontEndProgramStats dummyStats;
7212     dummyStats.which = which;
7213     dummyStats.pv = "#";
7214     SetProgramStats( &dummyStats );
7215 }
7216
7217 #define MAXPLAYERS 500
7218
7219 char *
7220 TourneyStandings(int display)
7221 {
7222     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7223     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7224     char result, *p, *names[MAXPLAYERS];
7225
7226     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7227         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7228     names[0] = p = strdup(appData.participants);
7229     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7230
7231     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7232
7233     while(result = appData.results[nr]) {
7234         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7235         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7236         wScore = bScore = 0;
7237         switch(result) {
7238           case '+': wScore = 2; break;
7239           case '-': bScore = 2; break;
7240           case '=': wScore = bScore = 1; break;
7241           case ' ':
7242           case '*': return strdup("busy"); // tourney not finished
7243         }
7244         score[w] += wScore;
7245         score[b] += bScore;
7246         games[w]++;
7247         games[b]++;
7248         nr++;
7249     }
7250     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7251     for(w=0; w<nPlayers; w++) {
7252         bScore = -1;
7253         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7254         ranking[w] = b; points[w] = bScore; score[b] = -2;
7255     }
7256     p = malloc(nPlayers*34+1);
7257     for(w=0; w<nPlayers && w<display; w++)
7258         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7259     free(names[0]);
7260     return p;
7261 }
7262
7263 void
7264 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7265 {       // count all piece types
7266         int p, f, r;
7267         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7268         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7269         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7270                 p = board[r][f];
7271                 pCnt[p]++;
7272                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7273                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7274                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7275                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7276                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7277                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7278         }
7279 }
7280
7281 int
7282 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7283 {
7284         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7285         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7286
7287         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7288         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7289         if(myPawns == 2 && nMine == 3) // KPP
7290             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7291         if(myPawns == 1 && nMine == 2) // KP
7292             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7293         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7294             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7295         if(myPawns) return FALSE;
7296         if(pCnt[WhiteRook+side])
7297             return pCnt[BlackRook-side] ||
7298                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7299                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7300                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7301         if(pCnt[WhiteCannon+side]) {
7302             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7303             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7304         }
7305         if(pCnt[WhiteKnight+side])
7306             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7307         return FALSE;
7308 }
7309
7310 int
7311 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7312 {
7313         VariantClass v = gameInfo.variant;
7314
7315         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7316         if(v == VariantShatranj) return TRUE; // always winnable through baring
7317         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7318         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7319
7320         if(v == VariantXiangqi) {
7321                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7322
7323                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7324                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7325                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7326                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7327                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7328                 if(stale) // we have at least one last-rank P plus perhaps C
7329                     return majors // KPKX
7330                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7331                 else // KCA*E*
7332                     return pCnt[WhiteFerz+side] // KCAK
7333                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7334                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7335                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7336
7337         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7338                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7339
7340                 if(nMine == 1) return FALSE; // bare King
7341                 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
7342                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7343                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7344                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7345                 if(pCnt[WhiteKnight+side])
7346                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7347                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7348                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7349                 if(nBishops)
7350                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7351                 if(pCnt[WhiteAlfil+side])
7352                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7353                 if(pCnt[WhiteWazir+side])
7354                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7355         }
7356
7357         return TRUE;
7358 }
7359
7360 int
7361 Adjudicate(ChessProgramState *cps)
7362 {       // [HGM] some adjudications useful with buggy engines
7363         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7364         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7365         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7366         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7367         int k, count = 0; static int bare = 1;
7368         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7369         Boolean canAdjudicate = !appData.icsActive;
7370
7371         // most tests only when we understand the game, i.e. legality-checking on
7372             if( appData.testLegality )
7373             {   /* [HGM] Some more adjudications for obstinate engines */
7374                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7375                 static int moveCount = 6;
7376                 ChessMove result;
7377                 char *reason = NULL;
7378
7379                 /* Count what is on board. */
7380                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7381
7382                 /* Some material-based adjudications that have to be made before stalemate test */
7383                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7384                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7385                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7386                      if(canAdjudicate && appData.checkMates) {
7387                          if(engineOpponent)
7388                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7389                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7390                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7391                          return 1;
7392                      }
7393                 }
7394
7395                 /* Bare King in Shatranj (loses) or Losers (wins) */
7396                 if( nrW == 1 || nrB == 1) {
7397                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7398                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7399                      if(canAdjudicate && appData.checkMates) {
7400                          if(engineOpponent)
7401                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7402                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7403                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7404                          return 1;
7405                      }
7406                   } else
7407                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7408                   {    /* bare King */
7409                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7410                         if(canAdjudicate && appData.checkMates) {
7411                             /* but only adjudicate if adjudication enabled */
7412                             if(engineOpponent)
7413                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7414                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7415                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7416                             return 1;
7417                         }
7418                   }
7419                 } else bare = 1;
7420
7421
7422             // don't wait for engine to announce game end if we can judge ourselves
7423             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7424               case MT_CHECK:
7425                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7426                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7427                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7428                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7429                             checkCnt++;
7430                         if(checkCnt >= 2) {
7431                             reason = "Xboard adjudication: 3rd check";
7432                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7433                             break;
7434                         }
7435                     }
7436                 }
7437               case MT_NONE:
7438               default:
7439                 break;
7440               case MT_STALEMATE:
7441               case MT_STAINMATE:
7442                 reason = "Xboard adjudication: Stalemate";
7443                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7444                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7445                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7446                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7447                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7448                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7449                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7450                                                                         EP_CHECKMATE : EP_WINS);
7451                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7452                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7453                 }
7454                 break;
7455               case MT_CHECKMATE:
7456                 reason = "Xboard adjudication: Checkmate";
7457                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7458                 break;
7459             }
7460
7461                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7462                     case EP_STALEMATE:
7463                         result = GameIsDrawn; break;
7464                     case EP_CHECKMATE:
7465                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7466                     case EP_WINS:
7467                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7468                     default:
7469                         result = EndOfFile;
7470                 }
7471                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7472                     if(engineOpponent)
7473                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7474                     GameEnds( result, reason, GE_XBOARD );
7475                     return 1;
7476                 }
7477
7478                 /* Next absolutely insufficient mating material. */
7479                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7480                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7481                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7482
7483                      /* always flag draws, for judging claims */
7484                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7485
7486                      if(canAdjudicate && appData.materialDraws) {
7487                          /* but only adjudicate them if adjudication enabled */
7488                          if(engineOpponent) {
7489                            SendToProgram("force\n", engineOpponent); // suppress reply
7490                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7491                          }
7492                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7493                          return 1;
7494                      }
7495                 }
7496
7497                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7498                 if(gameInfo.variant == VariantXiangqi ?
7499                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7500                  : nrW + nrB == 4 &&
7501                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7502                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7503                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7504                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7505                    ) ) {
7506                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7507                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7508                           if(engineOpponent) {
7509                             SendToProgram("force\n", engineOpponent); // suppress reply
7510                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7511                           }
7512                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7513                           return 1;
7514                      }
7515                 } else moveCount = 6;
7516             }
7517         if (appData.debugMode) { int i;
7518             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7519                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7520                     appData.drawRepeats);
7521             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7522               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7523
7524         }
7525
7526         // Repetition draws and 50-move rule can be applied independently of legality testing
7527
7528                 /* Check for rep-draws */
7529                 count = 0;
7530                 for(k = forwardMostMove-2;
7531                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7532                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7533                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7534                     k-=2)
7535                 {   int rights=0;
7536                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7537                         /* compare castling rights */
7538                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7539                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7540                                 rights++; /* King lost rights, while rook still had them */
7541                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7542                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7543                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7544                                    rights++; /* but at least one rook lost them */
7545                         }
7546                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7547                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7548                                 rights++;
7549                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7550                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7551                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7552                                    rights++;
7553                         }
7554                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7555                             && appData.drawRepeats > 1) {
7556                              /* adjudicate after user-specified nr of repeats */
7557                              int result = GameIsDrawn;
7558                              char *details = "XBoard adjudication: repetition draw";
7559                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7560                                 // [HGM] xiangqi: check for forbidden perpetuals
7561                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7562                                 for(m=forwardMostMove; m>k; m-=2) {
7563                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7564                                         ourPerpetual = 0; // the current mover did not always check
7565                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7566                                         hisPerpetual = 0; // the opponent did not always check
7567                                 }
7568                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7569                                                                         ourPerpetual, hisPerpetual);
7570                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7571                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7572                                     details = "Xboard adjudication: perpetual checking";
7573                                 } else
7574                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7575                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7576                                 } else
7577                                 // Now check for perpetual chases
7578                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7579                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7580                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7581                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7582                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7583                                         details = "Xboard adjudication: perpetual chasing";
7584                                     } else
7585                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7586                                         break; // Abort repetition-checking loop.
7587                                 }
7588                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7589                              }
7590                              if(engineOpponent) {
7591                                SendToProgram("force\n", engineOpponent); // suppress reply
7592                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7593                              }
7594                              GameEnds( result, details, GE_XBOARD );
7595                              return 1;
7596                         }
7597                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7598                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7599                     }
7600                 }
7601
7602                 /* Now we test for 50-move draws. Determine ply count */
7603                 count = forwardMostMove;
7604                 /* look for last irreversble move */
7605                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7606                     count--;
7607                 /* if we hit starting position, add initial plies */
7608                 if( count == backwardMostMove )
7609                     count -= initialRulePlies;
7610                 count = forwardMostMove - count;
7611                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7612                         // adjust reversible move counter for checks in Xiangqi
7613                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7614                         if(i < backwardMostMove) i = backwardMostMove;
7615                         while(i <= forwardMostMove) {
7616                                 lastCheck = inCheck; // check evasion does not count
7617                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7618                                 if(inCheck || lastCheck) count--; // check does not count
7619                                 i++;
7620                         }
7621                 }
7622                 if( count >= 100)
7623                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7624                          /* this is used to judge if draw claims are legal */
7625                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7626                          if(engineOpponent) {
7627                            SendToProgram("force\n", engineOpponent); // suppress reply
7628                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7629                          }
7630                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7631                          return 1;
7632                 }
7633
7634                 /* if draw offer is pending, treat it as a draw claim
7635                  * when draw condition present, to allow engines a way to
7636                  * claim draws before making their move to avoid a race
7637                  * condition occurring after their move
7638                  */
7639                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7640                          char *p = NULL;
7641                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7642                              p = "Draw claim: 50-move rule";
7643                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7644                              p = "Draw claim: 3-fold repetition";
7645                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7646                              p = "Draw claim: insufficient mating material";
7647                          if( p != NULL && canAdjudicate) {
7648                              if(engineOpponent) {
7649                                SendToProgram("force\n", engineOpponent); // suppress reply
7650                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7651                              }
7652                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7653                              return 1;
7654                          }
7655                 }
7656
7657                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7658                     if(engineOpponent) {
7659                       SendToProgram("force\n", engineOpponent); // suppress reply
7660                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7661                     }
7662                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7663                     return 1;
7664                 }
7665         return 0;
7666 }
7667
7668 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7669 {   // [HGM] book: this routine intercepts moves to simulate book replies
7670     char *bookHit = NULL;
7671
7672     //first determine if the incoming move brings opponent into his book
7673     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7674         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7675     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7676     if(bookHit != NULL && !cps->bookSuspend) {
7677         // make sure opponent is not going to reply after receiving move to book position
7678         SendToProgram("force\n", cps);
7679         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7680     }
7681     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7682     // now arrange restart after book miss
7683     if(bookHit) {
7684         // after a book hit we never send 'go', and the code after the call to this routine
7685         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7686         char buf[MSG_SIZ], *move = bookHit;
7687         if(cps->useSAN) {
7688             int fromX, fromY, toX, toY;
7689             char promoChar;
7690             ChessMove moveType;
7691             move = buf + 30;
7692             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7693                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7694                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7695                                     PosFlags(forwardMostMove),
7696                                     fromY, fromX, toY, toX, promoChar, move);
7697             } else {
7698                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7699                 bookHit = NULL;
7700             }
7701         }
7702         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7703         SendToProgram(buf, cps);
7704         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7705     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7706         SendToProgram("go\n", cps);
7707         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7708     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7709         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7710             SendToProgram("go\n", cps);
7711         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7712     }
7713     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7714 }
7715
7716 char *savedMessage;
7717 ChessProgramState *savedState;
7718 void DeferredBookMove(void)
7719 {
7720         if(savedState->lastPing != savedState->lastPong)
7721                     ScheduleDelayedEvent(DeferredBookMove, 10);
7722         else
7723         HandleMachineMove(savedMessage, savedState);
7724 }
7725
7726 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7727
7728 void
7729 HandleMachineMove(message, cps)
7730      char *message;
7731      ChessProgramState *cps;
7732 {
7733     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7734     char realname[MSG_SIZ];
7735     int fromX, fromY, toX, toY;
7736     ChessMove moveType;
7737     char promoChar;
7738     char *p, *pv=buf1;
7739     int machineWhite;
7740     char *bookHit;
7741
7742     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7743         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7744         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7745             DisplayError(_("Invalid pairing from pairing engine"), 0);
7746             return;
7747         }
7748         pairingReceived = 1;
7749         NextMatchGame();
7750         return; // Skim the pairing messages here.
7751     }
7752
7753     cps->userError = 0;
7754
7755 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7756     /*
7757      * Kludge to ignore BEL characters
7758      */
7759     while (*message == '\007') message++;
7760
7761     /*
7762      * [HGM] engine debug message: ignore lines starting with '#' character
7763      */
7764     if(cps->debug && *message == '#') return;
7765
7766     /*
7767      * Look for book output
7768      */
7769     if (cps == &first && bookRequested) {
7770         if (message[0] == '\t' || message[0] == ' ') {
7771             /* Part of the book output is here; append it */
7772             strcat(bookOutput, message);
7773             strcat(bookOutput, "  \n");
7774             return;
7775         } else if (bookOutput[0] != NULLCHAR) {
7776             /* All of book output has arrived; display it */
7777             char *p = bookOutput;
7778             while (*p != NULLCHAR) {
7779                 if (*p == '\t') *p = ' ';
7780                 p++;
7781             }
7782             DisplayInformation(bookOutput);
7783             bookRequested = FALSE;
7784             /* Fall through to parse the current output */
7785         }
7786     }
7787
7788     /*
7789      * Look for machine move.
7790      */
7791     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7792         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7793     {
7794         /* This method is only useful on engines that support ping */
7795         if (cps->lastPing != cps->lastPong) {
7796           if (gameMode == BeginningOfGame) {
7797             /* Extra move from before last new; ignore */
7798             if (appData.debugMode) {
7799                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7800             }
7801           } else {
7802             if (appData.debugMode) {
7803                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7804                         cps->which, gameMode);
7805             }
7806
7807             SendToProgram("undo\n", cps);
7808           }
7809           return;
7810         }
7811
7812         switch (gameMode) {
7813           case BeginningOfGame:
7814             /* Extra move from before last reset; ignore */
7815             if (appData.debugMode) {
7816                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7817             }
7818             return;
7819
7820           case EndOfGame:
7821           case IcsIdle:
7822           default:
7823             /* Extra move after we tried to stop.  The mode test is
7824                not a reliable way of detecting this problem, but it's
7825                the best we can do on engines that don't support ping.
7826             */
7827             if (appData.debugMode) {
7828                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7829                         cps->which, gameMode);
7830             }
7831             SendToProgram("undo\n", cps);
7832             return;
7833
7834           case MachinePlaysWhite:
7835           case IcsPlayingWhite:
7836             machineWhite = TRUE;
7837             break;
7838
7839           case MachinePlaysBlack:
7840           case IcsPlayingBlack:
7841             machineWhite = FALSE;
7842             break;
7843
7844           case TwoMachinesPlay:
7845             machineWhite = (cps->twoMachinesColor[0] == 'w');
7846             break;
7847         }
7848         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7849             if (appData.debugMode) {
7850                 fprintf(debugFP,
7851                         "Ignoring move out of turn by %s, gameMode %d"
7852                         ", forwardMost %d\n",
7853                         cps->which, gameMode, forwardMostMove);
7854             }
7855             return;
7856         }
7857
7858     if (appData.debugMode) { int f = forwardMostMove;
7859         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7860                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7861                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7862     }
7863         if(cps->alphaRank) AlphaRank(machineMove, 4);
7864         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7865                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7866             /* Machine move could not be parsed; ignore it. */
7867           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7868                     machineMove, _(cps->which));
7869             DisplayError(buf1, 0);
7870             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7871                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7872             if (gameMode == TwoMachinesPlay) {
7873               GameEnds(machineWhite ? BlackWins : WhiteWins,
7874                        buf1, GE_XBOARD);
7875             }
7876             return;
7877         }
7878
7879         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7880         /* So we have to redo legality test with true e.p. status here,  */
7881         /* to make sure an illegal e.p. capture does not slip through,   */
7882         /* to cause a forfeit on a justified illegal-move complaint      */
7883         /* of the opponent.                                              */
7884         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7885            ChessMove moveType;
7886            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7887                              fromY, fromX, toY, toX, promoChar);
7888             if (appData.debugMode) {
7889                 int i;
7890                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7891                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7892                 fprintf(debugFP, "castling rights\n");
7893             }
7894             if(moveType == IllegalMove) {
7895               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7896                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7897                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7898                            buf1, GE_XBOARD);
7899                 return;
7900            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7901            /* [HGM] Kludge to handle engines that send FRC-style castling
7902               when they shouldn't (like TSCP-Gothic) */
7903            switch(moveType) {
7904              case WhiteASideCastleFR:
7905              case BlackASideCastleFR:
7906                toX+=2;
7907                currentMoveString[2]++;
7908                break;
7909              case WhiteHSideCastleFR:
7910              case BlackHSideCastleFR:
7911                toX--;
7912                currentMoveString[2]--;
7913                break;
7914              default: ; // nothing to do, but suppresses warning of pedantic compilers
7915            }
7916         }
7917         hintRequested = FALSE;
7918         lastHint[0] = NULLCHAR;
7919         bookRequested = FALSE;
7920         /* Program may be pondering now */
7921         cps->maybeThinking = TRUE;
7922         if (cps->sendTime == 2) cps->sendTime = 1;
7923         if (cps->offeredDraw) cps->offeredDraw--;
7924
7925         /* [AS] Save move info*/
7926         pvInfoList[ forwardMostMove ].score = programStats.score;
7927         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7928         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7929
7930         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7931
7932         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7933         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7934             int count = 0;
7935
7936             while( count < adjudicateLossPlies ) {
7937                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7938
7939                 if( count & 1 ) {
7940                     score = -score; /* Flip score for winning side */
7941                 }
7942
7943                 if( score > adjudicateLossThreshold ) {
7944                     break;
7945                 }
7946
7947                 count++;
7948             }
7949
7950             if( count >= adjudicateLossPlies ) {
7951                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7952
7953                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7954                     "Xboard adjudication",
7955                     GE_XBOARD );
7956
7957                 return;
7958             }
7959         }
7960
7961         if(Adjudicate(cps)) {
7962             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7963             return; // [HGM] adjudicate: for all automatic game ends
7964         }
7965
7966 #if ZIPPY
7967         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7968             first.initDone) {
7969           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7970                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7971                 SendToICS("draw ");
7972                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7973           }
7974           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7975           ics_user_moved = 1;
7976           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7977                 char buf[3*MSG_SIZ];
7978
7979                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7980                         programStats.score / 100.,
7981                         programStats.depth,
7982                         programStats.time / 100.,
7983                         (unsigned int)programStats.nodes,
7984                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7985                         programStats.movelist);
7986                 SendToICS(buf);
7987 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7988           }
7989         }
7990 #endif
7991
7992         /* [AS] Clear stats for next move */
7993         ClearProgramStats();
7994         thinkOutput[0] = NULLCHAR;
7995         hiddenThinkOutputState = 0;
7996
7997         bookHit = NULL;
7998         if (gameMode == TwoMachinesPlay) {
7999             /* [HGM] relaying draw offers moved to after reception of move */
8000             /* and interpreting offer as claim if it brings draw condition */
8001             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8002                 SendToProgram("draw\n", cps->other);
8003             }
8004             if (cps->other->sendTime) {
8005                 SendTimeRemaining(cps->other,
8006                                   cps->other->twoMachinesColor[0] == 'w');
8007             }
8008             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8009             if (firstMove && !bookHit) {
8010                 firstMove = FALSE;
8011                 if (cps->other->useColors) {
8012                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8013                 }
8014                 SendToProgram("go\n", cps->other);
8015             }
8016             cps->other->maybeThinking = TRUE;
8017         }
8018
8019         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8020
8021         if (!pausing && appData.ringBellAfterMoves) {
8022             RingBell();
8023         }
8024
8025         /*
8026          * Reenable menu items that were disabled while
8027          * machine was thinking
8028          */
8029         if (gameMode != TwoMachinesPlay)
8030             SetUserThinkingEnables();
8031
8032         // [HGM] book: after book hit opponent has received move and is now in force mode
8033         // force the book reply into it, and then fake that it outputted this move by jumping
8034         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8035         if(bookHit) {
8036                 static char bookMove[MSG_SIZ]; // a bit generous?
8037
8038                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8039                 strcat(bookMove, bookHit);
8040                 message = bookMove;
8041                 cps = cps->other;
8042                 programStats.nodes = programStats.depth = programStats.time =
8043                 programStats.score = programStats.got_only_move = 0;
8044                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8045
8046                 if(cps->lastPing != cps->lastPong) {
8047                     savedMessage = message; // args for deferred call
8048                     savedState = cps;
8049                     ScheduleDelayedEvent(DeferredBookMove, 10);
8050                     return;
8051                 }
8052                 goto FakeBookMove;
8053         }
8054
8055         return;
8056     }
8057
8058     /* Set special modes for chess engines.  Later something general
8059      *  could be added here; for now there is just one kludge feature,
8060      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8061      *  when "xboard" is given as an interactive command.
8062      */
8063     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8064         cps->useSigint = FALSE;
8065         cps->useSigterm = FALSE;
8066     }
8067     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8068       ParseFeatures(message+8, cps);
8069       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8070     }
8071
8072     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8073       int dummy, s=6; char buf[MSG_SIZ];
8074       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8075       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8076       ParseFEN(boards[0], &dummy, message+s);
8077       DrawPosition(TRUE, boards[0]);
8078       startedFromSetupPosition = TRUE;
8079       return;
8080     }
8081     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8082      * want this, I was asked to put it in, and obliged.
8083      */
8084     if (!strncmp(message, "setboard ", 9)) {
8085         Board initial_position;
8086
8087         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8088
8089         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8090             DisplayError(_("Bad FEN received from engine"), 0);
8091             return ;
8092         } else {
8093            Reset(TRUE, FALSE);
8094            CopyBoard(boards[0], initial_position);
8095            initialRulePlies = FENrulePlies;
8096            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8097            else gameMode = MachinePlaysBlack;
8098            DrawPosition(FALSE, boards[currentMove]);
8099         }
8100         return;
8101     }
8102
8103     /*
8104      * Look for communication commands
8105      */
8106     if (!strncmp(message, "telluser ", 9)) {
8107         if(message[9] == '\\' && message[10] == '\\')
8108             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8109         PlayTellSound();
8110         DisplayNote(message + 9);
8111         return;
8112     }
8113     if (!strncmp(message, "tellusererror ", 14)) {
8114         cps->userError = 1;
8115         if(message[14] == '\\' && message[15] == '\\')
8116             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8117         PlayTellSound();
8118         DisplayError(message + 14, 0);
8119         return;
8120     }
8121     if (!strncmp(message, "tellopponent ", 13)) {
8122       if (appData.icsActive) {
8123         if (loggedOn) {
8124           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8125           SendToICS(buf1);
8126         }
8127       } else {
8128         DisplayNote(message + 13);
8129       }
8130       return;
8131     }
8132     if (!strncmp(message, "tellothers ", 11)) {
8133       if (appData.icsActive) {
8134         if (loggedOn) {
8135           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8136           SendToICS(buf1);
8137         }
8138       }
8139       return;
8140     }
8141     if (!strncmp(message, "tellall ", 8)) {
8142       if (appData.icsActive) {
8143         if (loggedOn) {
8144           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8145           SendToICS(buf1);
8146         }
8147       } else {
8148         DisplayNote(message + 8);
8149       }
8150       return;
8151     }
8152     if (strncmp(message, "warning", 7) == 0) {
8153         /* Undocumented feature, use tellusererror in new code */
8154         DisplayError(message, 0);
8155         return;
8156     }
8157     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8158         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8159         strcat(realname, " query");
8160         AskQuestion(realname, buf2, buf1, cps->pr);
8161         return;
8162     }
8163     /* Commands from the engine directly to ICS.  We don't allow these to be
8164      *  sent until we are logged on. Crafty kibitzes have been known to
8165      *  interfere with the login process.
8166      */
8167     if (loggedOn) {
8168         if (!strncmp(message, "tellics ", 8)) {
8169             SendToICS(message + 8);
8170             SendToICS("\n");
8171             return;
8172         }
8173         if (!strncmp(message, "tellicsnoalias ", 15)) {
8174             SendToICS(ics_prefix);
8175             SendToICS(message + 15);
8176             SendToICS("\n");
8177             return;
8178         }
8179         /* The following are for backward compatibility only */
8180         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8181             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8182             SendToICS(ics_prefix);
8183             SendToICS(message);
8184             SendToICS("\n");
8185             return;
8186         }
8187     }
8188     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8189         return;
8190     }
8191     /*
8192      * If the move is illegal, cancel it and redraw the board.
8193      * Also deal with other error cases.  Matching is rather loose
8194      * here to accommodate engines written before the spec.
8195      */
8196     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8197         strncmp(message, "Error", 5) == 0) {
8198         if (StrStr(message, "name") ||
8199             StrStr(message, "rating") || StrStr(message, "?") ||
8200             StrStr(message, "result") || StrStr(message, "board") ||
8201             StrStr(message, "bk") || StrStr(message, "computer") ||
8202             StrStr(message, "variant") || StrStr(message, "hint") ||
8203             StrStr(message, "random") || StrStr(message, "depth") ||
8204             StrStr(message, "accepted")) {
8205             return;
8206         }
8207         if (StrStr(message, "protover")) {
8208           /* Program is responding to input, so it's apparently done
8209              initializing, and this error message indicates it is
8210              protocol version 1.  So we don't need to wait any longer
8211              for it to initialize and send feature commands. */
8212           FeatureDone(cps, 1);
8213           cps->protocolVersion = 1;
8214           return;
8215         }
8216         cps->maybeThinking = FALSE;
8217
8218         if (StrStr(message, "draw")) {
8219             /* Program doesn't have "draw" command */
8220             cps->sendDrawOffers = 0;
8221             return;
8222         }
8223         if (cps->sendTime != 1 &&
8224             (StrStr(message, "time") || StrStr(message, "otim"))) {
8225           /* Program apparently doesn't have "time" or "otim" command */
8226           cps->sendTime = 0;
8227           return;
8228         }
8229         if (StrStr(message, "analyze")) {
8230             cps->analysisSupport = FALSE;
8231             cps->analyzing = FALSE;
8232             Reset(FALSE, TRUE);
8233             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8234             DisplayError(buf2, 0);
8235             return;
8236         }
8237         if (StrStr(message, "(no matching move)st")) {
8238           /* Special kludge for GNU Chess 4 only */
8239           cps->stKludge = TRUE;
8240           SendTimeControl(cps, movesPerSession, timeControl,
8241                           timeIncrement, appData.searchDepth,
8242                           searchTime);
8243           return;
8244         }
8245         if (StrStr(message, "(no matching move)sd")) {
8246           /* Special kludge for GNU Chess 4 only */
8247           cps->sdKludge = TRUE;
8248           SendTimeControl(cps, movesPerSession, timeControl,
8249                           timeIncrement, appData.searchDepth,
8250                           searchTime);
8251           return;
8252         }
8253         if (!StrStr(message, "llegal")) {
8254             return;
8255         }
8256         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8257             gameMode == IcsIdle) return;
8258         if (forwardMostMove <= backwardMostMove) return;
8259         if (pausing) PauseEvent();
8260       if(appData.forceIllegal) {
8261             // [HGM] illegal: machine refused move; force position after move into it
8262           SendToProgram("force\n", cps);
8263           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8264                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8265                 // when black is to move, while there might be nothing on a2 or black
8266                 // might already have the move. So send the board as if white has the move.
8267                 // But first we must change the stm of the engine, as it refused the last move
8268                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8269                 if(WhiteOnMove(forwardMostMove)) {
8270                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8271                     SendBoard(cps, forwardMostMove); // kludgeless board
8272                 } else {
8273                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8274                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8275                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8276                 }
8277           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8278             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8279                  gameMode == TwoMachinesPlay)
8280               SendToProgram("go\n", cps);
8281             return;
8282       } else
8283         if (gameMode == PlayFromGameFile) {
8284             /* Stop reading this game file */
8285             gameMode = EditGame;
8286             ModeHighlight();
8287         }
8288         /* [HGM] illegal-move claim should forfeit game when Xboard */
8289         /* only passes fully legal moves                            */
8290         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8291             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8292                                 "False illegal-move claim", GE_XBOARD );
8293             return; // do not take back move we tested as valid
8294         }
8295         currentMove = forwardMostMove-1;
8296         DisplayMove(currentMove-1); /* before DisplayMoveError */
8297         SwitchClocks(forwardMostMove-1); // [HGM] race
8298         DisplayBothClocks();
8299         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8300                 parseList[currentMove], _(cps->which));
8301         DisplayMoveError(buf1);
8302         DrawPosition(FALSE, boards[currentMove]);
8303         return;
8304     }
8305     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8306         /* Program has a broken "time" command that
8307            outputs a string not ending in newline.
8308            Don't use it. */
8309         cps->sendTime = 0;
8310     }
8311
8312     /*
8313      * If chess program startup fails, exit with an error message.
8314      * Attempts to recover here are futile.
8315      */
8316     if ((StrStr(message, "unknown host") != NULL)
8317         || (StrStr(message, "No remote directory") != NULL)
8318         || (StrStr(message, "not found") != NULL)
8319         || (StrStr(message, "No such file") != NULL)
8320         || (StrStr(message, "can't alloc") != NULL)
8321         || (StrStr(message, "Permission denied") != NULL)) {
8322
8323         cps->maybeThinking = FALSE;
8324         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8325                 _(cps->which), cps->program, cps->host, message);
8326         RemoveInputSource(cps->isr);
8327         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8328             if(cps == &first) appData.noChessProgram = TRUE;
8329             DisplayError(buf1, 0);
8330         }
8331         return;
8332     }
8333
8334     /*
8335      * Look for hint output
8336      */
8337     if (sscanf(message, "Hint: %s", buf1) == 1) {
8338         if (cps == &first && hintRequested) {
8339             hintRequested = FALSE;
8340             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8341                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8342                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8343                                     PosFlags(forwardMostMove),
8344                                     fromY, fromX, toY, toX, promoChar, buf1);
8345                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8346                 DisplayInformation(buf2);
8347             } else {
8348                 /* Hint move could not be parsed!? */
8349               snprintf(buf2, sizeof(buf2),
8350                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8351                         buf1, _(cps->which));
8352                 DisplayError(buf2, 0);
8353             }
8354         } else {
8355           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8356         }
8357         return;
8358     }
8359
8360     /*
8361      * Ignore other messages if game is not in progress
8362      */
8363     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8364         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8365
8366     /*
8367      * look for win, lose, draw, or draw offer
8368      */
8369     if (strncmp(message, "1-0", 3) == 0) {
8370         char *p, *q, *r = "";
8371         p = strchr(message, '{');
8372         if (p) {
8373             q = strchr(p, '}');
8374             if (q) {
8375                 *q = NULLCHAR;
8376                 r = p + 1;
8377             }
8378         }
8379         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8380         return;
8381     } else if (strncmp(message, "0-1", 3) == 0) {
8382         char *p, *q, *r = "";
8383         p = strchr(message, '{');
8384         if (p) {
8385             q = strchr(p, '}');
8386             if (q) {
8387                 *q = NULLCHAR;
8388                 r = p + 1;
8389             }
8390         }
8391         /* Kludge for Arasan 4.1 bug */
8392         if (strcmp(r, "Black resigns") == 0) {
8393             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8394             return;
8395         }
8396         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8397         return;
8398     } else if (strncmp(message, "1/2", 3) == 0) {
8399         char *p, *q, *r = "";
8400         p = strchr(message, '{');
8401         if (p) {
8402             q = strchr(p, '}');
8403             if (q) {
8404                 *q = NULLCHAR;
8405                 r = p + 1;
8406             }
8407         }
8408
8409         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8410         return;
8411
8412     } else if (strncmp(message, "White resign", 12) == 0) {
8413         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8414         return;
8415     } else if (strncmp(message, "Black resign", 12) == 0) {
8416         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8417         return;
8418     } else if (strncmp(message, "White matches", 13) == 0 ||
8419                strncmp(message, "Black matches", 13) == 0   ) {
8420         /* [HGM] ignore GNUShogi noises */
8421         return;
8422     } else if (strncmp(message, "White", 5) == 0 &&
8423                message[5] != '(' &&
8424                StrStr(message, "Black") == NULL) {
8425         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8426         return;
8427     } else if (strncmp(message, "Black", 5) == 0 &&
8428                message[5] != '(') {
8429         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8430         return;
8431     } else if (strcmp(message, "resign") == 0 ||
8432                strcmp(message, "computer resigns") == 0) {
8433         switch (gameMode) {
8434           case MachinePlaysBlack:
8435           case IcsPlayingBlack:
8436             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8437             break;
8438           case MachinePlaysWhite:
8439           case IcsPlayingWhite:
8440             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8441             break;
8442           case TwoMachinesPlay:
8443             if (cps->twoMachinesColor[0] == 'w')
8444               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8445             else
8446               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8447             break;
8448           default:
8449             /* can't happen */
8450             break;
8451         }
8452         return;
8453     } else if (strncmp(message, "opponent mates", 14) == 0) {
8454         switch (gameMode) {
8455           case MachinePlaysBlack:
8456           case IcsPlayingBlack:
8457             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8458             break;
8459           case MachinePlaysWhite:
8460           case IcsPlayingWhite:
8461             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8462             break;
8463           case TwoMachinesPlay:
8464             if (cps->twoMachinesColor[0] == 'w')
8465               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8466             else
8467               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8468             break;
8469           default:
8470             /* can't happen */
8471             break;
8472         }
8473         return;
8474     } else if (strncmp(message, "computer mates", 14) == 0) {
8475         switch (gameMode) {
8476           case MachinePlaysBlack:
8477           case IcsPlayingBlack:
8478             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8479             break;
8480           case MachinePlaysWhite:
8481           case IcsPlayingWhite:
8482             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8483             break;
8484           case TwoMachinesPlay:
8485             if (cps->twoMachinesColor[0] == 'w')
8486               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8487             else
8488               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8489             break;
8490           default:
8491             /* can't happen */
8492             break;
8493         }
8494         return;
8495     } else if (strncmp(message, "checkmate", 9) == 0) {
8496         if (WhiteOnMove(forwardMostMove)) {
8497             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8498         } else {
8499             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8500         }
8501         return;
8502     } else if (strstr(message, "Draw") != NULL ||
8503                strstr(message, "game is a draw") != NULL) {
8504         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8505         return;
8506     } else if (strstr(message, "offer") != NULL &&
8507                strstr(message, "draw") != NULL) {
8508 #if ZIPPY
8509         if (appData.zippyPlay && first.initDone) {
8510             /* Relay offer to ICS */
8511             SendToICS(ics_prefix);
8512             SendToICS("draw\n");
8513         }
8514 #endif
8515         cps->offeredDraw = 2; /* valid until this engine moves twice */
8516         if (gameMode == TwoMachinesPlay) {
8517             if (cps->other->offeredDraw) {
8518                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8519             /* [HGM] in two-machine mode we delay relaying draw offer      */
8520             /* until after we also have move, to see if it is really claim */
8521             }
8522         } else if (gameMode == MachinePlaysWhite ||
8523                    gameMode == MachinePlaysBlack) {
8524           if (userOfferedDraw) {
8525             DisplayInformation(_("Machine accepts your draw offer"));
8526             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8527           } else {
8528             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8529           }
8530         }
8531     }
8532
8533
8534     /*
8535      * Look for thinking output
8536      */
8537     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8538           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8539                                 ) {
8540         int plylev, mvleft, mvtot, curscore, time;
8541         char mvname[MOVE_LEN];
8542         u64 nodes; // [DM]
8543         char plyext;
8544         int ignore = FALSE;
8545         int prefixHint = FALSE;
8546         mvname[0] = NULLCHAR;
8547
8548         switch (gameMode) {
8549           case MachinePlaysBlack:
8550           case IcsPlayingBlack:
8551             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8552             break;
8553           case MachinePlaysWhite:
8554           case IcsPlayingWhite:
8555             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8556             break;
8557           case AnalyzeMode:
8558           case AnalyzeFile:
8559             break;
8560           case IcsObserving: /* [DM] icsEngineAnalyze */
8561             if (!appData.icsEngineAnalyze) ignore = TRUE;
8562             break;
8563           case TwoMachinesPlay:
8564             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8565                 ignore = TRUE;
8566             }
8567             break;
8568           default:
8569             ignore = TRUE;
8570             break;
8571         }
8572
8573         if (!ignore) {
8574             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8575             buf1[0] = NULLCHAR;
8576             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8577                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8578
8579                 if (plyext != ' ' && plyext != '\t') {
8580                     time *= 100;
8581                 }
8582
8583                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8584                 if( cps->scoreIsAbsolute &&
8585                     ( gameMode == MachinePlaysBlack ||
8586                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8587                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8588                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8589                      !WhiteOnMove(currentMove)
8590                     ) )
8591                 {
8592                     curscore = -curscore;
8593                 }
8594
8595                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8596
8597                 tempStats.depth = plylev;
8598                 tempStats.nodes = nodes;
8599                 tempStats.time = time;
8600                 tempStats.score = curscore;
8601                 tempStats.got_only_move = 0;
8602
8603                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8604                         int ticklen;
8605
8606                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8607                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8608                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8609                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8610                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8611                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8612                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8613                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8614                 }
8615
8616                 /* Buffer overflow protection */
8617                 if (pv[0] != NULLCHAR) {
8618                     if (strlen(pv) >= sizeof(tempStats.movelist)
8619                         && appData.debugMode) {
8620                         fprintf(debugFP,
8621                                 "PV is too long; using the first %u bytes.\n",
8622                                 (unsigned) sizeof(tempStats.movelist) - 1);
8623                     }
8624
8625                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8626                 } else {
8627                     sprintf(tempStats.movelist, " no PV\n");
8628                 }
8629
8630                 if (tempStats.seen_stat) {
8631                     tempStats.ok_to_send = 1;
8632                 }
8633
8634                 if (strchr(tempStats.movelist, '(') != NULL) {
8635                     tempStats.line_is_book = 1;
8636                     tempStats.nr_moves = 0;
8637                     tempStats.moves_left = 0;
8638                 } else {
8639                     tempStats.line_is_book = 0;
8640                 }
8641
8642                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8643                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8644
8645                 SendProgramStatsToFrontend( cps, &tempStats );
8646
8647                 /*
8648                     [AS] Protect the thinkOutput buffer from overflow... this
8649                     is only useful if buf1 hasn't overflowed first!
8650                 */
8651                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8652                          plylev,
8653                          (gameMode == TwoMachinesPlay ?
8654                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8655                          ((double) curscore) / 100.0,
8656                          prefixHint ? lastHint : "",
8657                          prefixHint ? " " : "" );
8658
8659                 if( buf1[0] != NULLCHAR ) {
8660                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8661
8662                     if( strlen(pv) > max_len ) {
8663                         if( appData.debugMode) {
8664                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8665                         }
8666                         pv[max_len+1] = '\0';
8667                     }
8668
8669                     strcat( thinkOutput, pv);
8670                 }
8671
8672                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8673                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8674                     DisplayMove(currentMove - 1);
8675                 }
8676                 return;
8677
8678             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8679                 /* crafty (9.25+) says "(only move) <move>"
8680                  * if there is only 1 legal move
8681                  */
8682                 sscanf(p, "(only move) %s", buf1);
8683                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8684                 sprintf(programStats.movelist, "%s (only move)", buf1);
8685                 programStats.depth = 1;
8686                 programStats.nr_moves = 1;
8687                 programStats.moves_left = 1;
8688                 programStats.nodes = 1;
8689                 programStats.time = 1;
8690                 programStats.got_only_move = 1;
8691
8692                 /* Not really, but we also use this member to
8693                    mean "line isn't going to change" (Crafty
8694                    isn't searching, so stats won't change) */
8695                 programStats.line_is_book = 1;
8696
8697                 SendProgramStatsToFrontend( cps, &programStats );
8698
8699                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8700                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8701                     DisplayMove(currentMove - 1);
8702                 }
8703                 return;
8704             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8705                               &time, &nodes, &plylev, &mvleft,
8706                               &mvtot, mvname) >= 5) {
8707                 /* The stat01: line is from Crafty (9.29+) in response
8708                    to the "." command */
8709                 programStats.seen_stat = 1;
8710                 cps->maybeThinking = TRUE;
8711
8712                 if (programStats.got_only_move || !appData.periodicUpdates)
8713                   return;
8714
8715                 programStats.depth = plylev;
8716                 programStats.time = time;
8717                 programStats.nodes = nodes;
8718                 programStats.moves_left = mvleft;
8719                 programStats.nr_moves = mvtot;
8720                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8721                 programStats.ok_to_send = 1;
8722                 programStats.movelist[0] = '\0';
8723
8724                 SendProgramStatsToFrontend( cps, &programStats );
8725
8726                 return;
8727
8728             } else if (strncmp(message,"++",2) == 0) {
8729                 /* Crafty 9.29+ outputs this */
8730                 programStats.got_fail = 2;
8731                 return;
8732
8733             } else if (strncmp(message,"--",2) == 0) {
8734                 /* Crafty 9.29+ outputs this */
8735                 programStats.got_fail = 1;
8736                 return;
8737
8738             } else if (thinkOutput[0] != NULLCHAR &&
8739                        strncmp(message, "    ", 4) == 0) {
8740                 unsigned message_len;
8741
8742                 p = message;
8743                 while (*p && *p == ' ') p++;
8744
8745                 message_len = strlen( p );
8746
8747                 /* [AS] Avoid buffer overflow */
8748                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8749                     strcat(thinkOutput, " ");
8750                     strcat(thinkOutput, p);
8751                 }
8752
8753                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8754                     strcat(programStats.movelist, " ");
8755                     strcat(programStats.movelist, p);
8756                 }
8757
8758                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8759                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8760                     DisplayMove(currentMove - 1);
8761                 }
8762                 return;
8763             }
8764         }
8765         else {
8766             buf1[0] = NULLCHAR;
8767
8768             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8769                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8770             {
8771                 ChessProgramStats cpstats;
8772
8773                 if (plyext != ' ' && plyext != '\t') {
8774                     time *= 100;
8775                 }
8776
8777                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8778                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8779                     curscore = -curscore;
8780                 }
8781
8782                 cpstats.depth = plylev;
8783                 cpstats.nodes = nodes;
8784                 cpstats.time = time;
8785                 cpstats.score = curscore;
8786                 cpstats.got_only_move = 0;
8787                 cpstats.movelist[0] = '\0';
8788
8789                 if (buf1[0] != NULLCHAR) {
8790                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8791                 }
8792
8793                 cpstats.ok_to_send = 0;
8794                 cpstats.line_is_book = 0;
8795                 cpstats.nr_moves = 0;
8796                 cpstats.moves_left = 0;
8797
8798                 SendProgramStatsToFrontend( cps, &cpstats );
8799             }
8800         }
8801     }
8802 }
8803
8804
8805 /* Parse a game score from the character string "game", and
8806    record it as the history of the current game.  The game
8807    score is NOT assumed to start from the standard position.
8808    The display is not updated in any way.
8809    */
8810 void
8811 ParseGameHistory(game)
8812      char *game;
8813 {
8814     ChessMove moveType;
8815     int fromX, fromY, toX, toY, boardIndex;
8816     char promoChar;
8817     char *p, *q;
8818     char buf[MSG_SIZ];
8819
8820     if (appData.debugMode)
8821       fprintf(debugFP, "Parsing game history: %s\n", game);
8822
8823     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8824     gameInfo.site = StrSave(appData.icsHost);
8825     gameInfo.date = PGNDate();
8826     gameInfo.round = StrSave("-");
8827
8828     /* Parse out names of players */
8829     while (*game == ' ') game++;
8830     p = buf;
8831     while (*game != ' ') *p++ = *game++;
8832     *p = NULLCHAR;
8833     gameInfo.white = StrSave(buf);
8834     while (*game == ' ') game++;
8835     p = buf;
8836     while (*game != ' ' && *game != '\n') *p++ = *game++;
8837     *p = NULLCHAR;
8838     gameInfo.black = StrSave(buf);
8839
8840     /* Parse moves */
8841     boardIndex = blackPlaysFirst ? 1 : 0;
8842     yynewstr(game);
8843     for (;;) {
8844         yyboardindex = boardIndex;
8845         moveType = (ChessMove) Myylex();
8846         switch (moveType) {
8847           case IllegalMove:             /* maybe suicide chess, etc. */
8848   if (appData.debugMode) {
8849     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8850     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8851     setbuf(debugFP, NULL);
8852   }
8853           case WhitePromotion:
8854           case BlackPromotion:
8855           case WhiteNonPromotion:
8856           case BlackNonPromotion:
8857           case NormalMove:
8858           case WhiteCapturesEnPassant:
8859           case BlackCapturesEnPassant:
8860           case WhiteKingSideCastle:
8861           case WhiteQueenSideCastle:
8862           case BlackKingSideCastle:
8863           case BlackQueenSideCastle:
8864           case WhiteKingSideCastleWild:
8865           case WhiteQueenSideCastleWild:
8866           case BlackKingSideCastleWild:
8867           case BlackQueenSideCastleWild:
8868           /* PUSH Fabien */
8869           case WhiteHSideCastleFR:
8870           case WhiteASideCastleFR:
8871           case BlackHSideCastleFR:
8872           case BlackASideCastleFR:
8873           /* POP Fabien */
8874             fromX = currentMoveString[0] - AAA;
8875             fromY = currentMoveString[1] - ONE;
8876             toX = currentMoveString[2] - AAA;
8877             toY = currentMoveString[3] - ONE;
8878             promoChar = currentMoveString[4];
8879             break;
8880           case WhiteDrop:
8881           case BlackDrop:
8882             fromX = moveType == WhiteDrop ?
8883               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8884             (int) CharToPiece(ToLower(currentMoveString[0]));
8885             fromY = DROP_RANK;
8886             toX = currentMoveString[2] - AAA;
8887             toY = currentMoveString[3] - ONE;
8888             promoChar = NULLCHAR;
8889             break;
8890           case AmbiguousMove:
8891             /* bug? */
8892             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8893   if (appData.debugMode) {
8894     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8895     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8896     setbuf(debugFP, NULL);
8897   }
8898             DisplayError(buf, 0);
8899             return;
8900           case ImpossibleMove:
8901             /* bug? */
8902             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8903   if (appData.debugMode) {
8904     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8905     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8906     setbuf(debugFP, NULL);
8907   }
8908             DisplayError(buf, 0);
8909             return;
8910           case EndOfFile:
8911             if (boardIndex < backwardMostMove) {
8912                 /* Oops, gap.  How did that happen? */
8913                 DisplayError(_("Gap in move list"), 0);
8914                 return;
8915             }
8916             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8917             if (boardIndex > forwardMostMove) {
8918                 forwardMostMove = boardIndex;
8919             }
8920             return;
8921           case ElapsedTime:
8922             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8923                 strcat(parseList[boardIndex-1], " ");
8924                 strcat(parseList[boardIndex-1], yy_text);
8925             }
8926             continue;
8927           case Comment:
8928           case PGNTag:
8929           case NAG:
8930           default:
8931             /* ignore */
8932             continue;
8933           case WhiteWins:
8934           case BlackWins:
8935           case GameIsDrawn:
8936           case GameUnfinished:
8937             if (gameMode == IcsExamining) {
8938                 if (boardIndex < backwardMostMove) {
8939                     /* Oops, gap.  How did that happen? */
8940                     return;
8941                 }
8942                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8943                 return;
8944             }
8945             gameInfo.result = moveType;
8946             p = strchr(yy_text, '{');
8947             if (p == NULL) p = strchr(yy_text, '(');
8948             if (p == NULL) {
8949                 p = yy_text;
8950                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8951             } else {
8952                 q = strchr(p, *p == '{' ? '}' : ')');
8953                 if (q != NULL) *q = NULLCHAR;
8954                 p++;
8955             }
8956             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8957             gameInfo.resultDetails = StrSave(p);
8958             continue;
8959         }
8960         if (boardIndex >= forwardMostMove &&
8961             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8962             backwardMostMove = blackPlaysFirst ? 1 : 0;
8963             return;
8964         }
8965         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8966                                  fromY, fromX, toY, toX, promoChar,
8967                                  parseList[boardIndex]);
8968         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8969         /* currentMoveString is set as a side-effect of yylex */
8970         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8971         strcat(moveList[boardIndex], "\n");
8972         boardIndex++;
8973         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8974         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8975           case MT_NONE:
8976           case MT_STALEMATE:
8977           default:
8978             break;
8979           case MT_CHECK:
8980             if(gameInfo.variant != VariantShogi)
8981                 strcat(parseList[boardIndex - 1], "+");
8982             break;
8983           case MT_CHECKMATE:
8984           case MT_STAINMATE:
8985             strcat(parseList[boardIndex - 1], "#");
8986             break;
8987         }
8988     }
8989 }
8990
8991
8992 /* Apply a move to the given board  */
8993 void
8994 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8995      int fromX, fromY, toX, toY;
8996      int promoChar;
8997      Board board;
8998 {
8999   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9000   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9001
9002     /* [HGM] compute & store e.p. status and castling rights for new position */
9003     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9004
9005       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9006       oldEP = (signed char)board[EP_STATUS];
9007       board[EP_STATUS] = EP_NONE;
9008
9009       if( board[toY][toX] != EmptySquare )
9010            board[EP_STATUS] = EP_CAPTURE;
9011
9012   if (fromY == DROP_RANK) {
9013         /* must be first */
9014         piece = board[toY][toX] = (ChessSquare) fromX;
9015   } else {
9016       int i;
9017
9018       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9019            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9020                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9021       } else
9022       if( board[fromY][fromX] == WhitePawn ) {
9023            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9024                board[EP_STATUS] = EP_PAWN_MOVE;
9025            if( toY-fromY==2) {
9026                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9027                         gameInfo.variant != VariantBerolina || toX < fromX)
9028                       board[EP_STATUS] = toX | berolina;
9029                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9030                         gameInfo.variant != VariantBerolina || toX > fromX)
9031                       board[EP_STATUS] = toX;
9032            }
9033       } else
9034       if( board[fromY][fromX] == BlackPawn ) {
9035            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9036                board[EP_STATUS] = EP_PAWN_MOVE;
9037            if( toY-fromY== -2) {
9038                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9039                         gameInfo.variant != VariantBerolina || toX < fromX)
9040                       board[EP_STATUS] = toX | berolina;
9041                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9042                         gameInfo.variant != VariantBerolina || toX > fromX)
9043                       board[EP_STATUS] = toX;
9044            }
9045        }
9046
9047        for(i=0; i<nrCastlingRights; i++) {
9048            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9049               board[CASTLING][i] == toX   && castlingRank[i] == toY
9050              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9051        }
9052
9053      if (fromX == toX && fromY == toY) return;
9054
9055      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9056      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9057      if(gameInfo.variant == VariantKnightmate)
9058          king += (int) WhiteUnicorn - (int) WhiteKing;
9059
9060     /* Code added by Tord: */
9061     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9062     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9063         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9064       board[fromY][fromX] = EmptySquare;
9065       board[toY][toX] = EmptySquare;
9066       if((toX > fromX) != (piece == WhiteRook)) {
9067         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9068       } else {
9069         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9070       }
9071     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9072                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9073       board[fromY][fromX] = EmptySquare;
9074       board[toY][toX] = EmptySquare;
9075       if((toX > fromX) != (piece == BlackRook)) {
9076         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9077       } else {
9078         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9079       }
9080     /* End of code added by Tord */
9081
9082     } else if (board[fromY][fromX] == king
9083         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9084         && toY == fromY && toX > fromX+1) {
9085         board[fromY][fromX] = EmptySquare;
9086         board[toY][toX] = king;
9087         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9088         board[fromY][BOARD_RGHT-1] = EmptySquare;
9089     } else if (board[fromY][fromX] == king
9090         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9091                && toY == fromY && toX < fromX-1) {
9092         board[fromY][fromX] = EmptySquare;
9093         board[toY][toX] = king;
9094         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9095         board[fromY][BOARD_LEFT] = EmptySquare;
9096     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9097                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9098                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9099                ) {
9100         /* white pawn promotion */
9101         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9102         if(gameInfo.variant==VariantBughouse ||
9103            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9104             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9105         board[fromY][fromX] = EmptySquare;
9106     } else if ((fromY >= BOARD_HEIGHT>>1)
9107                && (toX != fromX)
9108                && gameInfo.variant != VariantXiangqi
9109                && gameInfo.variant != VariantBerolina
9110                && (board[fromY][fromX] == WhitePawn)
9111                && (board[toY][toX] == EmptySquare)) {
9112         board[fromY][fromX] = EmptySquare;
9113         board[toY][toX] = WhitePawn;
9114         captured = board[toY - 1][toX];
9115         board[toY - 1][toX] = EmptySquare;
9116     } else if ((fromY == BOARD_HEIGHT-4)
9117                && (toX == fromX)
9118                && gameInfo.variant == VariantBerolina
9119                && (board[fromY][fromX] == WhitePawn)
9120                && (board[toY][toX] == EmptySquare)) {
9121         board[fromY][fromX] = EmptySquare;
9122         board[toY][toX] = WhitePawn;
9123         if(oldEP & EP_BEROLIN_A) {
9124                 captured = board[fromY][fromX-1];
9125                 board[fromY][fromX-1] = EmptySquare;
9126         }else{  captured = board[fromY][fromX+1];
9127                 board[fromY][fromX+1] = EmptySquare;
9128         }
9129     } else if (board[fromY][fromX] == king
9130         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9131                && toY == fromY && toX > fromX+1) {
9132         board[fromY][fromX] = EmptySquare;
9133         board[toY][toX] = king;
9134         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9135         board[fromY][BOARD_RGHT-1] = EmptySquare;
9136     } else if (board[fromY][fromX] == king
9137         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9138                && toY == fromY && toX < fromX-1) {
9139         board[fromY][fromX] = EmptySquare;
9140         board[toY][toX] = king;
9141         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9142         board[fromY][BOARD_LEFT] = EmptySquare;
9143     } else if (fromY == 7 && fromX == 3
9144                && board[fromY][fromX] == BlackKing
9145                && toY == 7 && toX == 5) {
9146         board[fromY][fromX] = EmptySquare;
9147         board[toY][toX] = BlackKing;
9148         board[fromY][7] = EmptySquare;
9149         board[toY][4] = BlackRook;
9150     } else if (fromY == 7 && fromX == 3
9151                && board[fromY][fromX] == BlackKing
9152                && toY == 7 && toX == 1) {
9153         board[fromY][fromX] = EmptySquare;
9154         board[toY][toX] = BlackKing;
9155         board[fromY][0] = EmptySquare;
9156         board[toY][2] = BlackRook;
9157     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9158                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9159                && toY < promoRank && promoChar
9160                ) {
9161         /* black pawn promotion */
9162         board[toY][toX] = CharToPiece(ToLower(promoChar));
9163         if(gameInfo.variant==VariantBughouse ||
9164            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9165             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9166         board[fromY][fromX] = EmptySquare;
9167     } else if ((fromY < BOARD_HEIGHT>>1)
9168                && (toX != fromX)
9169                && gameInfo.variant != VariantXiangqi
9170                && gameInfo.variant != VariantBerolina
9171                && (board[fromY][fromX] == BlackPawn)
9172                && (board[toY][toX] == EmptySquare)) {
9173         board[fromY][fromX] = EmptySquare;
9174         board[toY][toX] = BlackPawn;
9175         captured = board[toY + 1][toX];
9176         board[toY + 1][toX] = EmptySquare;
9177     } else if ((fromY == 3)
9178                && (toX == fromX)
9179                && gameInfo.variant == VariantBerolina
9180                && (board[fromY][fromX] == BlackPawn)
9181                && (board[toY][toX] == EmptySquare)) {
9182         board[fromY][fromX] = EmptySquare;
9183         board[toY][toX] = BlackPawn;
9184         if(oldEP & EP_BEROLIN_A) {
9185                 captured = board[fromY][fromX-1];
9186                 board[fromY][fromX-1] = EmptySquare;
9187         }else{  captured = board[fromY][fromX+1];
9188                 board[fromY][fromX+1] = EmptySquare;
9189         }
9190     } else {
9191         board[toY][toX] = board[fromY][fromX];
9192         board[fromY][fromX] = EmptySquare;
9193     }
9194   }
9195
9196     if (gameInfo.holdingsWidth != 0) {
9197
9198       /* !!A lot more code needs to be written to support holdings  */
9199       /* [HGM] OK, so I have written it. Holdings are stored in the */
9200       /* penultimate board files, so they are automaticlly stored   */
9201       /* in the game history.                                       */
9202       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9203                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9204         /* Delete from holdings, by decreasing count */
9205         /* and erasing image if necessary            */
9206         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9207         if(p < (int) BlackPawn) { /* white drop */
9208              p -= (int)WhitePawn;
9209                  p = PieceToNumber((ChessSquare)p);
9210              if(p >= gameInfo.holdingsSize) p = 0;
9211              if(--board[p][BOARD_WIDTH-2] <= 0)
9212                   board[p][BOARD_WIDTH-1] = EmptySquare;
9213              if((int)board[p][BOARD_WIDTH-2] < 0)
9214                         board[p][BOARD_WIDTH-2] = 0;
9215         } else {                  /* black drop */
9216              p -= (int)BlackPawn;
9217                  p = PieceToNumber((ChessSquare)p);
9218              if(p >= gameInfo.holdingsSize) p = 0;
9219              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9220                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9221              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9222                         board[BOARD_HEIGHT-1-p][1] = 0;
9223         }
9224       }
9225       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9226           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9227         /* [HGM] holdings: Add to holdings, if holdings exist */
9228         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9229                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9230                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9231         }
9232         p = (int) captured;
9233         if (p >= (int) BlackPawn) {
9234           p -= (int)BlackPawn;
9235           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9236                   /* in Shogi restore piece to its original  first */
9237                   captured = (ChessSquare) (DEMOTED captured);
9238                   p = DEMOTED p;
9239           }
9240           p = PieceToNumber((ChessSquare)p);
9241           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9242           board[p][BOARD_WIDTH-2]++;
9243           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9244         } else {
9245           p -= (int)WhitePawn;
9246           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9247                   captured = (ChessSquare) (DEMOTED captured);
9248                   p = DEMOTED p;
9249           }
9250           p = PieceToNumber((ChessSquare)p);
9251           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9252           board[BOARD_HEIGHT-1-p][1]++;
9253           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9254         }
9255       }
9256     } else if (gameInfo.variant == VariantAtomic) {
9257       if (captured != EmptySquare) {
9258         int y, x;
9259         for (y = toY-1; y <= toY+1; y++) {
9260           for (x = toX-1; x <= toX+1; x++) {
9261             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9262                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9263               board[y][x] = EmptySquare;
9264             }
9265           }
9266         }
9267         board[toY][toX] = EmptySquare;
9268       }
9269     }
9270     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9271         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9272     } else
9273     if(promoChar == '+') {
9274         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9275         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9276     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9277         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9278     }
9279     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9280                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9281         // [HGM] superchess: take promotion piece out of holdings
9282         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9283         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9284             if(!--board[k][BOARD_WIDTH-2])
9285                 board[k][BOARD_WIDTH-1] = EmptySquare;
9286         } else {
9287             if(!--board[BOARD_HEIGHT-1-k][1])
9288                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9289         }
9290     }
9291
9292 }
9293
9294 /* Updates forwardMostMove */
9295 void
9296 MakeMove(fromX, fromY, toX, toY, promoChar)
9297      int fromX, fromY, toX, toY;
9298      int promoChar;
9299 {
9300 //    forwardMostMove++; // [HGM] bare: moved downstream
9301
9302     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9303         int timeLeft; static int lastLoadFlag=0; int king, piece;
9304         piece = boards[forwardMostMove][fromY][fromX];
9305         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9306         if(gameInfo.variant == VariantKnightmate)
9307             king += (int) WhiteUnicorn - (int) WhiteKing;
9308         if(forwardMostMove == 0) {
9309             if(blackPlaysFirst)
9310                 fprintf(serverMoves, "%s;", second.tidy);
9311             fprintf(serverMoves, "%s;", first.tidy);
9312             if(!blackPlaysFirst)
9313                 fprintf(serverMoves, "%s;", second.tidy);
9314         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9315         lastLoadFlag = loadFlag;
9316         // print base move
9317         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9318         // print castling suffix
9319         if( toY == fromY && piece == king ) {
9320             if(toX-fromX > 1)
9321                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9322             if(fromX-toX >1)
9323                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9324         }
9325         // e.p. suffix
9326         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9327              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9328              boards[forwardMostMove][toY][toX] == EmptySquare
9329              && fromX != toX && fromY != toY)
9330                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9331         // promotion suffix
9332         if(promoChar != NULLCHAR)
9333                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9334         if(!loadFlag) {
9335             fprintf(serverMoves, "/%d/%d",
9336                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9337             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9338             else                      timeLeft = blackTimeRemaining/1000;
9339             fprintf(serverMoves, "/%d", timeLeft);
9340         }
9341         fflush(serverMoves);
9342     }
9343
9344     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9345       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9346                         0, 1);
9347       return;
9348     }
9349     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9350     if (commentList[forwardMostMove+1] != NULL) {
9351         free(commentList[forwardMostMove+1]);
9352         commentList[forwardMostMove+1] = NULL;
9353     }
9354     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9355     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9356     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9357     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9358     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9359     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9360     gameInfo.result = GameUnfinished;
9361     if (gameInfo.resultDetails != NULL) {
9362         free(gameInfo.resultDetails);
9363         gameInfo.resultDetails = NULL;
9364     }
9365     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9366                               moveList[forwardMostMove - 1]);
9367     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9368                              PosFlags(forwardMostMove - 1),
9369                              fromY, fromX, toY, toX, promoChar,
9370                              parseList[forwardMostMove - 1]);
9371     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9372       case MT_NONE:
9373       case MT_STALEMATE:
9374       default:
9375         break;
9376       case MT_CHECK:
9377         if(gameInfo.variant != VariantShogi)
9378             strcat(parseList[forwardMostMove - 1], "+");
9379         break;
9380       case MT_CHECKMATE:
9381       case MT_STAINMATE:
9382         strcat(parseList[forwardMostMove - 1], "#");
9383         break;
9384     }
9385     if (appData.debugMode) {
9386         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9387     }
9388
9389 }
9390
9391 /* Updates currentMove if not pausing */
9392 void
9393 ShowMove(fromX, fromY, toX, toY)
9394 {
9395     int instant = (gameMode == PlayFromGameFile) ?
9396         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9397     if(appData.noGUI) return;
9398     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9399         if (!instant) {
9400             if (forwardMostMove == currentMove + 1) {
9401                 AnimateMove(boards[forwardMostMove - 1],
9402                             fromX, fromY, toX, toY);
9403             }
9404             if (appData.highlightLastMove) {
9405                 SetHighlights(fromX, fromY, toX, toY);
9406             }
9407         }
9408         currentMove = forwardMostMove;
9409     }
9410
9411     if (instant) return;
9412
9413     DisplayMove(currentMove - 1);
9414     DrawPosition(FALSE, boards[currentMove]);
9415     DisplayBothClocks();
9416     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9417     DisplayBook(currentMove);
9418 }
9419
9420 void SendEgtPath(ChessProgramState *cps)
9421 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9422         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9423
9424         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9425
9426         while(*p) {
9427             char c, *q = name+1, *r, *s;
9428
9429             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9430             while(*p && *p != ',') *q++ = *p++;
9431             *q++ = ':'; *q = 0;
9432             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9433                 strcmp(name, ",nalimov:") == 0 ) {
9434                 // take nalimov path from the menu-changeable option first, if it is defined
9435               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9436                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9437             } else
9438             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9439                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9440                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9441                 s = r = StrStr(s, ":") + 1; // beginning of path info
9442                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9443                 c = *r; *r = 0;             // temporarily null-terminate path info
9444                     *--q = 0;               // strip of trailig ':' from name
9445                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9446                 *r = c;
9447                 SendToProgram(buf,cps);     // send egtbpath command for this format
9448             }
9449             if(*p == ',') p++; // read away comma to position for next format name
9450         }
9451 }
9452
9453 void
9454 InitChessProgram(cps, setup)
9455      ChessProgramState *cps;
9456      int setup; /* [HGM] needed to setup FRC opening position */
9457 {
9458     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9459     if (appData.noChessProgram) return;
9460     hintRequested = FALSE;
9461     bookRequested = FALSE;
9462
9463     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9464     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9465     if(cps->memSize) { /* [HGM] memory */
9466       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9467         SendToProgram(buf, cps);
9468     }
9469     SendEgtPath(cps); /* [HGM] EGT */
9470     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9471       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9472         SendToProgram(buf, cps);
9473     }
9474
9475     SendToProgram(cps->initString, cps);
9476     if (gameInfo.variant != VariantNormal &&
9477         gameInfo.variant != VariantLoadable
9478         /* [HGM] also send variant if board size non-standard */
9479         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9480                                             ) {
9481       char *v = VariantName(gameInfo.variant);
9482       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9483         /* [HGM] in protocol 1 we have to assume all variants valid */
9484         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9485         DisplayFatalError(buf, 0, 1);
9486         return;
9487       }
9488
9489       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9490       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9491       if( gameInfo.variant == VariantXiangqi )
9492            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9493       if( gameInfo.variant == VariantShogi )
9494            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9495       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9496            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9497       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9498           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9499            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9500       if( gameInfo.variant == VariantCourier )
9501            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9502       if( gameInfo.variant == VariantSuper )
9503            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9504       if( gameInfo.variant == VariantGreat )
9505            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9506       if( gameInfo.variant == VariantSChess )
9507            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9508       if( gameInfo.variant == VariantGrand )
9509            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9510
9511       if(overruled) {
9512         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9513                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9514            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9515            if(StrStr(cps->variants, b) == NULL) {
9516                // specific sized variant not known, check if general sizing allowed
9517                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9518                    if(StrStr(cps->variants, "boardsize") == NULL) {
9519                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9520                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9521                        DisplayFatalError(buf, 0, 1);
9522                        return;
9523                    }
9524                    /* [HGM] here we really should compare with the maximum supported board size */
9525                }
9526            }
9527       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9528       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9529       SendToProgram(buf, cps);
9530     }
9531     currentlyInitializedVariant = gameInfo.variant;
9532
9533     /* [HGM] send opening position in FRC to first engine */
9534     if(setup) {
9535           SendToProgram("force\n", cps);
9536           SendBoard(cps, 0);
9537           /* engine is now in force mode! Set flag to wake it up after first move. */
9538           setboardSpoiledMachineBlack = 1;
9539     }
9540
9541     if (cps->sendICS) {
9542       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9543       SendToProgram(buf, cps);
9544     }
9545     cps->maybeThinking = FALSE;
9546     cps->offeredDraw = 0;
9547     if (!appData.icsActive) {
9548         SendTimeControl(cps, movesPerSession, timeControl,
9549                         timeIncrement, appData.searchDepth,
9550                         searchTime);
9551     }
9552     if (appData.showThinking
9553         // [HGM] thinking: four options require thinking output to be sent
9554         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9555                                 ) {
9556         SendToProgram("post\n", cps);
9557     }
9558     SendToProgram("hard\n", cps);
9559     if (!appData.ponderNextMove) {
9560         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9561            it without being sure what state we are in first.  "hard"
9562            is not a toggle, so that one is OK.
9563          */
9564         SendToProgram("easy\n", cps);
9565     }
9566     if (cps->usePing) {
9567       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9568       SendToProgram(buf, cps);
9569     }
9570     cps->initDone = TRUE;
9571     ClearEngineOutputPane(cps == &second);
9572 }
9573
9574
9575 void
9576 StartChessProgram(cps)
9577      ChessProgramState *cps;
9578 {
9579     char buf[MSG_SIZ];
9580     int err;
9581
9582     if (appData.noChessProgram) return;
9583     cps->initDone = FALSE;
9584
9585     if (strcmp(cps->host, "localhost") == 0) {
9586         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9587     } else if (*appData.remoteShell == NULLCHAR) {
9588         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9589     } else {
9590         if (*appData.remoteUser == NULLCHAR) {
9591           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9592                     cps->program);
9593         } else {
9594           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9595                     cps->host, appData.remoteUser, cps->program);
9596         }
9597         err = StartChildProcess(buf, "", &cps->pr);
9598     }
9599
9600     if (err != 0) {
9601       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9602         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9603         if(cps != &first) return;
9604         appData.noChessProgram = TRUE;
9605         ThawUI();
9606         SetNCPMode();
9607 //      DisplayFatalError(buf, err, 1);
9608 //      cps->pr = NoProc;
9609 //      cps->isr = NULL;
9610         return;
9611     }
9612
9613     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9614     if (cps->protocolVersion > 1) {
9615       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9616       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9617       cps->comboCnt = 0;  //                and values of combo boxes
9618       SendToProgram(buf, cps);
9619     } else {
9620       SendToProgram("xboard\n", cps);
9621     }
9622 }
9623
9624 void
9625 TwoMachinesEventIfReady P((void))
9626 {
9627   static int curMess = 0;
9628   if (first.lastPing != first.lastPong) {
9629     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9630     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9631     return;
9632   }
9633   if (second.lastPing != second.lastPong) {
9634     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9635     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9636     return;
9637   }
9638   DisplayMessage("", ""); curMess = 0;
9639   ThawUI();
9640   TwoMachinesEvent();
9641 }
9642
9643 char *
9644 MakeName(char *template)
9645 {
9646     time_t clock;
9647     struct tm *tm;
9648     static char buf[MSG_SIZ];
9649     char *p = buf;
9650     int i;
9651
9652     clock = time((time_t *)NULL);
9653     tm = localtime(&clock);
9654
9655     while(*p++ = *template++) if(p[-1] == '%') {
9656         switch(*template++) {
9657           case 0:   *p = 0; return buf;
9658           case 'Y': i = tm->tm_year+1900; break;
9659           case 'y': i = tm->tm_year-100; break;
9660           case 'M': i = tm->tm_mon+1; break;
9661           case 'd': i = tm->tm_mday; break;
9662           case 'h': i = tm->tm_hour; break;
9663           case 'm': i = tm->tm_min; break;
9664           case 's': i = tm->tm_sec; break;
9665           default:  i = 0;
9666         }
9667         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9668     }
9669     return buf;
9670 }
9671
9672 int
9673 CountPlayers(char *p)
9674 {
9675     int n = 0;
9676     while(p = strchr(p, '\n')) p++, n++; // count participants
9677     return n;
9678 }
9679
9680 FILE *
9681 WriteTourneyFile(char *results)
9682 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9683     FILE *f = fopen(appData.tourneyFile, "w");
9684     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9685         // create a file with tournament description
9686         fprintf(f, "-participants {%s}\n", appData.participants);
9687         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9688         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9689         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9690         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9691         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9692         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9693         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9694         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9695         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9696         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9697         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9698         if(searchTime > 0)
9699                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9700         else {
9701                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9702                 fprintf(f, "-tc %s\n", appData.timeControl);
9703                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9704         }
9705         fprintf(f, "-results \"%s\"\n", results);
9706     }
9707     return f;
9708 }
9709
9710 int
9711 CreateTourney(char *name)
9712 {
9713         FILE *f;
9714         if(name[0] == NULLCHAR) {
9715             if(appData.participants[0])
9716                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9717             return 0;
9718         }
9719         f = fopen(name, "r");
9720         if(f) { // file exists
9721             ASSIGN(appData.tourneyFile, name);
9722             ParseArgsFromFile(f); // parse it
9723         } else {
9724             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9725             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9726                 DisplayError(_("Not enough participants"), 0);
9727                 return 0;
9728             }
9729             ASSIGN(appData.tourneyFile, name);
9730             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9731             if((f = WriteTourneyFile("")) == NULL) return 0;
9732         }
9733         fclose(f);
9734         appData.noChessProgram = FALSE;
9735         appData.clockMode = TRUE;
9736         SetGNUMode();
9737         return 1;
9738 }
9739
9740 #define MAXENGINES 1000
9741 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9742
9743 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9744 {
9745     char buf[MSG_SIZ], *p, *q;
9746     int i=1;
9747     while(*names) {
9748         p = names; q = buf;
9749         while(*p && *p != '\n') *q++ = *p++;
9750         *q = 0;
9751         if(engineList[i]) free(engineList[i]);
9752         engineList[i] = strdup(buf);
9753         if(*p == '\n') p++;
9754         TidyProgramName(engineList[i], "localhost", buf);
9755         if(engineMnemonic[i]) free(engineMnemonic[i]);
9756         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9757             strcat(buf, " (");
9758             sscanf(q + 8, "%s", buf + strlen(buf));
9759             strcat(buf, ")");
9760         }
9761         engineMnemonic[i] = strdup(buf);
9762         names = p; i++;
9763       if(i > MAXENGINES - 2) break;
9764     }
9765     engineList[i] = NULL;
9766 }
9767
9768 // following implemented as macro to avoid type limitations
9769 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9770
9771 void SwapEngines(int n)
9772 {   // swap settings for first engine and other engine (so far only some selected options)
9773     int h;
9774     char *p;
9775     if(n == 0) return;
9776     SWAP(directory, p)
9777     SWAP(chessProgram, p)
9778     SWAP(isUCI, h)
9779     SWAP(hasOwnBookUCI, h)
9780     SWAP(protocolVersion, h)
9781     SWAP(reuse, h)
9782     SWAP(scoreIsAbsolute, h)
9783     SWAP(timeOdds, h)
9784     SWAP(logo, p)
9785     SWAP(pgnName, p)
9786     SWAP(pvSAN, h)
9787 }
9788
9789 void
9790 SetPlayer(int player)
9791 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9792     int i;
9793     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9794     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9795     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9796     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9797     if(mnemonic[i]) {
9798         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9799         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9800         ParseArgsFromString(buf);
9801     }
9802     free(engineName);
9803 }
9804
9805 int
9806 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9807 {   // determine players from game number
9808     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9809
9810     if(appData.tourneyType == 0) {
9811         roundsPerCycle = (nPlayers - 1) | 1;
9812         pairingsPerRound = nPlayers / 2;
9813     } else if(appData.tourneyType > 0) {
9814         roundsPerCycle = nPlayers - appData.tourneyType;
9815         pairingsPerRound = appData.tourneyType;
9816     }
9817     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9818     gamesPerCycle = gamesPerRound * roundsPerCycle;
9819     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9820     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9821     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9822     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9823     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9824     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9825
9826     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9827     if(appData.roundSync) *syncInterval = gamesPerRound;
9828
9829     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9830
9831     if(appData.tourneyType == 0) {
9832         if(curPairing == (nPlayers-1)/2 ) {
9833             *whitePlayer = curRound;
9834             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9835         } else {
9836             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9837             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9838             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9839             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9840         }
9841     } else if(appData.tourneyType > 0) {
9842         *whitePlayer = curPairing;
9843         *blackPlayer = curRound + appData.tourneyType;
9844     }
9845
9846     // take care of white/black alternation per round. 
9847     // For cycles and games this is already taken care of by default, derived from matchGame!
9848     return curRound & 1;
9849 }
9850
9851 int
9852 NextTourneyGame(int nr, int *swapColors)
9853 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9854     char *p, *q;
9855     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9856     FILE *tf;
9857     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9858     tf = fopen(appData.tourneyFile, "r");
9859     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9860     ParseArgsFromFile(tf); fclose(tf);
9861     InitTimeControls(); // TC might be altered from tourney file
9862
9863     nPlayers = CountPlayers(appData.participants); // count participants
9864     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9865     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9866
9867     if(syncInterval) {
9868         p = q = appData.results;
9869         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9870         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9871             DisplayMessage(_("Waiting for other game(s)"),"");
9872             waitingForGame = TRUE;
9873             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9874             return 0;
9875         }
9876         waitingForGame = FALSE;
9877     }
9878
9879     if(appData.tourneyType < 0) {
9880         if(nr>=0 && !pairingReceived) {
9881             char buf[1<<16];
9882             if(pairing.pr == NoProc) {
9883                 if(!appData.pairingEngine[0]) {
9884                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9885                     return 0;
9886                 }
9887                 StartChessProgram(&pairing); // starts the pairing engine
9888             }
9889             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9890             SendToProgram(buf, &pairing);
9891             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9892             SendToProgram(buf, &pairing);
9893             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9894         }
9895         pairingReceived = 0;                              // ... so we continue here 
9896         *swapColors = 0;
9897         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9898         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9899         matchGame = 1; roundNr = nr / syncInterval + 1;
9900     }
9901
9902     if(first.pr != NoProc) return 1; // engines already loaded
9903
9904     // redefine engines, engine dir, etc.
9905     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9906     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9907     SwapEngines(1);
9908     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9909     SwapEngines(1);         // and make that valid for second engine by swapping
9910     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9911     InitEngine(&second, 1);
9912     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9913     UpdateLogos(FALSE);     // leave display to ModeHiglight()
9914     return 1;
9915 }
9916
9917 void
9918 NextMatchGame()
9919 {   // performs game initialization that does not invoke engines, and then tries to start the game
9920     int firstWhite, swapColors = 0;
9921     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9922     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9923     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9924     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9925     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9926     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9927     Reset(FALSE, first.pr != NoProc);
9928     appData.noChessProgram = FALSE;
9929     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9930     TwoMachinesEvent();
9931 }
9932
9933 void UserAdjudicationEvent( int result )
9934 {
9935     ChessMove gameResult = GameIsDrawn;
9936
9937     if( result > 0 ) {
9938         gameResult = WhiteWins;
9939     }
9940     else if( result < 0 ) {
9941         gameResult = BlackWins;
9942     }
9943
9944     if( gameMode == TwoMachinesPlay ) {
9945         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9946     }
9947 }
9948
9949
9950 // [HGM] save: calculate checksum of game to make games easily identifiable
9951 int StringCheckSum(char *s)
9952 {
9953         int i = 0;
9954         if(s==NULL) return 0;
9955         while(*s) i = i*259 + *s++;
9956         return i;
9957 }
9958
9959 int GameCheckSum()
9960 {
9961         int i, sum=0;
9962         for(i=backwardMostMove; i<forwardMostMove; i++) {
9963                 sum += pvInfoList[i].depth;
9964                 sum += StringCheckSum(parseList[i]);
9965                 sum += StringCheckSum(commentList[i]);
9966                 sum *= 261;
9967         }
9968         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9969         return sum + StringCheckSum(commentList[i]);
9970 } // end of save patch
9971
9972 void
9973 GameEnds(result, resultDetails, whosays)
9974      ChessMove result;
9975      char *resultDetails;
9976      int whosays;
9977 {
9978     GameMode nextGameMode;
9979     int isIcsGame;
9980     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9981
9982     if(endingGame) return; /* [HGM] crash: forbid recursion */
9983     endingGame = 1;
9984     if(twoBoards) { // [HGM] dual: switch back to one board
9985         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9986         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9987     }
9988     if (appData.debugMode) {
9989       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9990               result, resultDetails ? resultDetails : "(null)", whosays);
9991     }
9992
9993     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9994
9995     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9996         /* If we are playing on ICS, the server decides when the
9997            game is over, but the engine can offer to draw, claim
9998            a draw, or resign.
9999          */
10000 #if ZIPPY
10001         if (appData.zippyPlay && first.initDone) {
10002             if (result == GameIsDrawn) {
10003                 /* In case draw still needs to be claimed */
10004                 SendToICS(ics_prefix);
10005                 SendToICS("draw\n");
10006             } else if (StrCaseStr(resultDetails, "resign")) {
10007                 SendToICS(ics_prefix);
10008                 SendToICS("resign\n");
10009             }
10010         }
10011 #endif
10012         endingGame = 0; /* [HGM] crash */
10013         return;
10014     }
10015
10016     /* If we're loading the game from a file, stop */
10017     if (whosays == GE_FILE) {
10018       (void) StopLoadGameTimer();
10019       gameFileFP = NULL;
10020     }
10021
10022     /* Cancel draw offers */
10023     first.offeredDraw = second.offeredDraw = 0;
10024
10025     /* If this is an ICS game, only ICS can really say it's done;
10026        if not, anyone can. */
10027     isIcsGame = (gameMode == IcsPlayingWhite ||
10028                  gameMode == IcsPlayingBlack ||
10029                  gameMode == IcsObserving    ||
10030                  gameMode == IcsExamining);
10031
10032     if (!isIcsGame || whosays == GE_ICS) {
10033         /* OK -- not an ICS game, or ICS said it was done */
10034         StopClocks();
10035         if (!isIcsGame && !appData.noChessProgram)
10036           SetUserThinkingEnables();
10037
10038         /* [HGM] if a machine claims the game end we verify this claim */
10039         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10040             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10041                 char claimer;
10042                 ChessMove trueResult = (ChessMove) -1;
10043
10044                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10045                                             first.twoMachinesColor[0] :
10046                                             second.twoMachinesColor[0] ;
10047
10048                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10049                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10050                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10051                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10052                 } else
10053                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10054                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10055                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10056                 } else
10057                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10058                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10059                 }
10060
10061                 // now verify win claims, but not in drop games, as we don't understand those yet
10062                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10063                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10064                     (result == WhiteWins && claimer == 'w' ||
10065                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10066                       if (appData.debugMode) {
10067                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10068                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10069                       }
10070                       if(result != trueResult) {
10071                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10072                               result = claimer == 'w' ? BlackWins : WhiteWins;
10073                               resultDetails = buf;
10074                       }
10075                 } else
10076                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10077                     && (forwardMostMove <= backwardMostMove ||
10078                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10079                         (claimer=='b')==(forwardMostMove&1))
10080                                                                                   ) {
10081                       /* [HGM] verify: draws that were not flagged are false claims */
10082                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10083                       result = claimer == 'w' ? BlackWins : WhiteWins;
10084                       resultDetails = buf;
10085                 }
10086                 /* (Claiming a loss is accepted no questions asked!) */
10087             }
10088             /* [HGM] bare: don't allow bare King to win */
10089             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10090                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10091                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10092                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10093                && result != GameIsDrawn)
10094             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10095                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10096                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10097                         if(p >= 0 && p <= (int)WhiteKing) k++;
10098                 }
10099                 if (appData.debugMode) {
10100                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10101                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10102                 }
10103                 if(k <= 1) {
10104                         result = GameIsDrawn;
10105                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10106                         resultDetails = buf;
10107                 }
10108             }
10109         }
10110
10111
10112         if(serverMoves != NULL && !loadFlag) { char c = '=';
10113             if(result==WhiteWins) c = '+';
10114             if(result==BlackWins) c = '-';
10115             if(resultDetails != NULL)
10116                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10117         }
10118         if (resultDetails != NULL) {
10119             gameInfo.result = result;
10120             gameInfo.resultDetails = StrSave(resultDetails);
10121
10122             /* display last move only if game was not loaded from file */
10123             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10124                 DisplayMove(currentMove - 1);
10125
10126             if (forwardMostMove != 0) {
10127                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10128                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10129                                                                 ) {
10130                     if (*appData.saveGameFile != NULLCHAR) {
10131                         SaveGameToFile(appData.saveGameFile, TRUE);
10132                     } else if (appData.autoSaveGames) {
10133                         AutoSaveGame();
10134                     }
10135                     if (*appData.savePositionFile != NULLCHAR) {
10136                         SavePositionToFile(appData.savePositionFile);
10137                     }
10138                 }
10139             }
10140
10141             /* Tell program how game ended in case it is learning */
10142             /* [HGM] Moved this to after saving the PGN, just in case */
10143             /* engine died and we got here through time loss. In that */
10144             /* case we will get a fatal error writing the pipe, which */
10145             /* would otherwise lose us the PGN.                       */
10146             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10147             /* output during GameEnds should never be fatal anymore   */
10148             if (gameMode == MachinePlaysWhite ||
10149                 gameMode == MachinePlaysBlack ||
10150                 gameMode == TwoMachinesPlay ||
10151                 gameMode == IcsPlayingWhite ||
10152                 gameMode == IcsPlayingBlack ||
10153                 gameMode == BeginningOfGame) {
10154                 char buf[MSG_SIZ];
10155                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10156                         resultDetails);
10157                 if (first.pr != NoProc) {
10158                     SendToProgram(buf, &first);
10159                 }
10160                 if (second.pr != NoProc &&
10161                     gameMode == TwoMachinesPlay) {
10162                     SendToProgram(buf, &second);
10163                 }
10164             }
10165         }
10166
10167         if (appData.icsActive) {
10168             if (appData.quietPlay &&
10169                 (gameMode == IcsPlayingWhite ||
10170                  gameMode == IcsPlayingBlack)) {
10171                 SendToICS(ics_prefix);
10172                 SendToICS("set shout 1\n");
10173             }
10174             nextGameMode = IcsIdle;
10175             ics_user_moved = FALSE;
10176             /* clean up premove.  It's ugly when the game has ended and the
10177              * premove highlights are still on the board.
10178              */
10179             if (gotPremove) {
10180               gotPremove = FALSE;
10181               ClearPremoveHighlights();
10182               DrawPosition(FALSE, boards[currentMove]);
10183             }
10184             if (whosays == GE_ICS) {
10185                 switch (result) {
10186                 case WhiteWins:
10187                     if (gameMode == IcsPlayingWhite)
10188                         PlayIcsWinSound();
10189                     else if(gameMode == IcsPlayingBlack)
10190                         PlayIcsLossSound();
10191                     break;
10192                 case BlackWins:
10193                     if (gameMode == IcsPlayingBlack)
10194                         PlayIcsWinSound();
10195                     else if(gameMode == IcsPlayingWhite)
10196                         PlayIcsLossSound();
10197                     break;
10198                 case GameIsDrawn:
10199                     PlayIcsDrawSound();
10200                     break;
10201                 default:
10202                     PlayIcsUnfinishedSound();
10203                 }
10204             }
10205         } else if (gameMode == EditGame ||
10206                    gameMode == PlayFromGameFile ||
10207                    gameMode == AnalyzeMode ||
10208                    gameMode == AnalyzeFile) {
10209             nextGameMode = gameMode;
10210         } else {
10211             nextGameMode = EndOfGame;
10212         }
10213         pausing = FALSE;
10214         ModeHighlight();
10215     } else {
10216         nextGameMode = gameMode;
10217     }
10218
10219     if (appData.noChessProgram) {
10220         gameMode = nextGameMode;
10221         ModeHighlight();
10222         endingGame = 0; /* [HGM] crash */
10223         return;
10224     }
10225
10226     if (first.reuse) {
10227         /* Put first chess program into idle state */
10228         if (first.pr != NoProc &&
10229             (gameMode == MachinePlaysWhite ||
10230              gameMode == MachinePlaysBlack ||
10231              gameMode == TwoMachinesPlay ||
10232              gameMode == IcsPlayingWhite ||
10233              gameMode == IcsPlayingBlack ||
10234              gameMode == BeginningOfGame)) {
10235             SendToProgram("force\n", &first);
10236             if (first.usePing) {
10237               char buf[MSG_SIZ];
10238               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10239               SendToProgram(buf, &first);
10240             }
10241         }
10242     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10243         /* Kill off first chess program */
10244         if (first.isr != NULL)
10245           RemoveInputSource(first.isr);
10246         first.isr = NULL;
10247
10248         if (first.pr != NoProc) {
10249             ExitAnalyzeMode();
10250             DoSleep( appData.delayBeforeQuit );
10251             SendToProgram("quit\n", &first);
10252             DoSleep( appData.delayAfterQuit );
10253             DestroyChildProcess(first.pr, first.useSigterm);
10254         }
10255         first.pr = NoProc;
10256     }
10257     if (second.reuse) {
10258         /* Put second chess program into idle state */
10259         if (second.pr != NoProc &&
10260             gameMode == TwoMachinesPlay) {
10261             SendToProgram("force\n", &second);
10262             if (second.usePing) {
10263               char buf[MSG_SIZ];
10264               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10265               SendToProgram(buf, &second);
10266             }
10267         }
10268     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10269         /* Kill off second chess program */
10270         if (second.isr != NULL)
10271           RemoveInputSource(second.isr);
10272         second.isr = NULL;
10273
10274         if (second.pr != NoProc) {
10275             DoSleep( appData.delayBeforeQuit );
10276             SendToProgram("quit\n", &second);
10277             DoSleep( appData.delayAfterQuit );
10278             DestroyChildProcess(second.pr, second.useSigterm);
10279         }
10280         second.pr = NoProc;
10281     }
10282
10283     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10284         char resChar = '=';
10285         switch (result) {
10286         case WhiteWins:
10287           resChar = '+';
10288           if (first.twoMachinesColor[0] == 'w') {
10289             first.matchWins++;
10290           } else {
10291             second.matchWins++;
10292           }
10293           break;
10294         case BlackWins:
10295           resChar = '-';
10296           if (first.twoMachinesColor[0] == 'b') {
10297             first.matchWins++;
10298           } else {
10299             second.matchWins++;
10300           }
10301           break;
10302         case GameUnfinished:
10303           resChar = ' ';
10304         default:
10305           break;
10306         }
10307
10308         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10309         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10310             ReserveGame(nextGame, resChar); // sets nextGame
10311             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10312             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10313         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10314
10315         if (nextGame <= appData.matchGames && !abortMatch) {
10316             gameMode = nextGameMode;
10317             matchGame = nextGame; // this will be overruled in tourney mode!
10318             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10319             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10320             endingGame = 0; /* [HGM] crash */
10321             return;
10322         } else {
10323             gameMode = nextGameMode;
10324             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10325                      first.tidy, second.tidy,
10326                      first.matchWins, second.matchWins,
10327                      appData.matchGames - (first.matchWins + second.matchWins));
10328             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10329             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10330             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10331                 first.twoMachinesColor = "black\n";
10332                 second.twoMachinesColor = "white\n";
10333             } else {
10334                 first.twoMachinesColor = "white\n";
10335                 second.twoMachinesColor = "black\n";
10336             }
10337         }
10338     }
10339     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10340         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10341       ExitAnalyzeMode();
10342     gameMode = nextGameMode;
10343     ModeHighlight();
10344     endingGame = 0;  /* [HGM] crash */
10345     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10346         if(matchMode == TRUE) { // match through command line: exit with or without popup
10347             if(ranking) {
10348                 ToNrEvent(forwardMostMove);
10349                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10350                 else ExitEvent(0);
10351             } else DisplayFatalError(buf, 0, 0);
10352         } else { // match through menu; just stop, with or without popup
10353             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10354             ModeHighlight();
10355             if(ranking){
10356                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10357             } else DisplayNote(buf);
10358       }
10359       if(ranking) free(ranking);
10360     }
10361 }
10362
10363 /* Assumes program was just initialized (initString sent).
10364    Leaves program in force mode. */
10365 void
10366 FeedMovesToProgram(cps, upto)
10367      ChessProgramState *cps;
10368      int upto;
10369 {
10370     int i;
10371
10372     if (appData.debugMode)
10373       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10374               startedFromSetupPosition ? "position and " : "",
10375               backwardMostMove, upto, cps->which);
10376     if(currentlyInitializedVariant != gameInfo.variant) {
10377       char buf[MSG_SIZ];
10378         // [HGM] variantswitch: make engine aware of new variant
10379         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10380                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10381         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10382         SendToProgram(buf, cps);
10383         currentlyInitializedVariant = gameInfo.variant;
10384     }
10385     SendToProgram("force\n", cps);
10386     if (startedFromSetupPosition) {
10387         SendBoard(cps, backwardMostMove);
10388     if (appData.debugMode) {
10389         fprintf(debugFP, "feedMoves\n");
10390     }
10391     }
10392     for (i = backwardMostMove; i < upto; i++) {
10393         SendMoveToProgram(i, cps);
10394     }
10395 }
10396
10397
10398 int
10399 ResurrectChessProgram()
10400 {
10401      /* The chess program may have exited.
10402         If so, restart it and feed it all the moves made so far. */
10403     static int doInit = 0;
10404
10405     if (appData.noChessProgram) return 1;
10406
10407     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10408         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10409         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10410         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10411     } else {
10412         if (first.pr != NoProc) return 1;
10413         StartChessProgram(&first);
10414     }
10415     InitChessProgram(&first, FALSE);
10416     FeedMovesToProgram(&first, currentMove);
10417
10418     if (!first.sendTime) {
10419         /* can't tell gnuchess what its clock should read,
10420            so we bow to its notion. */
10421         ResetClocks();
10422         timeRemaining[0][currentMove] = whiteTimeRemaining;
10423         timeRemaining[1][currentMove] = blackTimeRemaining;
10424     }
10425
10426     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10427                 appData.icsEngineAnalyze) && first.analysisSupport) {
10428       SendToProgram("analyze\n", &first);
10429       first.analyzing = TRUE;
10430     }
10431     return 1;
10432 }
10433
10434 /*
10435  * Button procedures
10436  */
10437 void
10438 Reset(redraw, init)
10439      int redraw, init;
10440 {
10441     int i;
10442
10443     if (appData.debugMode) {
10444         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10445                 redraw, init, gameMode);
10446     }
10447     CleanupTail(); // [HGM] vari: delete any stored variations
10448     pausing = pauseExamInvalid = FALSE;
10449     startedFromSetupPosition = blackPlaysFirst = FALSE;
10450     firstMove = TRUE;
10451     whiteFlag = blackFlag = FALSE;
10452     userOfferedDraw = FALSE;
10453     hintRequested = bookRequested = FALSE;
10454     first.maybeThinking = FALSE;
10455     second.maybeThinking = FALSE;
10456     first.bookSuspend = FALSE; // [HGM] book
10457     second.bookSuspend = FALSE;
10458     thinkOutput[0] = NULLCHAR;
10459     lastHint[0] = NULLCHAR;
10460     ClearGameInfo(&gameInfo);
10461     gameInfo.variant = StringToVariant(appData.variant);
10462     ics_user_moved = ics_clock_paused = FALSE;
10463     ics_getting_history = H_FALSE;
10464     ics_gamenum = -1;
10465     white_holding[0] = black_holding[0] = NULLCHAR;
10466     ClearProgramStats();
10467     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10468
10469     ResetFrontEnd();
10470     ClearHighlights();
10471     flipView = appData.flipView;
10472     ClearPremoveHighlights();
10473     gotPremove = FALSE;
10474     alarmSounded = FALSE;
10475
10476     GameEnds(EndOfFile, NULL, GE_PLAYER);
10477     if(appData.serverMovesName != NULL) {
10478         /* [HGM] prepare to make moves file for broadcasting */
10479         clock_t t = clock();
10480         if(serverMoves != NULL) fclose(serverMoves);
10481         serverMoves = fopen(appData.serverMovesName, "r");
10482         if(serverMoves != NULL) {
10483             fclose(serverMoves);
10484             /* delay 15 sec before overwriting, so all clients can see end */
10485             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10486         }
10487         serverMoves = fopen(appData.serverMovesName, "w");
10488     }
10489
10490     ExitAnalyzeMode();
10491     gameMode = BeginningOfGame;
10492     ModeHighlight();
10493     if(appData.icsActive) gameInfo.variant = VariantNormal;
10494     currentMove = forwardMostMove = backwardMostMove = 0;
10495     InitPosition(redraw);
10496     for (i = 0; i < MAX_MOVES; i++) {
10497         if (commentList[i] != NULL) {
10498             free(commentList[i]);
10499             commentList[i] = NULL;
10500         }
10501     }
10502     ResetClocks();
10503     timeRemaining[0][0] = whiteTimeRemaining;
10504     timeRemaining[1][0] = blackTimeRemaining;
10505
10506     if (first.pr == NULL) {
10507         StartChessProgram(&first);
10508     }
10509     if (init) {
10510             InitChessProgram(&first, startedFromSetupPosition);
10511     }
10512     DisplayTitle("");
10513     DisplayMessage("", "");
10514     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10515     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10516 }
10517
10518 void
10519 AutoPlayGameLoop()
10520 {
10521     for (;;) {
10522         if (!AutoPlayOneMove())
10523           return;
10524         if (matchMode || appData.timeDelay == 0)
10525           continue;
10526         if (appData.timeDelay < 0)
10527           return;
10528         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10529         break;
10530     }
10531 }
10532
10533
10534 int
10535 AutoPlayOneMove()
10536 {
10537     int fromX, fromY, toX, toY;
10538
10539     if (appData.debugMode) {
10540       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10541     }
10542
10543     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10544       return FALSE;
10545
10546     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10547       pvInfoList[currentMove].depth = programStats.depth;
10548       pvInfoList[currentMove].score = programStats.score;
10549       pvInfoList[currentMove].time  = 0;
10550       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10551     }
10552
10553     if (currentMove >= forwardMostMove) {
10554       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10555       gameMode = EditGame;
10556       ModeHighlight();
10557
10558       /* [AS] Clear current move marker at the end of a game */
10559       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10560
10561       return FALSE;
10562     }
10563
10564     toX = moveList[currentMove][2] - AAA;
10565     toY = moveList[currentMove][3] - ONE;
10566
10567     if (moveList[currentMove][1] == '@') {
10568         if (appData.highlightLastMove) {
10569             SetHighlights(-1, -1, toX, toY);
10570         }
10571     } else {
10572         fromX = moveList[currentMove][0] - AAA;
10573         fromY = moveList[currentMove][1] - ONE;
10574
10575         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10576
10577         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10578
10579         if (appData.highlightLastMove) {
10580             SetHighlights(fromX, fromY, toX, toY);
10581         }
10582     }
10583     DisplayMove(currentMove);
10584     SendMoveToProgram(currentMove++, &first);
10585     DisplayBothClocks();
10586     DrawPosition(FALSE, boards[currentMove]);
10587     // [HGM] PV info: always display, routine tests if empty
10588     DisplayComment(currentMove - 1, commentList[currentMove]);
10589     return TRUE;
10590 }
10591
10592
10593 int
10594 LoadGameOneMove(readAhead)
10595      ChessMove readAhead;
10596 {
10597     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10598     char promoChar = NULLCHAR;
10599     ChessMove moveType;
10600     char move[MSG_SIZ];
10601     char *p, *q;
10602
10603     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10604         gameMode != AnalyzeMode && gameMode != Training) {
10605         gameFileFP = NULL;
10606         return FALSE;
10607     }
10608
10609     yyboardindex = forwardMostMove;
10610     if (readAhead != EndOfFile) {
10611       moveType = readAhead;
10612     } else {
10613       if (gameFileFP == NULL)
10614           return FALSE;
10615       moveType = (ChessMove) Myylex();
10616     }
10617
10618     done = FALSE;
10619     switch (moveType) {
10620       case Comment:
10621         if (appData.debugMode)
10622           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10623         p = yy_text;
10624
10625         /* append the comment but don't display it */
10626         AppendComment(currentMove, p, FALSE);
10627         return TRUE;
10628
10629       case WhiteCapturesEnPassant:
10630       case BlackCapturesEnPassant:
10631       case WhitePromotion:
10632       case BlackPromotion:
10633       case WhiteNonPromotion:
10634       case BlackNonPromotion:
10635       case NormalMove:
10636       case WhiteKingSideCastle:
10637       case WhiteQueenSideCastle:
10638       case BlackKingSideCastle:
10639       case BlackQueenSideCastle:
10640       case WhiteKingSideCastleWild:
10641       case WhiteQueenSideCastleWild:
10642       case BlackKingSideCastleWild:
10643       case BlackQueenSideCastleWild:
10644       /* PUSH Fabien */
10645       case WhiteHSideCastleFR:
10646       case WhiteASideCastleFR:
10647       case BlackHSideCastleFR:
10648       case BlackASideCastleFR:
10649       /* POP Fabien */
10650         if (appData.debugMode)
10651           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10652         fromX = currentMoveString[0] - AAA;
10653         fromY = currentMoveString[1] - ONE;
10654         toX = currentMoveString[2] - AAA;
10655         toY = currentMoveString[3] - ONE;
10656         promoChar = currentMoveString[4];
10657         break;
10658
10659       case WhiteDrop:
10660       case BlackDrop:
10661         if (appData.debugMode)
10662           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10663         fromX = moveType == WhiteDrop ?
10664           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10665         (int) CharToPiece(ToLower(currentMoveString[0]));
10666         fromY = DROP_RANK;
10667         toX = currentMoveString[2] - AAA;
10668         toY = currentMoveString[3] - ONE;
10669         break;
10670
10671       case WhiteWins:
10672       case BlackWins:
10673       case GameIsDrawn:
10674       case GameUnfinished:
10675         if (appData.debugMode)
10676           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10677         p = strchr(yy_text, '{');
10678         if (p == NULL) p = strchr(yy_text, '(');
10679         if (p == NULL) {
10680             p = yy_text;
10681             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10682         } else {
10683             q = strchr(p, *p == '{' ? '}' : ')');
10684             if (q != NULL) *q = NULLCHAR;
10685             p++;
10686         }
10687         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10688         GameEnds(moveType, p, GE_FILE);
10689         done = TRUE;
10690         if (cmailMsgLoaded) {
10691             ClearHighlights();
10692             flipView = WhiteOnMove(currentMove);
10693             if (moveType == GameUnfinished) flipView = !flipView;
10694             if (appData.debugMode)
10695               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10696         }
10697         break;
10698
10699       case EndOfFile:
10700         if (appData.debugMode)
10701           fprintf(debugFP, "Parser hit end of file\n");
10702         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10703           case MT_NONE:
10704           case MT_CHECK:
10705             break;
10706           case MT_CHECKMATE:
10707           case MT_STAINMATE:
10708             if (WhiteOnMove(currentMove)) {
10709                 GameEnds(BlackWins, "Black mates", GE_FILE);
10710             } else {
10711                 GameEnds(WhiteWins, "White mates", GE_FILE);
10712             }
10713             break;
10714           case MT_STALEMATE:
10715             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10716             break;
10717         }
10718         done = TRUE;
10719         break;
10720
10721       case MoveNumberOne:
10722         if (lastLoadGameStart == GNUChessGame) {
10723             /* GNUChessGames have numbers, but they aren't move numbers */
10724             if (appData.debugMode)
10725               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10726                       yy_text, (int) moveType);
10727             return LoadGameOneMove(EndOfFile); /* tail recursion */
10728         }
10729         /* else fall thru */
10730
10731       case XBoardGame:
10732       case GNUChessGame:
10733       case PGNTag:
10734         /* Reached start of next game in file */
10735         if (appData.debugMode)
10736           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10737         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10738           case MT_NONE:
10739           case MT_CHECK:
10740             break;
10741           case MT_CHECKMATE:
10742           case MT_STAINMATE:
10743             if (WhiteOnMove(currentMove)) {
10744                 GameEnds(BlackWins, "Black mates", GE_FILE);
10745             } else {
10746                 GameEnds(WhiteWins, "White mates", GE_FILE);
10747             }
10748             break;
10749           case MT_STALEMATE:
10750             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10751             break;
10752         }
10753         done = TRUE;
10754         break;
10755
10756       case PositionDiagram:     /* should not happen; ignore */
10757       case ElapsedTime:         /* ignore */
10758       case NAG:                 /* ignore */
10759         if (appData.debugMode)
10760           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10761                   yy_text, (int) moveType);
10762         return LoadGameOneMove(EndOfFile); /* tail recursion */
10763
10764       case IllegalMove:
10765         if (appData.testLegality) {
10766             if (appData.debugMode)
10767               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10768             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10769                     (forwardMostMove / 2) + 1,
10770                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10771             DisplayError(move, 0);
10772             done = TRUE;
10773         } else {
10774             if (appData.debugMode)
10775               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10776                       yy_text, currentMoveString);
10777             fromX = currentMoveString[0] - AAA;
10778             fromY = currentMoveString[1] - ONE;
10779             toX = currentMoveString[2] - AAA;
10780             toY = currentMoveString[3] - ONE;
10781             promoChar = currentMoveString[4];
10782         }
10783         break;
10784
10785       case AmbiguousMove:
10786         if (appData.debugMode)
10787           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10788         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10789                 (forwardMostMove / 2) + 1,
10790                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10791         DisplayError(move, 0);
10792         done = TRUE;
10793         break;
10794
10795       default:
10796       case ImpossibleMove:
10797         if (appData.debugMode)
10798           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10799         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10800                 (forwardMostMove / 2) + 1,
10801                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10802         DisplayError(move, 0);
10803         done = TRUE;
10804         break;
10805     }
10806
10807     if (done) {
10808         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10809             DrawPosition(FALSE, boards[currentMove]);
10810             DisplayBothClocks();
10811             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10812               DisplayComment(currentMove - 1, commentList[currentMove]);
10813         }
10814         (void) StopLoadGameTimer();
10815         gameFileFP = NULL;
10816         cmailOldMove = forwardMostMove;
10817         return FALSE;
10818     } else {
10819         /* currentMoveString is set as a side-effect of yylex */
10820
10821         thinkOutput[0] = NULLCHAR;
10822         MakeMove(fromX, fromY, toX, toY, promoChar);
10823         currentMove = forwardMostMove;
10824         return TRUE;
10825     }
10826 }
10827
10828 /* Load the nth game from the given file */
10829 int
10830 LoadGameFromFile(filename, n, title, useList)
10831      char *filename;
10832      int n;
10833      char *title;
10834      /*Boolean*/ int useList;
10835 {
10836     FILE *f;
10837     char buf[MSG_SIZ];
10838
10839     if (strcmp(filename, "-") == 0) {
10840         f = stdin;
10841         title = "stdin";
10842     } else {
10843         f = fopen(filename, "rb");
10844         if (f == NULL) {
10845           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10846             DisplayError(buf, errno);
10847             return FALSE;
10848         }
10849     }
10850     if (fseek(f, 0, 0) == -1) {
10851         /* f is not seekable; probably a pipe */
10852         useList = FALSE;
10853     }
10854     if (useList && n == 0) {
10855         int error = GameListBuild(f);
10856         if (error) {
10857             DisplayError(_("Cannot build game list"), error);
10858         } else if (!ListEmpty(&gameList) &&
10859                    ((ListGame *) gameList.tailPred)->number > 1) {
10860             GameListPopUp(f, title);
10861             return TRUE;
10862         }
10863         GameListDestroy();
10864         n = 1;
10865     }
10866     if (n == 0) n = 1;
10867     return LoadGame(f, n, title, FALSE);
10868 }
10869
10870
10871 void
10872 MakeRegisteredMove()
10873 {
10874     int fromX, fromY, toX, toY;
10875     char promoChar;
10876     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10877         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10878           case CMAIL_MOVE:
10879           case CMAIL_DRAW:
10880             if (appData.debugMode)
10881               fprintf(debugFP, "Restoring %s for game %d\n",
10882                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10883
10884             thinkOutput[0] = NULLCHAR;
10885             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10886             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10887             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10888             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10889             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10890             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10891             MakeMove(fromX, fromY, toX, toY, promoChar);
10892             ShowMove(fromX, fromY, toX, toY);
10893
10894             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10895               case MT_NONE:
10896               case MT_CHECK:
10897                 break;
10898
10899               case MT_CHECKMATE:
10900               case MT_STAINMATE:
10901                 if (WhiteOnMove(currentMove)) {
10902                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10903                 } else {
10904                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10905                 }
10906                 break;
10907
10908               case MT_STALEMATE:
10909                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10910                 break;
10911             }
10912
10913             break;
10914
10915           case CMAIL_RESIGN:
10916             if (WhiteOnMove(currentMove)) {
10917                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10918             } else {
10919                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10920             }
10921             break;
10922
10923           case CMAIL_ACCEPT:
10924             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10925             break;
10926
10927           default:
10928             break;
10929         }
10930     }
10931
10932     return;
10933 }
10934
10935 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10936 int
10937 CmailLoadGame(f, gameNumber, title, useList)
10938      FILE *f;
10939      int gameNumber;
10940      char *title;
10941      int useList;
10942 {
10943     int retVal;
10944
10945     if (gameNumber > nCmailGames) {
10946         DisplayError(_("No more games in this message"), 0);
10947         return FALSE;
10948     }
10949     if (f == lastLoadGameFP) {
10950         int offset = gameNumber - lastLoadGameNumber;
10951         if (offset == 0) {
10952             cmailMsg[0] = NULLCHAR;
10953             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10954                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10955                 nCmailMovesRegistered--;
10956             }
10957             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10958             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10959                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10960             }
10961         } else {
10962             if (! RegisterMove()) return FALSE;
10963         }
10964     }
10965
10966     retVal = LoadGame(f, gameNumber, title, useList);
10967
10968     /* Make move registered during previous look at this game, if any */
10969     MakeRegisteredMove();
10970
10971     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10972         commentList[currentMove]
10973           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10974         DisplayComment(currentMove - 1, commentList[currentMove]);
10975     }
10976
10977     return retVal;
10978 }
10979
10980 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10981 int
10982 ReloadGame(offset)
10983      int offset;
10984 {
10985     int gameNumber = lastLoadGameNumber + offset;
10986     if (lastLoadGameFP == NULL) {
10987         DisplayError(_("No game has been loaded yet"), 0);
10988         return FALSE;
10989     }
10990     if (gameNumber <= 0) {
10991         DisplayError(_("Can't back up any further"), 0);
10992         return FALSE;
10993     }
10994     if (cmailMsgLoaded) {
10995         return CmailLoadGame(lastLoadGameFP, gameNumber,
10996                              lastLoadGameTitle, lastLoadGameUseList);
10997     } else {
10998         return LoadGame(lastLoadGameFP, gameNumber,
10999                         lastLoadGameTitle, lastLoadGameUseList);
11000     }
11001 }
11002
11003
11004
11005 /* Load the nth game from open file f */
11006 int
11007 LoadGame(f, gameNumber, title, useList)
11008      FILE *f;
11009      int gameNumber;
11010      char *title;
11011      int useList;
11012 {
11013     ChessMove cm;
11014     char buf[MSG_SIZ];
11015     int gn = gameNumber;
11016     ListGame *lg = NULL;
11017     int numPGNTags = 0;
11018     int err;
11019     GameMode oldGameMode;
11020     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11021
11022     if (appData.debugMode)
11023         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11024
11025     if (gameMode == Training )
11026         SetTrainingModeOff();
11027
11028     oldGameMode = gameMode;
11029     if (gameMode != BeginningOfGame) {
11030       Reset(FALSE, TRUE);
11031     }
11032
11033     gameFileFP = f;
11034     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11035         fclose(lastLoadGameFP);
11036     }
11037
11038     if (useList) {
11039         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11040
11041         if (lg) {
11042             fseek(f, lg->offset, 0);
11043             GameListHighlight(gameNumber);
11044             gn = 1;
11045         }
11046         else {
11047             DisplayError(_("Game number out of range"), 0);
11048             return FALSE;
11049         }
11050     } else {
11051         GameListDestroy();
11052         if (fseek(f, 0, 0) == -1) {
11053             if (f == lastLoadGameFP ?
11054                 gameNumber == lastLoadGameNumber + 1 :
11055                 gameNumber == 1) {
11056                 gn = 1;
11057             } else {
11058                 DisplayError(_("Can't seek on game file"), 0);
11059                 return FALSE;
11060             }
11061         }
11062     }
11063     lastLoadGameFP = f;
11064     lastLoadGameNumber = gameNumber;
11065     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11066     lastLoadGameUseList = useList;
11067
11068     yynewfile(f);
11069
11070     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11071       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11072                 lg->gameInfo.black);
11073             DisplayTitle(buf);
11074     } else if (*title != NULLCHAR) {
11075         if (gameNumber > 1) {
11076           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11077             DisplayTitle(buf);
11078         } else {
11079             DisplayTitle(title);
11080         }
11081     }
11082
11083     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11084         gameMode = PlayFromGameFile;
11085         ModeHighlight();
11086     }
11087
11088     currentMove = forwardMostMove = backwardMostMove = 0;
11089     CopyBoard(boards[0], initialPosition);
11090     StopClocks();
11091
11092     /*
11093      * Skip the first gn-1 games in the file.
11094      * Also skip over anything that precedes an identifiable
11095      * start of game marker, to avoid being confused by
11096      * garbage at the start of the file.  Currently
11097      * recognized start of game markers are the move number "1",
11098      * the pattern "gnuchess .* game", the pattern
11099      * "^[#;%] [^ ]* game file", and a PGN tag block.
11100      * A game that starts with one of the latter two patterns
11101      * will also have a move number 1, possibly
11102      * following a position diagram.
11103      * 5-4-02: Let's try being more lenient and allowing a game to
11104      * start with an unnumbered move.  Does that break anything?
11105      */
11106     cm = lastLoadGameStart = EndOfFile;
11107     while (gn > 0) {
11108         yyboardindex = forwardMostMove;
11109         cm = (ChessMove) Myylex();
11110         switch (cm) {
11111           case EndOfFile:
11112             if (cmailMsgLoaded) {
11113                 nCmailGames = CMAIL_MAX_GAMES - gn;
11114             } else {
11115                 Reset(TRUE, TRUE);
11116                 DisplayError(_("Game not found in file"), 0);
11117             }
11118             return FALSE;
11119
11120           case GNUChessGame:
11121           case XBoardGame:
11122             gn--;
11123             lastLoadGameStart = cm;
11124             break;
11125
11126           case MoveNumberOne:
11127             switch (lastLoadGameStart) {
11128               case GNUChessGame:
11129               case XBoardGame:
11130               case PGNTag:
11131                 break;
11132               case MoveNumberOne:
11133               case EndOfFile:
11134                 gn--;           /* count this game */
11135                 lastLoadGameStart = cm;
11136                 break;
11137               default:
11138                 /* impossible */
11139                 break;
11140             }
11141             break;
11142
11143           case PGNTag:
11144             switch (lastLoadGameStart) {
11145               case GNUChessGame:
11146               case PGNTag:
11147               case MoveNumberOne:
11148               case EndOfFile:
11149                 gn--;           /* count this game */
11150                 lastLoadGameStart = cm;
11151                 break;
11152               case XBoardGame:
11153                 lastLoadGameStart = cm; /* game counted already */
11154                 break;
11155               default:
11156                 /* impossible */
11157                 break;
11158             }
11159             if (gn > 0) {
11160                 do {
11161                     yyboardindex = forwardMostMove;
11162                     cm = (ChessMove) Myylex();
11163                 } while (cm == PGNTag || cm == Comment);
11164             }
11165             break;
11166
11167           case WhiteWins:
11168           case BlackWins:
11169           case GameIsDrawn:
11170             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11171                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11172                     != CMAIL_OLD_RESULT) {
11173                     nCmailResults ++ ;
11174                     cmailResult[  CMAIL_MAX_GAMES
11175                                 - gn - 1] = CMAIL_OLD_RESULT;
11176                 }
11177             }
11178             break;
11179
11180           case NormalMove:
11181             /* Only a NormalMove can be at the start of a game
11182              * without a position diagram. */
11183             if (lastLoadGameStart == EndOfFile ) {
11184               gn--;
11185               lastLoadGameStart = MoveNumberOne;
11186             }
11187             break;
11188
11189           default:
11190             break;
11191         }
11192     }
11193
11194     if (appData.debugMode)
11195       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11196
11197     if (cm == XBoardGame) {
11198         /* Skip any header junk before position diagram and/or move 1 */
11199         for (;;) {
11200             yyboardindex = forwardMostMove;
11201             cm = (ChessMove) Myylex();
11202
11203             if (cm == EndOfFile ||
11204                 cm == GNUChessGame || cm == XBoardGame) {
11205                 /* Empty game; pretend end-of-file and handle later */
11206                 cm = EndOfFile;
11207                 break;
11208             }
11209
11210             if (cm == MoveNumberOne || cm == PositionDiagram ||
11211                 cm == PGNTag || cm == Comment)
11212               break;
11213         }
11214     } else if (cm == GNUChessGame) {
11215         if (gameInfo.event != NULL) {
11216             free(gameInfo.event);
11217         }
11218         gameInfo.event = StrSave(yy_text);
11219     }
11220
11221     startedFromSetupPosition = FALSE;
11222     while (cm == PGNTag) {
11223         if (appData.debugMode)
11224           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11225         err = ParsePGNTag(yy_text, &gameInfo);
11226         if (!err) numPGNTags++;
11227
11228         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11229         if(gameInfo.variant != oldVariant) {
11230             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11231             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11232             InitPosition(TRUE);
11233             oldVariant = gameInfo.variant;
11234             if (appData.debugMode)
11235               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11236         }
11237
11238
11239         if (gameInfo.fen != NULL) {
11240           Board initial_position;
11241           startedFromSetupPosition = TRUE;
11242           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11243             Reset(TRUE, TRUE);
11244             DisplayError(_("Bad FEN position in file"), 0);
11245             return FALSE;
11246           }
11247           CopyBoard(boards[0], initial_position);
11248           if (blackPlaysFirst) {
11249             currentMove = forwardMostMove = backwardMostMove = 1;
11250             CopyBoard(boards[1], initial_position);
11251             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11252             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11253             timeRemaining[0][1] = whiteTimeRemaining;
11254             timeRemaining[1][1] = blackTimeRemaining;
11255             if (commentList[0] != NULL) {
11256               commentList[1] = commentList[0];
11257               commentList[0] = NULL;
11258             }
11259           } else {
11260             currentMove = forwardMostMove = backwardMostMove = 0;
11261           }
11262           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11263           {   int i;
11264               initialRulePlies = FENrulePlies;
11265               for( i=0; i< nrCastlingRights; i++ )
11266                   initialRights[i] = initial_position[CASTLING][i];
11267           }
11268           yyboardindex = forwardMostMove;
11269           free(gameInfo.fen);
11270           gameInfo.fen = NULL;
11271         }
11272
11273         yyboardindex = forwardMostMove;
11274         cm = (ChessMove) Myylex();
11275
11276         /* Handle comments interspersed among the tags */
11277         while (cm == Comment) {
11278             char *p;
11279             if (appData.debugMode)
11280               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11281             p = yy_text;
11282             AppendComment(currentMove, p, FALSE);
11283             yyboardindex = forwardMostMove;
11284             cm = (ChessMove) Myylex();
11285         }
11286     }
11287
11288     /* don't rely on existence of Event tag since if game was
11289      * pasted from clipboard the Event tag may not exist
11290      */
11291     if (numPGNTags > 0){
11292         char *tags;
11293         if (gameInfo.variant == VariantNormal) {
11294           VariantClass v = StringToVariant(gameInfo.event);
11295           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11296           if(v < VariantShogi) gameInfo.variant = v;
11297         }
11298         if (!matchMode) {
11299           if( appData.autoDisplayTags ) {
11300             tags = PGNTags(&gameInfo);
11301             TagsPopUp(tags, CmailMsg());
11302             free(tags);
11303           }
11304         }
11305     } else {
11306         /* Make something up, but don't display it now */
11307         SetGameInfo();
11308         TagsPopDown();
11309     }
11310
11311     if (cm == PositionDiagram) {
11312         int i, j;
11313         char *p;
11314         Board initial_position;
11315
11316         if (appData.debugMode)
11317           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11318
11319         if (!startedFromSetupPosition) {
11320             p = yy_text;
11321             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11322               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11323                 switch (*p) {
11324                   case '{':
11325                   case '[':
11326                   case '-':
11327                   case ' ':
11328                   case '\t':
11329                   case '\n':
11330                   case '\r':
11331                     break;
11332                   default:
11333                     initial_position[i][j++] = CharToPiece(*p);
11334                     break;
11335                 }
11336             while (*p == ' ' || *p == '\t' ||
11337                    *p == '\n' || *p == '\r') p++;
11338
11339             if (strncmp(p, "black", strlen("black"))==0)
11340               blackPlaysFirst = TRUE;
11341             else
11342               blackPlaysFirst = FALSE;
11343             startedFromSetupPosition = TRUE;
11344
11345             CopyBoard(boards[0], initial_position);
11346             if (blackPlaysFirst) {
11347                 currentMove = forwardMostMove = backwardMostMove = 1;
11348                 CopyBoard(boards[1], initial_position);
11349                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11350                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11351                 timeRemaining[0][1] = whiteTimeRemaining;
11352                 timeRemaining[1][1] = blackTimeRemaining;
11353                 if (commentList[0] != NULL) {
11354                     commentList[1] = commentList[0];
11355                     commentList[0] = NULL;
11356                 }
11357             } else {
11358                 currentMove = forwardMostMove = backwardMostMove = 0;
11359             }
11360         }
11361         yyboardindex = forwardMostMove;
11362         cm = (ChessMove) Myylex();
11363     }
11364
11365     if (first.pr == NoProc) {
11366         StartChessProgram(&first);
11367     }
11368     InitChessProgram(&first, FALSE);
11369     SendToProgram("force\n", &first);
11370     if (startedFromSetupPosition) {
11371         SendBoard(&first, forwardMostMove);
11372     if (appData.debugMode) {
11373         fprintf(debugFP, "Load Game\n");
11374     }
11375         DisplayBothClocks();
11376     }
11377
11378     /* [HGM] server: flag to write setup moves in broadcast file as one */
11379     loadFlag = appData.suppressLoadMoves;
11380
11381     while (cm == Comment) {
11382         char *p;
11383         if (appData.debugMode)
11384           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11385         p = yy_text;
11386         AppendComment(currentMove, p, FALSE);
11387         yyboardindex = forwardMostMove;
11388         cm = (ChessMove) Myylex();
11389     }
11390
11391     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11392         cm == WhiteWins || cm == BlackWins ||
11393         cm == GameIsDrawn || cm == GameUnfinished) {
11394         DisplayMessage("", _("No moves in game"));
11395         if (cmailMsgLoaded) {
11396             if (appData.debugMode)
11397               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11398             ClearHighlights();
11399             flipView = FALSE;
11400         }
11401         DrawPosition(FALSE, boards[currentMove]);
11402         DisplayBothClocks();
11403         gameMode = EditGame;
11404         ModeHighlight();
11405         gameFileFP = NULL;
11406         cmailOldMove = 0;
11407         return TRUE;
11408     }
11409
11410     // [HGM] PV info: routine tests if comment empty
11411     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11412         DisplayComment(currentMove - 1, commentList[currentMove]);
11413     }
11414     if (!matchMode && appData.timeDelay != 0)
11415       DrawPosition(FALSE, boards[currentMove]);
11416
11417     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11418       programStats.ok_to_send = 1;
11419     }
11420
11421     /* if the first token after the PGN tags is a move
11422      * and not move number 1, retrieve it from the parser
11423      */
11424     if (cm != MoveNumberOne)
11425         LoadGameOneMove(cm);
11426
11427     /* load the remaining moves from the file */
11428     while (LoadGameOneMove(EndOfFile)) {
11429       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11430       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11431     }
11432
11433     /* rewind to the start of the game */
11434     currentMove = backwardMostMove;
11435
11436     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11437
11438     if (oldGameMode == AnalyzeFile ||
11439         oldGameMode == AnalyzeMode) {
11440       AnalyzeFileEvent();
11441     }
11442
11443     if (matchMode || appData.timeDelay == 0) {
11444       ToEndEvent();
11445       gameMode = EditGame;
11446       ModeHighlight();
11447     } else if (appData.timeDelay > 0) {
11448       AutoPlayGameLoop();
11449     }
11450
11451     if (appData.debugMode)
11452         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11453
11454     loadFlag = 0; /* [HGM] true game starts */
11455     return TRUE;
11456 }
11457
11458 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11459 int
11460 ReloadPosition(offset)
11461      int offset;
11462 {
11463     int positionNumber = lastLoadPositionNumber + offset;
11464     if (lastLoadPositionFP == NULL) {
11465         DisplayError(_("No position has been loaded yet"), 0);
11466         return FALSE;
11467     }
11468     if (positionNumber <= 0) {
11469         DisplayError(_("Can't back up any further"), 0);
11470         return FALSE;
11471     }
11472     return LoadPosition(lastLoadPositionFP, positionNumber,
11473                         lastLoadPositionTitle);
11474 }
11475
11476 /* Load the nth position from the given file */
11477 int
11478 LoadPositionFromFile(filename, n, title)
11479      char *filename;
11480      int n;
11481      char *title;
11482 {
11483     FILE *f;
11484     char buf[MSG_SIZ];
11485
11486     if (strcmp(filename, "-") == 0) {
11487         return LoadPosition(stdin, n, "stdin");
11488     } else {
11489         f = fopen(filename, "rb");
11490         if (f == NULL) {
11491             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11492             DisplayError(buf, errno);
11493             return FALSE;
11494         } else {
11495             return LoadPosition(f, n, title);
11496         }
11497     }
11498 }
11499
11500 /* Load the nth position from the given open file, and close it */
11501 int
11502 LoadPosition(f, positionNumber, title)
11503      FILE *f;
11504      int positionNumber;
11505      char *title;
11506 {
11507     char *p, line[MSG_SIZ];
11508     Board initial_position;
11509     int i, j, fenMode, pn;
11510
11511     if (gameMode == Training )
11512         SetTrainingModeOff();
11513
11514     if (gameMode != BeginningOfGame) {
11515         Reset(FALSE, TRUE);
11516     }
11517     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11518         fclose(lastLoadPositionFP);
11519     }
11520     if (positionNumber == 0) positionNumber = 1;
11521     lastLoadPositionFP = f;
11522     lastLoadPositionNumber = positionNumber;
11523     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11524     if (first.pr == NoProc) {
11525       StartChessProgram(&first);
11526       InitChessProgram(&first, FALSE);
11527     }
11528     pn = positionNumber;
11529     if (positionNumber < 0) {
11530         /* Negative position number means to seek to that byte offset */
11531         if (fseek(f, -positionNumber, 0) == -1) {
11532             DisplayError(_("Can't seek on position file"), 0);
11533             return FALSE;
11534         };
11535         pn = 1;
11536     } else {
11537         if (fseek(f, 0, 0) == -1) {
11538             if (f == lastLoadPositionFP ?
11539                 positionNumber == lastLoadPositionNumber + 1 :
11540                 positionNumber == 1) {
11541                 pn = 1;
11542             } else {
11543                 DisplayError(_("Can't seek on position file"), 0);
11544                 return FALSE;
11545             }
11546         }
11547     }
11548     /* See if this file is FEN or old-style xboard */
11549     if (fgets(line, MSG_SIZ, f) == NULL) {
11550         DisplayError(_("Position not found in file"), 0);
11551         return FALSE;
11552     }
11553     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11554     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11555
11556     if (pn >= 2) {
11557         if (fenMode || line[0] == '#') pn--;
11558         while (pn > 0) {
11559             /* skip positions before number pn */
11560             if (fgets(line, MSG_SIZ, f) == NULL) {
11561                 Reset(TRUE, TRUE);
11562                 DisplayError(_("Position not found in file"), 0);
11563                 return FALSE;
11564             }
11565             if (fenMode || line[0] == '#') pn--;
11566         }
11567     }
11568
11569     if (fenMode) {
11570         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11571             DisplayError(_("Bad FEN position in file"), 0);
11572             return FALSE;
11573         }
11574     } else {
11575         (void) fgets(line, MSG_SIZ, f);
11576         (void) fgets(line, MSG_SIZ, f);
11577
11578         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11579             (void) fgets(line, MSG_SIZ, f);
11580             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11581                 if (*p == ' ')
11582                   continue;
11583                 initial_position[i][j++] = CharToPiece(*p);
11584             }
11585         }
11586
11587         blackPlaysFirst = FALSE;
11588         if (!feof(f)) {
11589             (void) fgets(line, MSG_SIZ, f);
11590             if (strncmp(line, "black", strlen("black"))==0)
11591               blackPlaysFirst = TRUE;
11592         }
11593     }
11594     startedFromSetupPosition = TRUE;
11595
11596     SendToProgram("force\n", &first);
11597     CopyBoard(boards[0], initial_position);
11598     if (blackPlaysFirst) {
11599         currentMove = forwardMostMove = backwardMostMove = 1;
11600         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11601         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11602         CopyBoard(boards[1], initial_position);
11603         DisplayMessage("", _("Black to play"));
11604     } else {
11605         currentMove = forwardMostMove = backwardMostMove = 0;
11606         DisplayMessage("", _("White to play"));
11607     }
11608     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11609     SendBoard(&first, forwardMostMove);
11610     if (appData.debugMode) {
11611 int i, j;
11612   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11613   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11614         fprintf(debugFP, "Load Position\n");
11615     }
11616
11617     if (positionNumber > 1) {
11618       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11619         DisplayTitle(line);
11620     } else {
11621         DisplayTitle(title);
11622     }
11623     gameMode = EditGame;
11624     ModeHighlight();
11625     ResetClocks();
11626     timeRemaining[0][1] = whiteTimeRemaining;
11627     timeRemaining[1][1] = blackTimeRemaining;
11628     DrawPosition(FALSE, boards[currentMove]);
11629
11630     return TRUE;
11631 }
11632
11633
11634 void
11635 CopyPlayerNameIntoFileName(dest, src)
11636      char **dest, *src;
11637 {
11638     while (*src != NULLCHAR && *src != ',') {
11639         if (*src == ' ') {
11640             *(*dest)++ = '_';
11641             src++;
11642         } else {
11643             *(*dest)++ = *src++;
11644         }
11645     }
11646 }
11647
11648 char *DefaultFileName(ext)
11649      char *ext;
11650 {
11651     static char def[MSG_SIZ];
11652     char *p;
11653
11654     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11655         p = def;
11656         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11657         *p++ = '-';
11658         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11659         *p++ = '.';
11660         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11661     } else {
11662         def[0] = NULLCHAR;
11663     }
11664     return def;
11665 }
11666
11667 /* Save the current game to the given file */
11668 int
11669 SaveGameToFile(filename, append)
11670      char *filename;
11671      int append;
11672 {
11673     FILE *f;
11674     char buf[MSG_SIZ];
11675     int result;
11676
11677     if (strcmp(filename, "-") == 0) {
11678         return SaveGame(stdout, 0, NULL);
11679     } else {
11680         f = fopen(filename, append ? "a" : "w");
11681         if (f == NULL) {
11682             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11683             DisplayError(buf, errno);
11684             return FALSE;
11685         } else {
11686             safeStrCpy(buf, lastMsg, MSG_SIZ);
11687             DisplayMessage(_("Waiting for access to save file"), "");
11688             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11689             DisplayMessage(_("Saving game"), "");
11690             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11691             result = SaveGame(f, 0, NULL);
11692             DisplayMessage(buf, "");
11693             return result;
11694         }
11695     }
11696 }
11697
11698 char *
11699 SavePart(str)
11700      char *str;
11701 {
11702     static char buf[MSG_SIZ];
11703     char *p;
11704
11705     p = strchr(str, ' ');
11706     if (p == NULL) return str;
11707     strncpy(buf, str, p - str);
11708     buf[p - str] = NULLCHAR;
11709     return buf;
11710 }
11711
11712 #define PGN_MAX_LINE 75
11713
11714 #define PGN_SIDE_WHITE  0
11715 #define PGN_SIDE_BLACK  1
11716
11717 /* [AS] */
11718 static int FindFirstMoveOutOfBook( int side )
11719 {
11720     int result = -1;
11721
11722     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11723         int index = backwardMostMove;
11724         int has_book_hit = 0;
11725
11726         if( (index % 2) != side ) {
11727             index++;
11728         }
11729
11730         while( index < forwardMostMove ) {
11731             /* Check to see if engine is in book */
11732             int depth = pvInfoList[index].depth;
11733             int score = pvInfoList[index].score;
11734             int in_book = 0;
11735
11736             if( depth <= 2 ) {
11737                 in_book = 1;
11738             }
11739             else if( score == 0 && depth == 63 ) {
11740                 in_book = 1; /* Zappa */
11741             }
11742             else if( score == 2 && depth == 99 ) {
11743                 in_book = 1; /* Abrok */
11744             }
11745
11746             has_book_hit += in_book;
11747
11748             if( ! in_book ) {
11749                 result = index;
11750
11751                 break;
11752             }
11753
11754             index += 2;
11755         }
11756     }
11757
11758     return result;
11759 }
11760
11761 /* [AS] */
11762 void GetOutOfBookInfo( char * buf )
11763 {
11764     int oob[2];
11765     int i;
11766     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11767
11768     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11769     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11770
11771     *buf = '\0';
11772
11773     if( oob[0] >= 0 || oob[1] >= 0 ) {
11774         for( i=0; i<2; i++ ) {
11775             int idx = oob[i];
11776
11777             if( idx >= 0 ) {
11778                 if( i > 0 && oob[0] >= 0 ) {
11779                     strcat( buf, "   " );
11780                 }
11781
11782                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11783                 sprintf( buf+strlen(buf), "%s%.2f",
11784                     pvInfoList[idx].score >= 0 ? "+" : "",
11785                     pvInfoList[idx].score / 100.0 );
11786             }
11787         }
11788     }
11789 }
11790
11791 /* Save game in PGN style and close the file */
11792 int
11793 SaveGamePGN(f)
11794      FILE *f;
11795 {
11796     int i, offset, linelen, newblock;
11797     time_t tm;
11798 //    char *movetext;
11799     char numtext[32];
11800     int movelen, numlen, blank;
11801     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11802
11803     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11804
11805     tm = time((time_t *) NULL);
11806
11807     PrintPGNTags(f, &gameInfo);
11808
11809     if (backwardMostMove > 0 || startedFromSetupPosition) {
11810         char *fen = PositionToFEN(backwardMostMove, NULL);
11811         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11812         fprintf(f, "\n{--------------\n");
11813         PrintPosition(f, backwardMostMove);
11814         fprintf(f, "--------------}\n");
11815         free(fen);
11816     }
11817     else {
11818         /* [AS] Out of book annotation */
11819         if( appData.saveOutOfBookInfo ) {
11820             char buf[64];
11821
11822             GetOutOfBookInfo( buf );
11823
11824             if( buf[0] != '\0' ) {
11825                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11826             }
11827         }
11828
11829         fprintf(f, "\n");
11830     }
11831
11832     i = backwardMostMove;
11833     linelen = 0;
11834     newblock = TRUE;
11835
11836     while (i < forwardMostMove) {
11837         /* Print comments preceding this move */
11838         if (commentList[i] != NULL) {
11839             if (linelen > 0) fprintf(f, "\n");
11840             fprintf(f, "%s", commentList[i]);
11841             linelen = 0;
11842             newblock = TRUE;
11843         }
11844
11845         /* Format move number */
11846         if ((i % 2) == 0)
11847           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11848         else
11849           if (newblock)
11850             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11851           else
11852             numtext[0] = NULLCHAR;
11853
11854         numlen = strlen(numtext);
11855         newblock = FALSE;
11856
11857         /* Print move number */
11858         blank = linelen > 0 && numlen > 0;
11859         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11860             fprintf(f, "\n");
11861             linelen = 0;
11862             blank = 0;
11863         }
11864         if (blank) {
11865             fprintf(f, " ");
11866             linelen++;
11867         }
11868         fprintf(f, "%s", numtext);
11869         linelen += numlen;
11870
11871         /* Get move */
11872         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11873         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11874
11875         /* Print move */
11876         blank = linelen > 0 && movelen > 0;
11877         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11878             fprintf(f, "\n");
11879             linelen = 0;
11880             blank = 0;
11881         }
11882         if (blank) {
11883             fprintf(f, " ");
11884             linelen++;
11885         }
11886         fprintf(f, "%s", move_buffer);
11887         linelen += movelen;
11888
11889         /* [AS] Add PV info if present */
11890         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11891             /* [HGM] add time */
11892             char buf[MSG_SIZ]; int seconds;
11893
11894             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11895
11896             if( seconds <= 0)
11897               buf[0] = 0;
11898             else
11899               if( seconds < 30 )
11900                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11901               else
11902                 {
11903                   seconds = (seconds + 4)/10; // round to full seconds
11904                   if( seconds < 60 )
11905                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11906                   else
11907                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11908                 }
11909
11910             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11911                       pvInfoList[i].score >= 0 ? "+" : "",
11912                       pvInfoList[i].score / 100.0,
11913                       pvInfoList[i].depth,
11914                       buf );
11915
11916             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11917
11918             /* Print score/depth */
11919             blank = linelen > 0 && movelen > 0;
11920             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11921                 fprintf(f, "\n");
11922                 linelen = 0;
11923                 blank = 0;
11924             }
11925             if (blank) {
11926                 fprintf(f, " ");
11927                 linelen++;
11928             }
11929             fprintf(f, "%s", move_buffer);
11930             linelen += movelen;
11931         }
11932
11933         i++;
11934     }
11935
11936     /* Start a new line */
11937     if (linelen > 0) fprintf(f, "\n");
11938
11939     /* Print comments after last move */
11940     if (commentList[i] != NULL) {
11941         fprintf(f, "%s\n", commentList[i]);
11942     }
11943
11944     /* Print result */
11945     if (gameInfo.resultDetails != NULL &&
11946         gameInfo.resultDetails[0] != NULLCHAR) {
11947         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11948                 PGNResult(gameInfo.result));
11949     } else {
11950         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11951     }
11952
11953     fclose(f);
11954     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11955     return TRUE;
11956 }
11957
11958 /* Save game in old style and close the file */
11959 int
11960 SaveGameOldStyle(f)
11961      FILE *f;
11962 {
11963     int i, offset;
11964     time_t tm;
11965
11966     tm = time((time_t *) NULL);
11967
11968     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11969     PrintOpponents(f);
11970
11971     if (backwardMostMove > 0 || startedFromSetupPosition) {
11972         fprintf(f, "\n[--------------\n");
11973         PrintPosition(f, backwardMostMove);
11974         fprintf(f, "--------------]\n");
11975     } else {
11976         fprintf(f, "\n");
11977     }
11978
11979     i = backwardMostMove;
11980     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11981
11982     while (i < forwardMostMove) {
11983         if (commentList[i] != NULL) {
11984             fprintf(f, "[%s]\n", commentList[i]);
11985         }
11986
11987         if ((i % 2) == 1) {
11988             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11989             i++;
11990         } else {
11991             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11992             i++;
11993             if (commentList[i] != NULL) {
11994                 fprintf(f, "\n");
11995                 continue;
11996             }
11997             if (i >= forwardMostMove) {
11998                 fprintf(f, "\n");
11999                 break;
12000             }
12001             fprintf(f, "%s\n", parseList[i]);
12002             i++;
12003         }
12004     }
12005
12006     if (commentList[i] != NULL) {
12007         fprintf(f, "[%s]\n", commentList[i]);
12008     }
12009
12010     /* This isn't really the old style, but it's close enough */
12011     if (gameInfo.resultDetails != NULL &&
12012         gameInfo.resultDetails[0] != NULLCHAR) {
12013         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12014                 gameInfo.resultDetails);
12015     } else {
12016         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12017     }
12018
12019     fclose(f);
12020     return TRUE;
12021 }
12022
12023 /* Save the current game to open file f and close the file */
12024 int
12025 SaveGame(f, dummy, dummy2)
12026      FILE *f;
12027      int dummy;
12028      char *dummy2;
12029 {
12030     if (gameMode == EditPosition) EditPositionDone(TRUE);
12031     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12032     if (appData.oldSaveStyle)
12033       return SaveGameOldStyle(f);
12034     else
12035       return SaveGamePGN(f);
12036 }
12037
12038 /* Save the current position to the given file */
12039 int
12040 SavePositionToFile(filename)
12041      char *filename;
12042 {
12043     FILE *f;
12044     char buf[MSG_SIZ];
12045
12046     if (strcmp(filename, "-") == 0) {
12047         return SavePosition(stdout, 0, NULL);
12048     } else {
12049         f = fopen(filename, "a");
12050         if (f == NULL) {
12051             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12052             DisplayError(buf, errno);
12053             return FALSE;
12054         } else {
12055             safeStrCpy(buf, lastMsg, MSG_SIZ);
12056             DisplayMessage(_("Waiting for access to save file"), "");
12057             flock(fileno(f), LOCK_EX); // [HGM] lock
12058             DisplayMessage(_("Saving position"), "");
12059             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12060             SavePosition(f, 0, NULL);
12061             DisplayMessage(buf, "");
12062             return TRUE;
12063         }
12064     }
12065 }
12066
12067 /* Save the current position to the given open file and close the file */
12068 int
12069 SavePosition(f, dummy, dummy2)
12070      FILE *f;
12071      int dummy;
12072      char *dummy2;
12073 {
12074     time_t tm;
12075     char *fen;
12076
12077     if (gameMode == EditPosition) EditPositionDone(TRUE);
12078     if (appData.oldSaveStyle) {
12079         tm = time((time_t *) NULL);
12080
12081         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12082         PrintOpponents(f);
12083         fprintf(f, "[--------------\n");
12084         PrintPosition(f, currentMove);
12085         fprintf(f, "--------------]\n");
12086     } else {
12087         fen = PositionToFEN(currentMove, NULL);
12088         fprintf(f, "%s\n", fen);
12089         free(fen);
12090     }
12091     fclose(f);
12092     return TRUE;
12093 }
12094
12095 void
12096 ReloadCmailMsgEvent(unregister)
12097      int unregister;
12098 {
12099 #if !WIN32
12100     static char *inFilename = NULL;
12101     static char *outFilename;
12102     int i;
12103     struct stat inbuf, outbuf;
12104     int status;
12105
12106     /* Any registered moves are unregistered if unregister is set, */
12107     /* i.e. invoked by the signal handler */
12108     if (unregister) {
12109         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12110             cmailMoveRegistered[i] = FALSE;
12111             if (cmailCommentList[i] != NULL) {
12112                 free(cmailCommentList[i]);
12113                 cmailCommentList[i] = NULL;
12114             }
12115         }
12116         nCmailMovesRegistered = 0;
12117     }
12118
12119     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12120         cmailResult[i] = CMAIL_NOT_RESULT;
12121     }
12122     nCmailResults = 0;
12123
12124     if (inFilename == NULL) {
12125         /* Because the filenames are static they only get malloced once  */
12126         /* and they never get freed                                      */
12127         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12128         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12129
12130         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12131         sprintf(outFilename, "%s.out", appData.cmailGameName);
12132     }
12133
12134     status = stat(outFilename, &outbuf);
12135     if (status < 0) {
12136         cmailMailedMove = FALSE;
12137     } else {
12138         status = stat(inFilename, &inbuf);
12139         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12140     }
12141
12142     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12143        counts the games, notes how each one terminated, etc.
12144
12145        It would be nice to remove this kludge and instead gather all
12146        the information while building the game list.  (And to keep it
12147        in the game list nodes instead of having a bunch of fixed-size
12148        parallel arrays.)  Note this will require getting each game's
12149        termination from the PGN tags, as the game list builder does
12150        not process the game moves.  --mann
12151        */
12152     cmailMsgLoaded = TRUE;
12153     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12154
12155     /* Load first game in the file or popup game menu */
12156     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12157
12158 #endif /* !WIN32 */
12159     return;
12160 }
12161
12162 int
12163 RegisterMove()
12164 {
12165     FILE *f;
12166     char string[MSG_SIZ];
12167
12168     if (   cmailMailedMove
12169         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12170         return TRUE;            /* Allow free viewing  */
12171     }
12172
12173     /* Unregister move to ensure that we don't leave RegisterMove        */
12174     /* with the move registered when the conditions for registering no   */
12175     /* longer hold                                                       */
12176     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12177         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12178         nCmailMovesRegistered --;
12179
12180         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12181           {
12182               free(cmailCommentList[lastLoadGameNumber - 1]);
12183               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12184           }
12185     }
12186
12187     if (cmailOldMove == -1) {
12188         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12189         return FALSE;
12190     }
12191
12192     if (currentMove > cmailOldMove + 1) {
12193         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12194         return FALSE;
12195     }
12196
12197     if (currentMove < cmailOldMove) {
12198         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12199         return FALSE;
12200     }
12201
12202     if (forwardMostMove > currentMove) {
12203         /* Silently truncate extra moves */
12204         TruncateGame();
12205     }
12206
12207     if (   (currentMove == cmailOldMove + 1)
12208         || (   (currentMove == cmailOldMove)
12209             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12210                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12211         if (gameInfo.result != GameUnfinished) {
12212             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12213         }
12214
12215         if (commentList[currentMove] != NULL) {
12216             cmailCommentList[lastLoadGameNumber - 1]
12217               = StrSave(commentList[currentMove]);
12218         }
12219         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12220
12221         if (appData.debugMode)
12222           fprintf(debugFP, "Saving %s for game %d\n",
12223                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12224
12225         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12226
12227         f = fopen(string, "w");
12228         if (appData.oldSaveStyle) {
12229             SaveGameOldStyle(f); /* also closes the file */
12230
12231             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12232             f = fopen(string, "w");
12233             SavePosition(f, 0, NULL); /* also closes the file */
12234         } else {
12235             fprintf(f, "{--------------\n");
12236             PrintPosition(f, currentMove);
12237             fprintf(f, "--------------}\n\n");
12238
12239             SaveGame(f, 0, NULL); /* also closes the file*/
12240         }
12241
12242         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12243         nCmailMovesRegistered ++;
12244     } else if (nCmailGames == 1) {
12245         DisplayError(_("You have not made a move yet"), 0);
12246         return FALSE;
12247     }
12248
12249     return TRUE;
12250 }
12251
12252 void
12253 MailMoveEvent()
12254 {
12255 #if !WIN32
12256     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12257     FILE *commandOutput;
12258     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12259     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12260     int nBuffers;
12261     int i;
12262     int archived;
12263     char *arcDir;
12264
12265     if (! cmailMsgLoaded) {
12266         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12267         return;
12268     }
12269
12270     if (nCmailGames == nCmailResults) {
12271         DisplayError(_("No unfinished games"), 0);
12272         return;
12273     }
12274
12275 #if CMAIL_PROHIBIT_REMAIL
12276     if (cmailMailedMove) {
12277       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);
12278         DisplayError(msg, 0);
12279         return;
12280     }
12281 #endif
12282
12283     if (! (cmailMailedMove || RegisterMove())) return;
12284
12285     if (   cmailMailedMove
12286         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12287       snprintf(string, MSG_SIZ, partCommandString,
12288                appData.debugMode ? " -v" : "", appData.cmailGameName);
12289         commandOutput = popen(string, "r");
12290
12291         if (commandOutput == NULL) {
12292             DisplayError(_("Failed to invoke cmail"), 0);
12293         } else {
12294             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12295                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12296             }
12297             if (nBuffers > 1) {
12298                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12299                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12300                 nBytes = MSG_SIZ - 1;
12301             } else {
12302                 (void) memcpy(msg, buffer, nBytes);
12303             }
12304             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12305
12306             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12307                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12308
12309                 archived = TRUE;
12310                 for (i = 0; i < nCmailGames; i ++) {
12311                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12312                         archived = FALSE;
12313                     }
12314                 }
12315                 if (   archived
12316                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12317                         != NULL)) {
12318                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12319                            arcDir,
12320                            appData.cmailGameName,
12321                            gameInfo.date);
12322                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12323                     cmailMsgLoaded = FALSE;
12324                 }
12325             }
12326
12327             DisplayInformation(msg);
12328             pclose(commandOutput);
12329         }
12330     } else {
12331         if ((*cmailMsg) != '\0') {
12332             DisplayInformation(cmailMsg);
12333         }
12334     }
12335
12336     return;
12337 #endif /* !WIN32 */
12338 }
12339
12340 char *
12341 CmailMsg()
12342 {
12343 #if WIN32
12344     return NULL;
12345 #else
12346     int  prependComma = 0;
12347     char number[5];
12348     char string[MSG_SIZ];       /* Space for game-list */
12349     int  i;
12350
12351     if (!cmailMsgLoaded) return "";
12352
12353     if (cmailMailedMove) {
12354       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12355     } else {
12356         /* Create a list of games left */
12357       snprintf(string, MSG_SIZ, "[");
12358         for (i = 0; i < nCmailGames; i ++) {
12359             if (! (   cmailMoveRegistered[i]
12360                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12361                 if (prependComma) {
12362                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12363                 } else {
12364                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12365                     prependComma = 1;
12366                 }
12367
12368                 strcat(string, number);
12369             }
12370         }
12371         strcat(string, "]");
12372
12373         if (nCmailMovesRegistered + nCmailResults == 0) {
12374             switch (nCmailGames) {
12375               case 1:
12376                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12377                 break;
12378
12379               case 2:
12380                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12381                 break;
12382
12383               default:
12384                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12385                          nCmailGames);
12386                 break;
12387             }
12388         } else {
12389             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12390               case 1:
12391                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12392                          string);
12393                 break;
12394
12395               case 0:
12396                 if (nCmailResults == nCmailGames) {
12397                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12398                 } else {
12399                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12400                 }
12401                 break;
12402
12403               default:
12404                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12405                          string);
12406             }
12407         }
12408     }
12409     return cmailMsg;
12410 #endif /* WIN32 */
12411 }
12412
12413 void
12414 ResetGameEvent()
12415 {
12416     if (gameMode == Training)
12417       SetTrainingModeOff();
12418
12419     Reset(TRUE, TRUE);
12420     cmailMsgLoaded = FALSE;
12421     if (appData.icsActive) {
12422       SendToICS(ics_prefix);
12423       SendToICS("refresh\n");
12424     }
12425 }
12426
12427 void
12428 ExitEvent(status)
12429      int status;
12430 {
12431     exiting++;
12432     if (exiting > 2) {
12433       /* Give up on clean exit */
12434       exit(status);
12435     }
12436     if (exiting > 1) {
12437       /* Keep trying for clean exit */
12438       return;
12439     }
12440
12441     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12442
12443     if (telnetISR != NULL) {
12444       RemoveInputSource(telnetISR);
12445     }
12446     if (icsPR != NoProc) {
12447       DestroyChildProcess(icsPR, TRUE);
12448     }
12449
12450     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12451     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12452
12453     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12454     /* make sure this other one finishes before killing it!                  */
12455     if(endingGame) { int count = 0;
12456         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12457         while(endingGame && count++ < 10) DoSleep(1);
12458         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12459     }
12460
12461     /* Kill off chess programs */
12462     if (first.pr != NoProc) {
12463         ExitAnalyzeMode();
12464
12465         DoSleep( appData.delayBeforeQuit );
12466         SendToProgram("quit\n", &first);
12467         DoSleep( appData.delayAfterQuit );
12468         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12469     }
12470     if (second.pr != NoProc) {
12471         DoSleep( appData.delayBeforeQuit );
12472         SendToProgram("quit\n", &second);
12473         DoSleep( appData.delayAfterQuit );
12474         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12475     }
12476     if (first.isr != NULL) {
12477         RemoveInputSource(first.isr);
12478     }
12479     if (second.isr != NULL) {
12480         RemoveInputSource(second.isr);
12481     }
12482
12483     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12484     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12485
12486     ShutDownFrontEnd();
12487     exit(status);
12488 }
12489
12490 void
12491 PauseEvent()
12492 {
12493     if (appData.debugMode)
12494         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12495     if (pausing) {
12496         pausing = FALSE;
12497         ModeHighlight();
12498         if (gameMode == MachinePlaysWhite ||
12499             gameMode == MachinePlaysBlack) {
12500             StartClocks();
12501         } else {
12502             DisplayBothClocks();
12503         }
12504         if (gameMode == PlayFromGameFile) {
12505             if (appData.timeDelay >= 0)
12506                 AutoPlayGameLoop();
12507         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12508             Reset(FALSE, TRUE);
12509             SendToICS(ics_prefix);
12510             SendToICS("refresh\n");
12511         } else if (currentMove < forwardMostMove) {
12512             ForwardInner(forwardMostMove);
12513         }
12514         pauseExamInvalid = FALSE;
12515     } else {
12516         switch (gameMode) {
12517           default:
12518             return;
12519           case IcsExamining:
12520             pauseExamForwardMostMove = forwardMostMove;
12521             pauseExamInvalid = FALSE;
12522             /* fall through */
12523           case IcsObserving:
12524           case IcsPlayingWhite:
12525           case IcsPlayingBlack:
12526             pausing = TRUE;
12527             ModeHighlight();
12528             return;
12529           case PlayFromGameFile:
12530             (void) StopLoadGameTimer();
12531             pausing = TRUE;
12532             ModeHighlight();
12533             break;
12534           case BeginningOfGame:
12535             if (appData.icsActive) return;
12536             /* else fall through */
12537           case MachinePlaysWhite:
12538           case MachinePlaysBlack:
12539           case TwoMachinesPlay:
12540             if (forwardMostMove == 0)
12541               return;           /* don't pause if no one has moved */
12542             if ((gameMode == MachinePlaysWhite &&
12543                  !WhiteOnMove(forwardMostMove)) ||
12544                 (gameMode == MachinePlaysBlack &&
12545                  WhiteOnMove(forwardMostMove))) {
12546                 StopClocks();
12547             }
12548             pausing = TRUE;
12549             ModeHighlight();
12550             break;
12551         }
12552     }
12553 }
12554
12555 void
12556 EditCommentEvent()
12557 {
12558     char title[MSG_SIZ];
12559
12560     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12561       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12562     } else {
12563       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12564                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12565                parseList[currentMove - 1]);
12566     }
12567
12568     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12569 }
12570
12571
12572 void
12573 EditTagsEvent()
12574 {
12575     char *tags = PGNTags(&gameInfo);
12576     bookUp = FALSE;
12577     EditTagsPopUp(tags, NULL);
12578     free(tags);
12579 }
12580
12581 void
12582 AnalyzeModeEvent()
12583 {
12584     if (appData.noChessProgram || gameMode == AnalyzeMode)
12585       return;
12586
12587     if (gameMode != AnalyzeFile) {
12588         if (!appData.icsEngineAnalyze) {
12589                EditGameEvent();
12590                if (gameMode != EditGame) return;
12591         }
12592         ResurrectChessProgram();
12593         SendToProgram("analyze\n", &first);
12594         first.analyzing = TRUE;
12595         /*first.maybeThinking = TRUE;*/
12596         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12597         EngineOutputPopUp();
12598     }
12599     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12600     pausing = FALSE;
12601     ModeHighlight();
12602     SetGameInfo();
12603
12604     StartAnalysisClock();
12605     GetTimeMark(&lastNodeCountTime);
12606     lastNodeCount = 0;
12607 }
12608
12609 void
12610 AnalyzeFileEvent()
12611 {
12612     if (appData.noChessProgram || gameMode == AnalyzeFile)
12613       return;
12614
12615     if (gameMode != AnalyzeMode) {
12616         EditGameEvent();
12617         if (gameMode != EditGame) return;
12618         ResurrectChessProgram();
12619         SendToProgram("analyze\n", &first);
12620         first.analyzing = TRUE;
12621         /*first.maybeThinking = TRUE;*/
12622         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12623         EngineOutputPopUp();
12624     }
12625     gameMode = AnalyzeFile;
12626     pausing = FALSE;
12627     ModeHighlight();
12628     SetGameInfo();
12629
12630     StartAnalysisClock();
12631     GetTimeMark(&lastNodeCountTime);
12632     lastNodeCount = 0;
12633 }
12634
12635 void
12636 MachineWhiteEvent()
12637 {
12638     char buf[MSG_SIZ];
12639     char *bookHit = NULL;
12640
12641     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12642       return;
12643
12644
12645     if (gameMode == PlayFromGameFile ||
12646         gameMode == TwoMachinesPlay  ||
12647         gameMode == Training         ||
12648         gameMode == AnalyzeMode      ||
12649         gameMode == EndOfGame)
12650         EditGameEvent();
12651
12652     if (gameMode == EditPosition)
12653         EditPositionDone(TRUE);
12654
12655     if (!WhiteOnMove(currentMove)) {
12656         DisplayError(_("It is not White's turn"), 0);
12657         return;
12658     }
12659
12660     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12661       ExitAnalyzeMode();
12662
12663     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12664         gameMode == AnalyzeFile)
12665         TruncateGame();
12666
12667     ResurrectChessProgram();    /* in case it isn't running */
12668     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12669         gameMode = MachinePlaysWhite;
12670         ResetClocks();
12671     } else
12672     gameMode = MachinePlaysWhite;
12673     pausing = FALSE;
12674     ModeHighlight();
12675     SetGameInfo();
12676     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12677     DisplayTitle(buf);
12678     if (first.sendName) {
12679       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12680       SendToProgram(buf, &first);
12681     }
12682     if (first.sendTime) {
12683       if (first.useColors) {
12684         SendToProgram("black\n", &first); /*gnu kludge*/
12685       }
12686       SendTimeRemaining(&first, TRUE);
12687     }
12688     if (first.useColors) {
12689       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12690     }
12691     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12692     SetMachineThinkingEnables();
12693     first.maybeThinking = TRUE;
12694     StartClocks();
12695     firstMove = FALSE;
12696
12697     if (appData.autoFlipView && !flipView) {
12698       flipView = !flipView;
12699       DrawPosition(FALSE, NULL);
12700       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12701     }
12702
12703     if(bookHit) { // [HGM] book: simulate book reply
12704         static char bookMove[MSG_SIZ]; // a bit generous?
12705
12706         programStats.nodes = programStats.depth = programStats.time =
12707         programStats.score = programStats.got_only_move = 0;
12708         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12709
12710         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12711         strcat(bookMove, bookHit);
12712         HandleMachineMove(bookMove, &first);
12713     }
12714 }
12715
12716 void
12717 MachineBlackEvent()
12718 {
12719   char buf[MSG_SIZ];
12720   char *bookHit = NULL;
12721
12722     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12723         return;
12724
12725
12726     if (gameMode == PlayFromGameFile ||
12727         gameMode == TwoMachinesPlay  ||
12728         gameMode == Training         ||
12729         gameMode == AnalyzeMode      ||
12730         gameMode == EndOfGame)
12731         EditGameEvent();
12732
12733     if (gameMode == EditPosition)
12734         EditPositionDone(TRUE);
12735
12736     if (WhiteOnMove(currentMove)) {
12737         DisplayError(_("It is not Black's turn"), 0);
12738         return;
12739     }
12740
12741     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12742       ExitAnalyzeMode();
12743
12744     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12745         gameMode == AnalyzeFile)
12746         TruncateGame();
12747
12748     ResurrectChessProgram();    /* in case it isn't running */
12749     gameMode = MachinePlaysBlack;
12750     pausing = FALSE;
12751     ModeHighlight();
12752     SetGameInfo();
12753     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12754     DisplayTitle(buf);
12755     if (first.sendName) {
12756       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12757       SendToProgram(buf, &first);
12758     }
12759     if (first.sendTime) {
12760       if (first.useColors) {
12761         SendToProgram("white\n", &first); /*gnu kludge*/
12762       }
12763       SendTimeRemaining(&first, FALSE);
12764     }
12765     if (first.useColors) {
12766       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12767     }
12768     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12769     SetMachineThinkingEnables();
12770     first.maybeThinking = TRUE;
12771     StartClocks();
12772
12773     if (appData.autoFlipView && flipView) {
12774       flipView = !flipView;
12775       DrawPosition(FALSE, NULL);
12776       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12777     }
12778     if(bookHit) { // [HGM] book: simulate book reply
12779         static char bookMove[MSG_SIZ]; // a bit generous?
12780
12781         programStats.nodes = programStats.depth = programStats.time =
12782         programStats.score = programStats.got_only_move = 0;
12783         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12784
12785         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12786         strcat(bookMove, bookHit);
12787         HandleMachineMove(bookMove, &first);
12788     }
12789 }
12790
12791
12792 void
12793 DisplayTwoMachinesTitle()
12794 {
12795     char buf[MSG_SIZ];
12796     if (appData.matchGames > 0) {
12797         if(appData.tourneyFile[0]) {
12798           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12799                    gameInfo.white, gameInfo.black,
12800                    nextGame+1, appData.matchGames+1,
12801                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12802         } else 
12803         if (first.twoMachinesColor[0] == 'w') {
12804           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12805                    gameInfo.white, gameInfo.black,
12806                    first.matchWins, second.matchWins,
12807                    matchGame - 1 - (first.matchWins + second.matchWins));
12808         } else {
12809           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12810                    gameInfo.white, gameInfo.black,
12811                    second.matchWins, first.matchWins,
12812                    matchGame - 1 - (first.matchWins + second.matchWins));
12813         }
12814     } else {
12815       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12816     }
12817     DisplayTitle(buf);
12818 }
12819
12820 void
12821 SettingsMenuIfReady()
12822 {
12823   if (second.lastPing != second.lastPong) {
12824     DisplayMessage("", _("Waiting for second chess program"));
12825     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12826     return;
12827   }
12828   ThawUI();
12829   DisplayMessage("", "");
12830   SettingsPopUp(&second);
12831 }
12832
12833 int
12834 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12835 {
12836     char buf[MSG_SIZ];
12837     if (cps->pr == NULL) {
12838         StartChessProgram(cps);
12839         if (cps->protocolVersion == 1) {
12840           retry();
12841         } else {
12842           /* kludge: allow timeout for initial "feature" command */
12843           FreezeUI();
12844           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12845           DisplayMessage("", buf);
12846           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12847         }
12848         return 1;
12849     }
12850     return 0;
12851 }
12852
12853 void
12854 TwoMachinesEvent P((void))
12855 {
12856     int i;
12857     char buf[MSG_SIZ];
12858     ChessProgramState *onmove;
12859     char *bookHit = NULL;
12860     static int stalling = 0;
12861     TimeMark now;
12862     long wait;
12863
12864     if (appData.noChessProgram) return;
12865
12866     switch (gameMode) {
12867       case TwoMachinesPlay:
12868         return;
12869       case MachinePlaysWhite:
12870       case MachinePlaysBlack:
12871         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12872             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12873             return;
12874         }
12875         /* fall through */
12876       case BeginningOfGame:
12877       case PlayFromGameFile:
12878       case EndOfGame:
12879         EditGameEvent();
12880         if (gameMode != EditGame) return;
12881         break;
12882       case EditPosition:
12883         EditPositionDone(TRUE);
12884         break;
12885       case AnalyzeMode:
12886       case AnalyzeFile:
12887         ExitAnalyzeMode();
12888         break;
12889       case EditGame:
12890       default:
12891         break;
12892     }
12893
12894 //    forwardMostMove = currentMove;
12895     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12896
12897     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12898
12899     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12900     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12901       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12902       return;
12903     }
12904     if(!stalling) {
12905       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12906       SendToProgram("force\n", &second);
12907       stalling = 1;
12908       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12909       return;
12910     }
12911     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12912     if(appData.matchPause>10000 || appData.matchPause<10)
12913                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12914     wait = SubtractTimeMarks(&now, &pauseStart);
12915     if(wait < appData.matchPause) {
12916         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12917         return;
12918     }
12919     stalling = 0;
12920     DisplayMessage("", "");
12921     if (startedFromSetupPosition) {
12922         SendBoard(&second, backwardMostMove);
12923     if (appData.debugMode) {
12924         fprintf(debugFP, "Two Machines\n");
12925     }
12926     }
12927     for (i = backwardMostMove; i < forwardMostMove; i++) {
12928         SendMoveToProgram(i, &second);
12929     }
12930
12931     gameMode = TwoMachinesPlay;
12932     pausing = FALSE;
12933     ModeHighlight(); // [HGM] logo: this triggers display update of logos
12934     SetGameInfo();
12935     DisplayTwoMachinesTitle();
12936     firstMove = TRUE;
12937     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12938         onmove = &first;
12939     } else {
12940         onmove = &second;
12941     }
12942     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12943     SendToProgram(first.computerString, &first);
12944     if (first.sendName) {
12945       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12946       SendToProgram(buf, &first);
12947     }
12948     SendToProgram(second.computerString, &second);
12949     if (second.sendName) {
12950       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12951       SendToProgram(buf, &second);
12952     }
12953
12954     ResetClocks();
12955     if (!first.sendTime || !second.sendTime) {
12956         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12957         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12958     }
12959     if (onmove->sendTime) {
12960       if (onmove->useColors) {
12961         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12962       }
12963       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12964     }
12965     if (onmove->useColors) {
12966       SendToProgram(onmove->twoMachinesColor, onmove);
12967     }
12968     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12969 //    SendToProgram("go\n", onmove);
12970     onmove->maybeThinking = TRUE;
12971     SetMachineThinkingEnables();
12972
12973     StartClocks();
12974
12975     if(bookHit) { // [HGM] book: simulate book reply
12976         static char bookMove[MSG_SIZ]; // a bit generous?
12977
12978         programStats.nodes = programStats.depth = programStats.time =
12979         programStats.score = programStats.got_only_move = 0;
12980         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12981
12982         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12983         strcat(bookMove, bookHit);
12984         savedMessage = bookMove; // args for deferred call
12985         savedState = onmove;
12986         ScheduleDelayedEvent(DeferredBookMove, 1);
12987     }
12988 }
12989
12990 void
12991 TrainingEvent()
12992 {
12993     if (gameMode == Training) {
12994       SetTrainingModeOff();
12995       gameMode = PlayFromGameFile;
12996       DisplayMessage("", _("Training mode off"));
12997     } else {
12998       gameMode = Training;
12999       animateTraining = appData.animate;
13000
13001       /* make sure we are not already at the end of the game */
13002       if (currentMove < forwardMostMove) {
13003         SetTrainingModeOn();
13004         DisplayMessage("", _("Training mode on"));
13005       } else {
13006         gameMode = PlayFromGameFile;
13007         DisplayError(_("Already at end of game"), 0);
13008       }
13009     }
13010     ModeHighlight();
13011 }
13012
13013 void
13014 IcsClientEvent()
13015 {
13016     if (!appData.icsActive) return;
13017     switch (gameMode) {
13018       case IcsPlayingWhite:
13019       case IcsPlayingBlack:
13020       case IcsObserving:
13021       case IcsIdle:
13022       case BeginningOfGame:
13023       case IcsExamining:
13024         return;
13025
13026       case EditGame:
13027         break;
13028
13029       case EditPosition:
13030         EditPositionDone(TRUE);
13031         break;
13032
13033       case AnalyzeMode:
13034       case AnalyzeFile:
13035         ExitAnalyzeMode();
13036         break;
13037
13038       default:
13039         EditGameEvent();
13040         break;
13041     }
13042
13043     gameMode = IcsIdle;
13044     ModeHighlight();
13045     return;
13046 }
13047
13048
13049 void
13050 EditGameEvent()
13051 {
13052     int i;
13053
13054     switch (gameMode) {
13055       case Training:
13056         SetTrainingModeOff();
13057         break;
13058       case MachinePlaysWhite:
13059       case MachinePlaysBlack:
13060       case BeginningOfGame:
13061         SendToProgram("force\n", &first);
13062         SetUserThinkingEnables();
13063         break;
13064       case PlayFromGameFile:
13065         (void) StopLoadGameTimer();
13066         if (gameFileFP != NULL) {
13067             gameFileFP = NULL;
13068         }
13069         break;
13070       case EditPosition:
13071         EditPositionDone(TRUE);
13072         break;
13073       case AnalyzeMode:
13074       case AnalyzeFile:
13075         ExitAnalyzeMode();
13076         SendToProgram("force\n", &first);
13077         break;
13078       case TwoMachinesPlay:
13079         GameEnds(EndOfFile, NULL, GE_PLAYER);
13080         ResurrectChessProgram();
13081         SetUserThinkingEnables();
13082         break;
13083       case EndOfGame:
13084         ResurrectChessProgram();
13085         break;
13086       case IcsPlayingBlack:
13087       case IcsPlayingWhite:
13088         DisplayError(_("Warning: You are still playing a game"), 0);
13089         break;
13090       case IcsObserving:
13091         DisplayError(_("Warning: You are still observing a game"), 0);
13092         break;
13093       case IcsExamining:
13094         DisplayError(_("Warning: You are still examining a game"), 0);
13095         break;
13096       case IcsIdle:
13097         break;
13098       case EditGame:
13099       default:
13100         return;
13101     }
13102
13103     pausing = FALSE;
13104     StopClocks();
13105     first.offeredDraw = second.offeredDraw = 0;
13106
13107     if (gameMode == PlayFromGameFile) {
13108         whiteTimeRemaining = timeRemaining[0][currentMove];
13109         blackTimeRemaining = timeRemaining[1][currentMove];
13110         DisplayTitle("");
13111     }
13112
13113     if (gameMode == MachinePlaysWhite ||
13114         gameMode == MachinePlaysBlack ||
13115         gameMode == TwoMachinesPlay ||
13116         gameMode == EndOfGame) {
13117         i = forwardMostMove;
13118         while (i > currentMove) {
13119             SendToProgram("undo\n", &first);
13120             i--;
13121         }
13122         whiteTimeRemaining = timeRemaining[0][currentMove];
13123         blackTimeRemaining = timeRemaining[1][currentMove];
13124         DisplayBothClocks();
13125         if (whiteFlag || blackFlag) {
13126             whiteFlag = blackFlag = 0;
13127         }
13128         DisplayTitle("");
13129     }
13130
13131     gameMode = EditGame;
13132     ModeHighlight();
13133     SetGameInfo();
13134 }
13135
13136
13137 void
13138 EditPositionEvent()
13139 {
13140     if (gameMode == EditPosition) {
13141         EditGameEvent();
13142         return;
13143     }
13144
13145     EditGameEvent();
13146     if (gameMode != EditGame) return;
13147
13148     gameMode = EditPosition;
13149     ModeHighlight();
13150     SetGameInfo();
13151     if (currentMove > 0)
13152       CopyBoard(boards[0], boards[currentMove]);
13153
13154     blackPlaysFirst = !WhiteOnMove(currentMove);
13155     ResetClocks();
13156     currentMove = forwardMostMove = backwardMostMove = 0;
13157     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13158     DisplayMove(-1);
13159 }
13160
13161 void
13162 ExitAnalyzeMode()
13163 {
13164     /* [DM] icsEngineAnalyze - possible call from other functions */
13165     if (appData.icsEngineAnalyze) {
13166         appData.icsEngineAnalyze = FALSE;
13167
13168         DisplayMessage("",_("Close ICS engine analyze..."));
13169     }
13170     if (first.analysisSupport && first.analyzing) {
13171       SendToProgram("exit\n", &first);
13172       first.analyzing = FALSE;
13173     }
13174     thinkOutput[0] = NULLCHAR;
13175 }
13176
13177 void
13178 EditPositionDone(Boolean fakeRights)
13179 {
13180     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13181
13182     startedFromSetupPosition = TRUE;
13183     InitChessProgram(&first, FALSE);
13184     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13185       boards[0][EP_STATUS] = EP_NONE;
13186       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13187     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13188         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13189         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13190       } else boards[0][CASTLING][2] = NoRights;
13191     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13192         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13193         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13194       } else boards[0][CASTLING][5] = NoRights;
13195     }
13196     SendToProgram("force\n", &first);
13197     if (blackPlaysFirst) {
13198         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13199         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13200         currentMove = forwardMostMove = backwardMostMove = 1;
13201         CopyBoard(boards[1], boards[0]);
13202     } else {
13203         currentMove = forwardMostMove = backwardMostMove = 0;
13204     }
13205     SendBoard(&first, forwardMostMove);
13206     if (appData.debugMode) {
13207         fprintf(debugFP, "EditPosDone\n");
13208     }
13209     DisplayTitle("");
13210     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13211     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13212     gameMode = EditGame;
13213     ModeHighlight();
13214     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13215     ClearHighlights(); /* [AS] */
13216 }
13217
13218 /* Pause for `ms' milliseconds */
13219 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13220 void
13221 TimeDelay(ms)
13222      long ms;
13223 {
13224     TimeMark m1, m2;
13225
13226     GetTimeMark(&m1);
13227     do {
13228         GetTimeMark(&m2);
13229     } while (SubtractTimeMarks(&m2, &m1) < ms);
13230 }
13231
13232 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13233 void
13234 SendMultiLineToICS(buf)
13235      char *buf;
13236 {
13237     char temp[MSG_SIZ+1], *p;
13238     int len;
13239
13240     len = strlen(buf);
13241     if (len > MSG_SIZ)
13242       len = MSG_SIZ;
13243
13244     strncpy(temp, buf, len);
13245     temp[len] = 0;
13246
13247     p = temp;
13248     while (*p) {
13249         if (*p == '\n' || *p == '\r')
13250           *p = ' ';
13251         ++p;
13252     }
13253
13254     strcat(temp, "\n");
13255     SendToICS(temp);
13256     SendToPlayer(temp, strlen(temp));
13257 }
13258
13259 void
13260 SetWhiteToPlayEvent()
13261 {
13262     if (gameMode == EditPosition) {
13263         blackPlaysFirst = FALSE;
13264         DisplayBothClocks();    /* works because currentMove is 0 */
13265     } else if (gameMode == IcsExamining) {
13266         SendToICS(ics_prefix);
13267         SendToICS("tomove white\n");
13268     }
13269 }
13270
13271 void
13272 SetBlackToPlayEvent()
13273 {
13274     if (gameMode == EditPosition) {
13275         blackPlaysFirst = TRUE;
13276         currentMove = 1;        /* kludge */
13277         DisplayBothClocks();
13278         currentMove = 0;
13279     } else if (gameMode == IcsExamining) {
13280         SendToICS(ics_prefix);
13281         SendToICS("tomove black\n");
13282     }
13283 }
13284
13285 void
13286 EditPositionMenuEvent(selection, x, y)
13287      ChessSquare selection;
13288      int x, y;
13289 {
13290     char buf[MSG_SIZ];
13291     ChessSquare piece = boards[0][y][x];
13292
13293     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13294
13295     switch (selection) {
13296       case ClearBoard:
13297         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13298             SendToICS(ics_prefix);
13299             SendToICS("bsetup clear\n");
13300         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13301             SendToICS(ics_prefix);
13302             SendToICS("clearboard\n");
13303         } else {
13304             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13305                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13306                 for (y = 0; y < BOARD_HEIGHT; y++) {
13307                     if (gameMode == IcsExamining) {
13308                         if (boards[currentMove][y][x] != EmptySquare) {
13309                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13310                                     AAA + x, ONE + y);
13311                             SendToICS(buf);
13312                         }
13313                     } else {
13314                         boards[0][y][x] = p;
13315                     }
13316                 }
13317             }
13318         }
13319         if (gameMode == EditPosition) {
13320             DrawPosition(FALSE, boards[0]);
13321         }
13322         break;
13323
13324       case WhitePlay:
13325         SetWhiteToPlayEvent();
13326         break;
13327
13328       case BlackPlay:
13329         SetBlackToPlayEvent();
13330         break;
13331
13332       case EmptySquare:
13333         if (gameMode == IcsExamining) {
13334             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13335             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13336             SendToICS(buf);
13337         } else {
13338             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13339                 if(x == BOARD_LEFT-2) {
13340                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13341                     boards[0][y][1] = 0;
13342                 } else
13343                 if(x == BOARD_RGHT+1) {
13344                     if(y >= gameInfo.holdingsSize) break;
13345                     boards[0][y][BOARD_WIDTH-2] = 0;
13346                 } else break;
13347             }
13348             boards[0][y][x] = EmptySquare;
13349             DrawPosition(FALSE, boards[0]);
13350         }
13351         break;
13352
13353       case PromotePiece:
13354         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13355            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13356             selection = (ChessSquare) (PROMOTED piece);
13357         } else if(piece == EmptySquare) selection = WhiteSilver;
13358         else selection = (ChessSquare)((int)piece - 1);
13359         goto defaultlabel;
13360
13361       case DemotePiece:
13362         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13363            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13364             selection = (ChessSquare) (DEMOTED piece);
13365         } else if(piece == EmptySquare) selection = BlackSilver;
13366         else selection = (ChessSquare)((int)piece + 1);
13367         goto defaultlabel;
13368
13369       case WhiteQueen:
13370       case BlackQueen:
13371         if(gameInfo.variant == VariantShatranj ||
13372            gameInfo.variant == VariantXiangqi  ||
13373            gameInfo.variant == VariantCourier  ||
13374            gameInfo.variant == VariantMakruk     )
13375             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13376         goto defaultlabel;
13377
13378       case WhiteKing:
13379       case BlackKing:
13380         if(gameInfo.variant == VariantXiangqi)
13381             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13382         if(gameInfo.variant == VariantKnightmate)
13383             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13384       default:
13385         defaultlabel:
13386         if (gameMode == IcsExamining) {
13387             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13388             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13389                      PieceToChar(selection), AAA + x, ONE + y);
13390             SendToICS(buf);
13391         } else {
13392             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13393                 int n;
13394                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13395                     n = PieceToNumber(selection - BlackPawn);
13396                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13397                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13398                     boards[0][BOARD_HEIGHT-1-n][1]++;
13399                 } else
13400                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13401                     n = PieceToNumber(selection);
13402                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13403                     boards[0][n][BOARD_WIDTH-1] = selection;
13404                     boards[0][n][BOARD_WIDTH-2]++;
13405                 }
13406             } else
13407             boards[0][y][x] = selection;
13408             DrawPosition(TRUE, boards[0]);
13409         }
13410         break;
13411     }
13412 }
13413
13414
13415 void
13416 DropMenuEvent(selection, x, y)
13417      ChessSquare selection;
13418      int x, y;
13419 {
13420     ChessMove moveType;
13421
13422     switch (gameMode) {
13423       case IcsPlayingWhite:
13424       case MachinePlaysBlack:
13425         if (!WhiteOnMove(currentMove)) {
13426             DisplayMoveError(_("It is Black's turn"));
13427             return;
13428         }
13429         moveType = WhiteDrop;
13430         break;
13431       case IcsPlayingBlack:
13432       case MachinePlaysWhite:
13433         if (WhiteOnMove(currentMove)) {
13434             DisplayMoveError(_("It is White's turn"));
13435             return;
13436         }
13437         moveType = BlackDrop;
13438         break;
13439       case EditGame:
13440         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13441         break;
13442       default:
13443         return;
13444     }
13445
13446     if (moveType == BlackDrop && selection < BlackPawn) {
13447       selection = (ChessSquare) ((int) selection
13448                                  + (int) BlackPawn - (int) WhitePawn);
13449     }
13450     if (boards[currentMove][y][x] != EmptySquare) {
13451         DisplayMoveError(_("That square is occupied"));
13452         return;
13453     }
13454
13455     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13456 }
13457
13458 void
13459 AcceptEvent()
13460 {
13461     /* Accept a pending offer of any kind from opponent */
13462
13463     if (appData.icsActive) {
13464         SendToICS(ics_prefix);
13465         SendToICS("accept\n");
13466     } else if (cmailMsgLoaded) {
13467         if (currentMove == cmailOldMove &&
13468             commentList[cmailOldMove] != NULL &&
13469             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13470                    "Black offers a draw" : "White offers a draw")) {
13471             TruncateGame();
13472             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13473             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13474         } else {
13475             DisplayError(_("There is no pending offer on this move"), 0);
13476             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13477         }
13478     } else {
13479         /* Not used for offers from chess program */
13480     }
13481 }
13482
13483 void
13484 DeclineEvent()
13485 {
13486     /* Decline a pending offer of any kind from opponent */
13487
13488     if (appData.icsActive) {
13489         SendToICS(ics_prefix);
13490         SendToICS("decline\n");
13491     } else if (cmailMsgLoaded) {
13492         if (currentMove == cmailOldMove &&
13493             commentList[cmailOldMove] != NULL &&
13494             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13495                    "Black offers a draw" : "White offers a draw")) {
13496 #ifdef NOTDEF
13497             AppendComment(cmailOldMove, "Draw declined", TRUE);
13498             DisplayComment(cmailOldMove - 1, "Draw declined");
13499 #endif /*NOTDEF*/
13500         } else {
13501             DisplayError(_("There is no pending offer on this move"), 0);
13502         }
13503     } else {
13504         /* Not used for offers from chess program */
13505     }
13506 }
13507
13508 void
13509 RematchEvent()
13510 {
13511     /* Issue ICS rematch command */
13512     if (appData.icsActive) {
13513         SendToICS(ics_prefix);
13514         SendToICS("rematch\n");
13515     }
13516 }
13517
13518 void
13519 CallFlagEvent()
13520 {
13521     /* Call your opponent's flag (claim a win on time) */
13522     if (appData.icsActive) {
13523         SendToICS(ics_prefix);
13524         SendToICS("flag\n");
13525     } else {
13526         switch (gameMode) {
13527           default:
13528             return;
13529           case MachinePlaysWhite:
13530             if (whiteFlag) {
13531                 if (blackFlag)
13532                   GameEnds(GameIsDrawn, "Both players ran out of time",
13533                            GE_PLAYER);
13534                 else
13535                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13536             } else {
13537                 DisplayError(_("Your opponent is not out of time"), 0);
13538             }
13539             break;
13540           case MachinePlaysBlack:
13541             if (blackFlag) {
13542                 if (whiteFlag)
13543                   GameEnds(GameIsDrawn, "Both players ran out of time",
13544                            GE_PLAYER);
13545                 else
13546                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13547             } else {
13548                 DisplayError(_("Your opponent is not out of time"), 0);
13549             }
13550             break;
13551         }
13552     }
13553 }
13554
13555 void
13556 ClockClick(int which)
13557 {       // [HGM] code moved to back-end from winboard.c
13558         if(which) { // black clock
13559           if (gameMode == EditPosition || gameMode == IcsExamining) {
13560             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13561             SetBlackToPlayEvent();
13562           } else if (gameMode == EditGame || shiftKey) {
13563             AdjustClock(which, -1);
13564           } else if (gameMode == IcsPlayingWhite ||
13565                      gameMode == MachinePlaysBlack) {
13566             CallFlagEvent();
13567           }
13568         } else { // white clock
13569           if (gameMode == EditPosition || gameMode == IcsExamining) {
13570             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13571             SetWhiteToPlayEvent();
13572           } else if (gameMode == EditGame || shiftKey) {
13573             AdjustClock(which, -1);
13574           } else if (gameMode == IcsPlayingBlack ||
13575                    gameMode == MachinePlaysWhite) {
13576             CallFlagEvent();
13577           }
13578         }
13579 }
13580
13581 void
13582 DrawEvent()
13583 {
13584     /* Offer draw or accept pending draw offer from opponent */
13585
13586     if (appData.icsActive) {
13587         /* Note: tournament rules require draw offers to be
13588            made after you make your move but before you punch
13589            your clock.  Currently ICS doesn't let you do that;
13590            instead, you immediately punch your clock after making
13591            a move, but you can offer a draw at any time. */
13592
13593         SendToICS(ics_prefix);
13594         SendToICS("draw\n");
13595         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13596     } else if (cmailMsgLoaded) {
13597         if (currentMove == cmailOldMove &&
13598             commentList[cmailOldMove] != NULL &&
13599             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13600                    "Black offers a draw" : "White offers a draw")) {
13601             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13602             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13603         } else if (currentMove == cmailOldMove + 1) {
13604             char *offer = WhiteOnMove(cmailOldMove) ?
13605               "White offers a draw" : "Black offers a draw";
13606             AppendComment(currentMove, offer, TRUE);
13607             DisplayComment(currentMove - 1, offer);
13608             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13609         } else {
13610             DisplayError(_("You must make your move before offering a draw"), 0);
13611             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13612         }
13613     } else if (first.offeredDraw) {
13614         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13615     } else {
13616         if (first.sendDrawOffers) {
13617             SendToProgram("draw\n", &first);
13618             userOfferedDraw = TRUE;
13619         }
13620     }
13621 }
13622
13623 void
13624 AdjournEvent()
13625 {
13626     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13627
13628     if (appData.icsActive) {
13629         SendToICS(ics_prefix);
13630         SendToICS("adjourn\n");
13631     } else {
13632         /* Currently GNU Chess doesn't offer or accept Adjourns */
13633     }
13634 }
13635
13636
13637 void
13638 AbortEvent()
13639 {
13640     /* Offer Abort or accept pending Abort offer from opponent */
13641
13642     if (appData.icsActive) {
13643         SendToICS(ics_prefix);
13644         SendToICS("abort\n");
13645     } else {
13646         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13647     }
13648 }
13649
13650 void
13651 ResignEvent()
13652 {
13653     /* Resign.  You can do this even if it's not your turn. */
13654
13655     if (appData.icsActive) {
13656         SendToICS(ics_prefix);
13657         SendToICS("resign\n");
13658     } else {
13659         switch (gameMode) {
13660           case MachinePlaysWhite:
13661             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13662             break;
13663           case MachinePlaysBlack:
13664             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13665             break;
13666           case EditGame:
13667             if (cmailMsgLoaded) {
13668                 TruncateGame();
13669                 if (WhiteOnMove(cmailOldMove)) {
13670                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13671                 } else {
13672                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13673                 }
13674                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13675             }
13676             break;
13677           default:
13678             break;
13679         }
13680     }
13681 }
13682
13683
13684 void
13685 StopObservingEvent()
13686 {
13687     /* Stop observing current games */
13688     SendToICS(ics_prefix);
13689     SendToICS("unobserve\n");
13690 }
13691
13692 void
13693 StopExaminingEvent()
13694 {
13695     /* Stop observing current game */
13696     SendToICS(ics_prefix);
13697     SendToICS("unexamine\n");
13698 }
13699
13700 void
13701 ForwardInner(target)
13702      int target;
13703 {
13704     int limit;
13705
13706     if (appData.debugMode)
13707         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13708                 target, currentMove, forwardMostMove);
13709
13710     if (gameMode == EditPosition)
13711       return;
13712
13713     if (gameMode == PlayFromGameFile && !pausing)
13714       PauseEvent();
13715
13716     if (gameMode == IcsExamining && pausing)
13717       limit = pauseExamForwardMostMove;
13718     else
13719       limit = forwardMostMove;
13720
13721     if (target > limit) target = limit;
13722
13723     if (target > 0 && moveList[target - 1][0]) {
13724         int fromX, fromY, toX, toY;
13725         toX = moveList[target - 1][2] - AAA;
13726         toY = moveList[target - 1][3] - ONE;
13727         if (moveList[target - 1][1] == '@') {
13728             if (appData.highlightLastMove) {
13729                 SetHighlights(-1, -1, toX, toY);
13730             }
13731         } else {
13732             fromX = moveList[target - 1][0] - AAA;
13733             fromY = moveList[target - 1][1] - ONE;
13734             if (target == currentMove + 1) {
13735                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13736             }
13737             if (appData.highlightLastMove) {
13738                 SetHighlights(fromX, fromY, toX, toY);
13739             }
13740         }
13741     }
13742     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13743         gameMode == Training || gameMode == PlayFromGameFile ||
13744         gameMode == AnalyzeFile) {
13745         while (currentMove < target) {
13746             SendMoveToProgram(currentMove++, &first);
13747         }
13748     } else {
13749         currentMove = target;
13750     }
13751
13752     if (gameMode == EditGame || gameMode == EndOfGame) {
13753         whiteTimeRemaining = timeRemaining[0][currentMove];
13754         blackTimeRemaining = timeRemaining[1][currentMove];
13755     }
13756     DisplayBothClocks();
13757     DisplayMove(currentMove - 1);
13758     DrawPosition(FALSE, boards[currentMove]);
13759     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13760     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13761         DisplayComment(currentMove - 1, commentList[currentMove]);
13762     }
13763     DisplayBook(currentMove);
13764 }
13765
13766
13767 void
13768 ForwardEvent()
13769 {
13770     if (gameMode == IcsExamining && !pausing) {
13771         SendToICS(ics_prefix);
13772         SendToICS("forward\n");
13773     } else {
13774         ForwardInner(currentMove + 1);
13775     }
13776 }
13777
13778 void
13779 ToEndEvent()
13780 {
13781     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13782         /* to optimze, we temporarily turn off analysis mode while we feed
13783          * the remaining moves to the engine. Otherwise we get analysis output
13784          * after each move.
13785          */
13786         if (first.analysisSupport) {
13787           SendToProgram("exit\nforce\n", &first);
13788           first.analyzing = FALSE;
13789         }
13790     }
13791
13792     if (gameMode == IcsExamining && !pausing) {
13793         SendToICS(ics_prefix);
13794         SendToICS("forward 999999\n");
13795     } else {
13796         ForwardInner(forwardMostMove);
13797     }
13798
13799     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13800         /* we have fed all the moves, so reactivate analysis mode */
13801         SendToProgram("analyze\n", &first);
13802         first.analyzing = TRUE;
13803         /*first.maybeThinking = TRUE;*/
13804         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13805     }
13806 }
13807
13808 void
13809 BackwardInner(target)
13810      int target;
13811 {
13812     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13813
13814     if (appData.debugMode)
13815         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13816                 target, currentMove, forwardMostMove);
13817
13818     if (gameMode == EditPosition) return;
13819     if (currentMove <= backwardMostMove) {
13820         ClearHighlights();
13821         DrawPosition(full_redraw, boards[currentMove]);
13822         return;
13823     }
13824     if (gameMode == PlayFromGameFile && !pausing)
13825       PauseEvent();
13826
13827     if (moveList[target][0]) {
13828         int fromX, fromY, toX, toY;
13829         toX = moveList[target][2] - AAA;
13830         toY = moveList[target][3] - ONE;
13831         if (moveList[target][1] == '@') {
13832             if (appData.highlightLastMove) {
13833                 SetHighlights(-1, -1, toX, toY);
13834             }
13835         } else {
13836             fromX = moveList[target][0] - AAA;
13837             fromY = moveList[target][1] - ONE;
13838             if (target == currentMove - 1) {
13839                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13840             }
13841             if (appData.highlightLastMove) {
13842                 SetHighlights(fromX, fromY, toX, toY);
13843             }
13844         }
13845     }
13846     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13847         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13848         while (currentMove > target) {
13849             SendToProgram("undo\n", &first);
13850             currentMove--;
13851         }
13852     } else {
13853         currentMove = target;
13854     }
13855
13856     if (gameMode == EditGame || gameMode == EndOfGame) {
13857         whiteTimeRemaining = timeRemaining[0][currentMove];
13858         blackTimeRemaining = timeRemaining[1][currentMove];
13859     }
13860     DisplayBothClocks();
13861     DisplayMove(currentMove - 1);
13862     DrawPosition(full_redraw, boards[currentMove]);
13863     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13864     // [HGM] PV info: routine tests if comment empty
13865     DisplayComment(currentMove - 1, commentList[currentMove]);
13866     DisplayBook(currentMove);
13867 }
13868
13869 void
13870 BackwardEvent()
13871 {
13872     if (gameMode == IcsExamining && !pausing) {
13873         SendToICS(ics_prefix);
13874         SendToICS("backward\n");
13875     } else {
13876         BackwardInner(currentMove - 1);
13877     }
13878 }
13879
13880 void
13881 ToStartEvent()
13882 {
13883     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13884         /* to optimize, we temporarily turn off analysis mode while we undo
13885          * all the moves. Otherwise we get analysis output after each undo.
13886          */
13887         if (first.analysisSupport) {
13888           SendToProgram("exit\nforce\n", &first);
13889           first.analyzing = FALSE;
13890         }
13891     }
13892
13893     if (gameMode == IcsExamining && !pausing) {
13894         SendToICS(ics_prefix);
13895         SendToICS("backward 999999\n");
13896     } else {
13897         BackwardInner(backwardMostMove);
13898     }
13899
13900     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13901         /* we have fed all the moves, so reactivate analysis mode */
13902         SendToProgram("analyze\n", &first);
13903         first.analyzing = TRUE;
13904         /*first.maybeThinking = TRUE;*/
13905         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13906     }
13907 }
13908
13909 void
13910 ToNrEvent(int to)
13911 {
13912   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13913   if (to >= forwardMostMove) to = forwardMostMove;
13914   if (to <= backwardMostMove) to = backwardMostMove;
13915   if (to < currentMove) {
13916     BackwardInner(to);
13917   } else {
13918     ForwardInner(to);
13919   }
13920 }
13921
13922 void
13923 RevertEvent(Boolean annotate)
13924 {
13925     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13926         return;
13927     }
13928     if (gameMode != IcsExamining) {
13929         DisplayError(_("You are not examining a game"), 0);
13930         return;
13931     }
13932     if (pausing) {
13933         DisplayError(_("You can't revert while pausing"), 0);
13934         return;
13935     }
13936     SendToICS(ics_prefix);
13937     SendToICS("revert\n");
13938 }
13939
13940 void
13941 RetractMoveEvent()
13942 {
13943     switch (gameMode) {
13944       case MachinePlaysWhite:
13945       case MachinePlaysBlack:
13946         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13947             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13948             return;
13949         }
13950         if (forwardMostMove < 2) return;
13951         currentMove = forwardMostMove = forwardMostMove - 2;
13952         whiteTimeRemaining = timeRemaining[0][currentMove];
13953         blackTimeRemaining = timeRemaining[1][currentMove];
13954         DisplayBothClocks();
13955         DisplayMove(currentMove - 1);
13956         ClearHighlights();/*!! could figure this out*/
13957         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13958         SendToProgram("remove\n", &first);
13959         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13960         break;
13961
13962       case BeginningOfGame:
13963       default:
13964         break;
13965
13966       case IcsPlayingWhite:
13967       case IcsPlayingBlack:
13968         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13969             SendToICS(ics_prefix);
13970             SendToICS("takeback 2\n");
13971         } else {
13972             SendToICS(ics_prefix);
13973             SendToICS("takeback 1\n");
13974         }
13975         break;
13976     }
13977 }
13978
13979 void
13980 MoveNowEvent()
13981 {
13982     ChessProgramState *cps;
13983
13984     switch (gameMode) {
13985       case MachinePlaysWhite:
13986         if (!WhiteOnMove(forwardMostMove)) {
13987             DisplayError(_("It is your turn"), 0);
13988             return;
13989         }
13990         cps = &first;
13991         break;
13992       case MachinePlaysBlack:
13993         if (WhiteOnMove(forwardMostMove)) {
13994             DisplayError(_("It is your turn"), 0);
13995             return;
13996         }
13997         cps = &first;
13998         break;
13999       case TwoMachinesPlay:
14000         if (WhiteOnMove(forwardMostMove) ==
14001             (first.twoMachinesColor[0] == 'w')) {
14002             cps = &first;
14003         } else {
14004             cps = &second;
14005         }
14006         break;
14007       case BeginningOfGame:
14008       default:
14009         return;
14010     }
14011     SendToProgram("?\n", cps);
14012 }
14013
14014 void
14015 TruncateGameEvent()
14016 {
14017     EditGameEvent();
14018     if (gameMode != EditGame) return;
14019     TruncateGame();
14020 }
14021
14022 void
14023 TruncateGame()
14024 {
14025     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14026     if (forwardMostMove > currentMove) {
14027         if (gameInfo.resultDetails != NULL) {
14028             free(gameInfo.resultDetails);
14029             gameInfo.resultDetails = NULL;
14030             gameInfo.result = GameUnfinished;
14031         }
14032         forwardMostMove = currentMove;
14033         HistorySet(parseList, backwardMostMove, forwardMostMove,
14034                    currentMove-1);
14035     }
14036 }
14037
14038 void
14039 HintEvent()
14040 {
14041     if (appData.noChessProgram) return;
14042     switch (gameMode) {
14043       case MachinePlaysWhite:
14044         if (WhiteOnMove(forwardMostMove)) {
14045             DisplayError(_("Wait until your turn"), 0);
14046             return;
14047         }
14048         break;
14049       case BeginningOfGame:
14050       case MachinePlaysBlack:
14051         if (!WhiteOnMove(forwardMostMove)) {
14052             DisplayError(_("Wait until your turn"), 0);
14053             return;
14054         }
14055         break;
14056       default:
14057         DisplayError(_("No hint available"), 0);
14058         return;
14059     }
14060     SendToProgram("hint\n", &first);
14061     hintRequested = TRUE;
14062 }
14063
14064 void
14065 BookEvent()
14066 {
14067     if (appData.noChessProgram) return;
14068     switch (gameMode) {
14069       case MachinePlaysWhite:
14070         if (WhiteOnMove(forwardMostMove)) {
14071             DisplayError(_("Wait until your turn"), 0);
14072             return;
14073         }
14074         break;
14075       case BeginningOfGame:
14076       case MachinePlaysBlack:
14077         if (!WhiteOnMove(forwardMostMove)) {
14078             DisplayError(_("Wait until your turn"), 0);
14079             return;
14080         }
14081         break;
14082       case EditPosition:
14083         EditPositionDone(TRUE);
14084         break;
14085       case TwoMachinesPlay:
14086         return;
14087       default:
14088         break;
14089     }
14090     SendToProgram("bk\n", &first);
14091     bookOutput[0] = NULLCHAR;
14092     bookRequested = TRUE;
14093 }
14094
14095 void
14096 AboutGameEvent()
14097 {
14098     char *tags = PGNTags(&gameInfo);
14099     TagsPopUp(tags, CmailMsg());
14100     free(tags);
14101 }
14102
14103 /* end button procedures */
14104
14105 void
14106 PrintPosition(fp, move)
14107      FILE *fp;
14108      int move;
14109 {
14110     int i, j;
14111
14112     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14113         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14114             char c = PieceToChar(boards[move][i][j]);
14115             fputc(c == 'x' ? '.' : c, fp);
14116             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14117         }
14118     }
14119     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14120       fprintf(fp, "white to play\n");
14121     else
14122       fprintf(fp, "black to play\n");
14123 }
14124
14125 void
14126 PrintOpponents(fp)
14127      FILE *fp;
14128 {
14129     if (gameInfo.white != NULL) {
14130         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14131     } else {
14132         fprintf(fp, "\n");
14133     }
14134 }
14135
14136 /* Find last component of program's own name, using some heuristics */
14137 void
14138 TidyProgramName(prog, host, buf)
14139      char *prog, *host, buf[MSG_SIZ];
14140 {
14141     char *p, *q;
14142     int local = (strcmp(host, "localhost") == 0);
14143     while (!local && (p = strchr(prog, ';')) != NULL) {
14144         p++;
14145         while (*p == ' ') p++;
14146         prog = p;
14147     }
14148     if (*prog == '"' || *prog == '\'') {
14149         q = strchr(prog + 1, *prog);
14150     } else {
14151         q = strchr(prog, ' ');
14152     }
14153     if (q == NULL) q = prog + strlen(prog);
14154     p = q;
14155     while (p >= prog && *p != '/' && *p != '\\') p--;
14156     p++;
14157     if(p == prog && *p == '"') p++;
14158     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14159     memcpy(buf, p, q - p);
14160     buf[q - p] = NULLCHAR;
14161     if (!local) {
14162         strcat(buf, "@");
14163         strcat(buf, host);
14164     }
14165 }
14166
14167 char *
14168 TimeControlTagValue()
14169 {
14170     char buf[MSG_SIZ];
14171     if (!appData.clockMode) {
14172       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14173     } else if (movesPerSession > 0) {
14174       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14175     } else if (timeIncrement == 0) {
14176       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14177     } else {
14178       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14179     }
14180     return StrSave(buf);
14181 }
14182
14183 void
14184 SetGameInfo()
14185 {
14186     /* This routine is used only for certain modes */
14187     VariantClass v = gameInfo.variant;
14188     ChessMove r = GameUnfinished;
14189     char *p = NULL;
14190
14191     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14192         r = gameInfo.result;
14193         p = gameInfo.resultDetails;
14194         gameInfo.resultDetails = NULL;
14195     }
14196     ClearGameInfo(&gameInfo);
14197     gameInfo.variant = v;
14198
14199     switch (gameMode) {
14200       case MachinePlaysWhite:
14201         gameInfo.event = StrSave( appData.pgnEventHeader );
14202         gameInfo.site = StrSave(HostName());
14203         gameInfo.date = PGNDate();
14204         gameInfo.round = StrSave("-");
14205         gameInfo.white = StrSave(first.tidy);
14206         gameInfo.black = StrSave(UserName());
14207         gameInfo.timeControl = TimeControlTagValue();
14208         break;
14209
14210       case MachinePlaysBlack:
14211         gameInfo.event = StrSave( appData.pgnEventHeader );
14212         gameInfo.site = StrSave(HostName());
14213         gameInfo.date = PGNDate();
14214         gameInfo.round = StrSave("-");
14215         gameInfo.white = StrSave(UserName());
14216         gameInfo.black = StrSave(first.tidy);
14217         gameInfo.timeControl = TimeControlTagValue();
14218         break;
14219
14220       case TwoMachinesPlay:
14221         gameInfo.event = StrSave( appData.pgnEventHeader );
14222         gameInfo.site = StrSave(HostName());
14223         gameInfo.date = PGNDate();
14224         if (roundNr > 0) {
14225             char buf[MSG_SIZ];
14226             snprintf(buf, MSG_SIZ, "%d", roundNr);
14227             gameInfo.round = StrSave(buf);
14228         } else {
14229             gameInfo.round = StrSave("-");
14230         }
14231         if (first.twoMachinesColor[0] == 'w') {
14232             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14233             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14234         } else {
14235             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14236             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14237         }
14238         gameInfo.timeControl = TimeControlTagValue();
14239         break;
14240
14241       case EditGame:
14242         gameInfo.event = StrSave("Edited game");
14243         gameInfo.site = StrSave(HostName());
14244         gameInfo.date = PGNDate();
14245         gameInfo.round = StrSave("-");
14246         gameInfo.white = StrSave("-");
14247         gameInfo.black = StrSave("-");
14248         gameInfo.result = r;
14249         gameInfo.resultDetails = p;
14250         break;
14251
14252       case EditPosition:
14253         gameInfo.event = StrSave("Edited position");
14254         gameInfo.site = StrSave(HostName());
14255         gameInfo.date = PGNDate();
14256         gameInfo.round = StrSave("-");
14257         gameInfo.white = StrSave("-");
14258         gameInfo.black = StrSave("-");
14259         break;
14260
14261       case IcsPlayingWhite:
14262       case IcsPlayingBlack:
14263       case IcsObserving:
14264       case IcsExamining:
14265         break;
14266
14267       case PlayFromGameFile:
14268         gameInfo.event = StrSave("Game from non-PGN file");
14269         gameInfo.site = StrSave(HostName());
14270         gameInfo.date = PGNDate();
14271         gameInfo.round = StrSave("-");
14272         gameInfo.white = StrSave("?");
14273         gameInfo.black = StrSave("?");
14274         break;
14275
14276       default:
14277         break;
14278     }
14279 }
14280
14281 void
14282 ReplaceComment(index, text)
14283      int index;
14284      char *text;
14285 {
14286     int len;
14287     char *p;
14288     float score;
14289
14290     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14291        pvInfoList[index-1].depth == len &&
14292        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14293        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14294     while (*text == '\n') text++;
14295     len = strlen(text);
14296     while (len > 0 && text[len - 1] == '\n') len--;
14297
14298     if (commentList[index] != NULL)
14299       free(commentList[index]);
14300
14301     if (len == 0) {
14302         commentList[index] = NULL;
14303         return;
14304     }
14305   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14306       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14307       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14308     commentList[index] = (char *) malloc(len + 2);
14309     strncpy(commentList[index], text, len);
14310     commentList[index][len] = '\n';
14311     commentList[index][len + 1] = NULLCHAR;
14312   } else {
14313     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14314     char *p;
14315     commentList[index] = (char *) malloc(len + 7);
14316     safeStrCpy(commentList[index], "{\n", 3);
14317     safeStrCpy(commentList[index]+2, text, len+1);
14318     commentList[index][len+2] = NULLCHAR;
14319     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14320     strcat(commentList[index], "\n}\n");
14321   }
14322 }
14323
14324 void
14325 CrushCRs(text)
14326      char *text;
14327 {
14328   char *p = text;
14329   char *q = text;
14330   char ch;
14331
14332   do {
14333     ch = *p++;
14334     if (ch == '\r') continue;
14335     *q++ = ch;
14336   } while (ch != '\0');
14337 }
14338
14339 void
14340 AppendComment(index, text, addBraces)
14341      int index;
14342      char *text;
14343      Boolean addBraces; // [HGM] braces: tells if we should add {}
14344 {
14345     int oldlen, len;
14346     char *old;
14347
14348 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14349     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14350
14351     CrushCRs(text);
14352     while (*text == '\n') text++;
14353     len = strlen(text);
14354     while (len > 0 && text[len - 1] == '\n') len--;
14355
14356     if (len == 0) return;
14357
14358     if (commentList[index] != NULL) {
14359         old = commentList[index];
14360         oldlen = strlen(old);
14361         while(commentList[index][oldlen-1] ==  '\n')
14362           commentList[index][--oldlen] = NULLCHAR;
14363         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14364         safeStrCpy(commentList[index], old, oldlen + len + 6);
14365         free(old);
14366         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14367         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14368           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14369           while (*text == '\n') { text++; len--; }
14370           commentList[index][--oldlen] = NULLCHAR;
14371       }
14372         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14373         else          strcat(commentList[index], "\n");
14374         strcat(commentList[index], text);
14375         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14376         else          strcat(commentList[index], "\n");
14377     } else {
14378         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14379         if(addBraces)
14380           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14381         else commentList[index][0] = NULLCHAR;
14382         strcat(commentList[index], text);
14383         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14384         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14385     }
14386 }
14387
14388 static char * FindStr( char * text, char * sub_text )
14389 {
14390     char * result = strstr( text, sub_text );
14391
14392     if( result != NULL ) {
14393         result += strlen( sub_text );
14394     }
14395
14396     return result;
14397 }
14398
14399 /* [AS] Try to extract PV info from PGN comment */
14400 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14401 char *GetInfoFromComment( int index, char * text )
14402 {
14403     char * sep = text, *p;
14404
14405     if( text != NULL && index > 0 ) {
14406         int score = 0;
14407         int depth = 0;
14408         int time = -1, sec = 0, deci;
14409         char * s_eval = FindStr( text, "[%eval " );
14410         char * s_emt = FindStr( text, "[%emt " );
14411
14412         if( s_eval != NULL || s_emt != NULL ) {
14413             /* New style */
14414             char delim;
14415
14416             if( s_eval != NULL ) {
14417                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14418                     return text;
14419                 }
14420
14421                 if( delim != ']' ) {
14422                     return text;
14423                 }
14424             }
14425
14426             if( s_emt != NULL ) {
14427             }
14428                 return text;
14429         }
14430         else {
14431             /* We expect something like: [+|-]nnn.nn/dd */
14432             int score_lo = 0;
14433
14434             if(*text != '{') return text; // [HGM] braces: must be normal comment
14435
14436             sep = strchr( text, '/' );
14437             if( sep == NULL || sep < (text+4) ) {
14438                 return text;
14439             }
14440
14441             p = text;
14442             if(p[1] == '(') { // comment starts with PV
14443                p = strchr(p, ')'); // locate end of PV
14444                if(p == NULL || sep < p+5) return text;
14445                // at this point we have something like "{(.*) +0.23/6 ..."
14446                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14447                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14448                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14449             }
14450             time = -1; sec = -1; deci = -1;
14451             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14452                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14453                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14454                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14455                 return text;
14456             }
14457
14458             if( score_lo < 0 || score_lo >= 100 ) {
14459                 return text;
14460             }
14461
14462             if(sec >= 0) time = 600*time + 10*sec; else
14463             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14464
14465             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14466
14467             /* [HGM] PV time: now locate end of PV info */
14468             while( *++sep >= '0' && *sep <= '9'); // strip depth
14469             if(time >= 0)
14470             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14471             if(sec >= 0)
14472             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14473             if(deci >= 0)
14474             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14475             while(*sep == ' ') sep++;
14476         }
14477
14478         if( depth <= 0 ) {
14479             return text;
14480         }
14481
14482         if( time < 0 ) {
14483             time = -1;
14484         }
14485
14486         pvInfoList[index-1].depth = depth;
14487         pvInfoList[index-1].score = score;
14488         pvInfoList[index-1].time  = 10*time; // centi-sec
14489         if(*sep == '}') *sep = 0; else *--sep = '{';
14490         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14491     }
14492     return sep;
14493 }
14494
14495 void
14496 SendToProgram(message, cps)
14497      char *message;
14498      ChessProgramState *cps;
14499 {
14500     int count, outCount, error;
14501     char buf[MSG_SIZ];
14502
14503     if (cps->pr == NULL) return;
14504     Attention(cps);
14505
14506     if (appData.debugMode) {
14507         TimeMark now;
14508         GetTimeMark(&now);
14509         fprintf(debugFP, "%ld >%-6s: %s",
14510                 SubtractTimeMarks(&now, &programStartTime),
14511                 cps->which, message);
14512     }
14513
14514     count = strlen(message);
14515     outCount = OutputToProcess(cps->pr, message, count, &error);
14516     if (outCount < count && !exiting
14517                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14518       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14519       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14520         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14521             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14522                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14523                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14524                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14525             } else {
14526                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14527                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14528                 gameInfo.result = res;
14529             }
14530             gameInfo.resultDetails = StrSave(buf);
14531         }
14532         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14533         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14534     }
14535 }
14536
14537 void
14538 ReceiveFromProgram(isr, closure, message, count, error)
14539      InputSourceRef isr;
14540      VOIDSTAR closure;
14541      char *message;
14542      int count;
14543      int error;
14544 {
14545     char *end_str;
14546     char buf[MSG_SIZ];
14547     ChessProgramState *cps = (ChessProgramState *)closure;
14548
14549     if (isr != cps->isr) return; /* Killed intentionally */
14550     if (count <= 0) {
14551         if (count == 0) {
14552             RemoveInputSource(cps->isr);
14553             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14554             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14555                     _(cps->which), cps->program);
14556         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14557                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14558                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14559                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14560                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14561                 } else {
14562                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14563                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14564                     gameInfo.result = res;
14565                 }
14566                 gameInfo.resultDetails = StrSave(buf);
14567             }
14568             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14569             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14570         } else {
14571             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14572                     _(cps->which), cps->program);
14573             RemoveInputSource(cps->isr);
14574
14575             /* [AS] Program is misbehaving badly... kill it */
14576             if( count == -2 ) {
14577                 DestroyChildProcess( cps->pr, 9 );
14578                 cps->pr = NoProc;
14579             }
14580
14581             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14582         }
14583         return;
14584     }
14585
14586     if ((end_str = strchr(message, '\r')) != NULL)
14587       *end_str = NULLCHAR;
14588     if ((end_str = strchr(message, '\n')) != NULL)
14589       *end_str = NULLCHAR;
14590
14591     if (appData.debugMode) {
14592         TimeMark now; int print = 1;
14593         char *quote = ""; char c; int i;
14594
14595         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14596                 char start = message[0];
14597                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14598                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14599                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14600                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14601                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14602                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14603                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14604                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14605                    sscanf(message, "hint: %c", &c)!=1 && 
14606                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14607                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14608                     print = (appData.engineComments >= 2);
14609                 }
14610                 message[0] = start; // restore original message
14611         }
14612         if(print) {
14613                 GetTimeMark(&now);
14614                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14615                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14616                         quote,
14617                         message);
14618         }
14619     }
14620
14621     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14622     if (appData.icsEngineAnalyze) {
14623         if (strstr(message, "whisper") != NULL ||
14624              strstr(message, "kibitz") != NULL ||
14625             strstr(message, "tellics") != NULL) return;
14626     }
14627
14628     HandleMachineMove(message, cps);
14629 }
14630
14631
14632 void
14633 SendTimeControl(cps, mps, tc, inc, sd, st)
14634      ChessProgramState *cps;
14635      int mps, inc, sd, st;
14636      long tc;
14637 {
14638     char buf[MSG_SIZ];
14639     int seconds;
14640
14641     if( timeControl_2 > 0 ) {
14642         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14643             tc = timeControl_2;
14644         }
14645     }
14646     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14647     inc /= cps->timeOdds;
14648     st  /= cps->timeOdds;
14649
14650     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14651
14652     if (st > 0) {
14653       /* Set exact time per move, normally using st command */
14654       if (cps->stKludge) {
14655         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14656         seconds = st % 60;
14657         if (seconds == 0) {
14658           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14659         } else {
14660           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14661         }
14662       } else {
14663         snprintf(buf, MSG_SIZ, "st %d\n", st);
14664       }
14665     } else {
14666       /* Set conventional or incremental time control, using level command */
14667       if (seconds == 0) {
14668         /* Note old gnuchess bug -- minutes:seconds used to not work.
14669            Fixed in later versions, but still avoid :seconds
14670            when seconds is 0. */
14671         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14672       } else {
14673         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14674                  seconds, inc/1000.);
14675       }
14676     }
14677     SendToProgram(buf, cps);
14678
14679     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14680     /* Orthogonally, limit search to given depth */
14681     if (sd > 0) {
14682       if (cps->sdKludge) {
14683         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14684       } else {
14685         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14686       }
14687       SendToProgram(buf, cps);
14688     }
14689
14690     if(cps->nps >= 0) { /* [HGM] nps */
14691         if(cps->supportsNPS == FALSE)
14692           cps->nps = -1; // don't use if engine explicitly says not supported!
14693         else {
14694           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14695           SendToProgram(buf, cps);
14696         }
14697     }
14698 }
14699
14700 ChessProgramState *WhitePlayer()
14701 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14702 {
14703     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14704        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14705         return &second;
14706     return &first;
14707 }
14708
14709 void
14710 SendTimeRemaining(cps, machineWhite)
14711      ChessProgramState *cps;
14712      int /*boolean*/ machineWhite;
14713 {
14714     char message[MSG_SIZ];
14715     long time, otime;
14716
14717     /* Note: this routine must be called when the clocks are stopped
14718        or when they have *just* been set or switched; otherwise
14719        it will be off by the time since the current tick started.
14720     */
14721     if (machineWhite) {
14722         time = whiteTimeRemaining / 10;
14723         otime = blackTimeRemaining / 10;
14724     } else {
14725         time = blackTimeRemaining / 10;
14726         otime = whiteTimeRemaining / 10;
14727     }
14728     /* [HGM] translate opponent's time by time-odds factor */
14729     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14730     if (appData.debugMode) {
14731         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14732     }
14733
14734     if (time <= 0) time = 1;
14735     if (otime <= 0) otime = 1;
14736
14737     snprintf(message, MSG_SIZ, "time %ld\n", time);
14738     SendToProgram(message, cps);
14739
14740     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14741     SendToProgram(message, cps);
14742 }
14743
14744 int
14745 BoolFeature(p, name, loc, cps)
14746      char **p;
14747      char *name;
14748      int *loc;
14749      ChessProgramState *cps;
14750 {
14751   char buf[MSG_SIZ];
14752   int len = strlen(name);
14753   int val;
14754
14755   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14756     (*p) += len + 1;
14757     sscanf(*p, "%d", &val);
14758     *loc = (val != 0);
14759     while (**p && **p != ' ')
14760       (*p)++;
14761     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14762     SendToProgram(buf, cps);
14763     return TRUE;
14764   }
14765   return FALSE;
14766 }
14767
14768 int
14769 IntFeature(p, name, loc, cps)
14770      char **p;
14771      char *name;
14772      int *loc;
14773      ChessProgramState *cps;
14774 {
14775   char buf[MSG_SIZ];
14776   int len = strlen(name);
14777   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14778     (*p) += len + 1;
14779     sscanf(*p, "%d", loc);
14780     while (**p && **p != ' ') (*p)++;
14781     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14782     SendToProgram(buf, cps);
14783     return TRUE;
14784   }
14785   return FALSE;
14786 }
14787
14788 int
14789 StringFeature(p, name, loc, cps)
14790      char **p;
14791      char *name;
14792      char loc[];
14793      ChessProgramState *cps;
14794 {
14795   char buf[MSG_SIZ];
14796   int len = strlen(name);
14797   if (strncmp((*p), name, len) == 0
14798       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14799     (*p) += len + 2;
14800     sscanf(*p, "%[^\"]", loc);
14801     while (**p && **p != '\"') (*p)++;
14802     if (**p == '\"') (*p)++;
14803     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14804     SendToProgram(buf, cps);
14805     return TRUE;
14806   }
14807   return FALSE;
14808 }
14809
14810 int
14811 ParseOption(Option *opt, ChessProgramState *cps)
14812 // [HGM] options: process the string that defines an engine option, and determine
14813 // name, type, default value, and allowed value range
14814 {
14815         char *p, *q, buf[MSG_SIZ];
14816         int n, min = (-1)<<31, max = 1<<31, def;
14817
14818         if(p = strstr(opt->name, " -spin ")) {
14819             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14820             if(max < min) max = min; // enforce consistency
14821             if(def < min) def = min;
14822             if(def > max) def = max;
14823             opt->value = def;
14824             opt->min = min;
14825             opt->max = max;
14826             opt->type = Spin;
14827         } else if((p = strstr(opt->name, " -slider "))) {
14828             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14829             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14830             if(max < min) max = min; // enforce consistency
14831             if(def < min) def = min;
14832             if(def > max) def = max;
14833             opt->value = def;
14834             opt->min = min;
14835             opt->max = max;
14836             opt->type = Spin; // Slider;
14837         } else if((p = strstr(opt->name, " -string "))) {
14838             opt->textValue = p+9;
14839             opt->type = TextBox;
14840         } else if((p = strstr(opt->name, " -file "))) {
14841             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14842             opt->textValue = p+7;
14843             opt->type = FileName; // FileName;
14844         } else if((p = strstr(opt->name, " -path "))) {
14845             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14846             opt->textValue = p+7;
14847             opt->type = PathName; // PathName;
14848         } else if(p = strstr(opt->name, " -check ")) {
14849             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14850             opt->value = (def != 0);
14851             opt->type = CheckBox;
14852         } else if(p = strstr(opt->name, " -combo ")) {
14853             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14854             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14855             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14856             opt->value = n = 0;
14857             while(q = StrStr(q, " /// ")) {
14858                 n++; *q = 0;    // count choices, and null-terminate each of them
14859                 q += 5;
14860                 if(*q == '*') { // remember default, which is marked with * prefix
14861                     q++;
14862                     opt->value = n;
14863                 }
14864                 cps->comboList[cps->comboCnt++] = q;
14865             }
14866             cps->comboList[cps->comboCnt++] = NULL;
14867             opt->max = n + 1;
14868             opt->type = ComboBox;
14869         } else if(p = strstr(opt->name, " -button")) {
14870             opt->type = Button;
14871         } else if(p = strstr(opt->name, " -save")) {
14872             opt->type = SaveButton;
14873         } else return FALSE;
14874         *p = 0; // terminate option name
14875         // now look if the command-line options define a setting for this engine option.
14876         if(cps->optionSettings && cps->optionSettings[0])
14877             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14878         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14879           snprintf(buf, MSG_SIZ, "option %s", p);
14880                 if(p = strstr(buf, ",")) *p = 0;
14881                 if(q = strchr(buf, '=')) switch(opt->type) {
14882                     case ComboBox:
14883                         for(n=0; n<opt->max; n++)
14884                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14885                         break;
14886                     case TextBox:
14887                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14888                         break;
14889                     case Spin:
14890                     case CheckBox:
14891                         opt->value = atoi(q+1);
14892                     default:
14893                         break;
14894                 }
14895                 strcat(buf, "\n");
14896                 SendToProgram(buf, cps);
14897         }
14898         return TRUE;
14899 }
14900
14901 void
14902 FeatureDone(cps, val)
14903      ChessProgramState* cps;
14904      int val;
14905 {
14906   DelayedEventCallback cb = GetDelayedEvent();
14907   if ((cb == InitBackEnd3 && cps == &first) ||
14908       (cb == SettingsMenuIfReady && cps == &second) ||
14909       (cb == LoadEngine) ||
14910       (cb == TwoMachinesEventIfReady)) {
14911     CancelDelayedEvent();
14912     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14913   }
14914   cps->initDone = val;
14915 }
14916
14917 /* Parse feature command from engine */
14918 void
14919 ParseFeatures(args, cps)
14920      char* args;
14921      ChessProgramState *cps;
14922 {
14923   char *p = args;
14924   char *q;
14925   int val;
14926   char buf[MSG_SIZ];
14927
14928   for (;;) {
14929     while (*p == ' ') p++;
14930     if (*p == NULLCHAR) return;
14931
14932     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14933     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14934     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14935     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14936     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14937     if (BoolFeature(&p, "reuse", &val, cps)) {
14938       /* Engine can disable reuse, but can't enable it if user said no */
14939       if (!val) cps->reuse = FALSE;
14940       continue;
14941     }
14942     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14943     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14944       if (gameMode == TwoMachinesPlay) {
14945         DisplayTwoMachinesTitle();
14946       } else {
14947         DisplayTitle("");
14948       }
14949       continue;
14950     }
14951     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14952     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14953     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14954     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14955     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14956     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14957     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14958     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14959     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14960     if (IntFeature(&p, "done", &val, cps)) {
14961       FeatureDone(cps, val);
14962       continue;
14963     }
14964     /* Added by Tord: */
14965     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14966     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14967     /* End of additions by Tord */
14968
14969     /* [HGM] added features: */
14970     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14971     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14972     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14973     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14974     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14975     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14976     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14977         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14978           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14979             SendToProgram(buf, cps);
14980             continue;
14981         }
14982         if(cps->nrOptions >= MAX_OPTIONS) {
14983             cps->nrOptions--;
14984             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14985             DisplayError(buf, 0);
14986         }
14987         continue;
14988     }
14989     /* End of additions by HGM */
14990
14991     /* unknown feature: complain and skip */
14992     q = p;
14993     while (*q && *q != '=') q++;
14994     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14995     SendToProgram(buf, cps);
14996     p = q;
14997     if (*p == '=') {
14998       p++;
14999       if (*p == '\"') {
15000         p++;
15001         while (*p && *p != '\"') p++;
15002         if (*p == '\"') p++;
15003       } else {
15004         while (*p && *p != ' ') p++;
15005       }
15006     }
15007   }
15008
15009 }
15010
15011 void
15012 PeriodicUpdatesEvent(newState)
15013      int newState;
15014 {
15015     if (newState == appData.periodicUpdates)
15016       return;
15017
15018     appData.periodicUpdates=newState;
15019
15020     /* Display type changes, so update it now */
15021 //    DisplayAnalysis();
15022
15023     /* Get the ball rolling again... */
15024     if (newState) {
15025         AnalysisPeriodicEvent(1);
15026         StartAnalysisClock();
15027     }
15028 }
15029
15030 void
15031 PonderNextMoveEvent(newState)
15032      int newState;
15033 {
15034     if (newState == appData.ponderNextMove) return;
15035     if (gameMode == EditPosition) EditPositionDone(TRUE);
15036     if (newState) {
15037         SendToProgram("hard\n", &first);
15038         if (gameMode == TwoMachinesPlay) {
15039             SendToProgram("hard\n", &second);
15040         }
15041     } else {
15042         SendToProgram("easy\n", &first);
15043         thinkOutput[0] = NULLCHAR;
15044         if (gameMode == TwoMachinesPlay) {
15045             SendToProgram("easy\n", &second);
15046         }
15047     }
15048     appData.ponderNextMove = newState;
15049 }
15050
15051 void
15052 NewSettingEvent(option, feature, command, value)
15053      char *command;
15054      int option, value, *feature;
15055 {
15056     char buf[MSG_SIZ];
15057
15058     if (gameMode == EditPosition) EditPositionDone(TRUE);
15059     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15060     if(feature == NULL || *feature) SendToProgram(buf, &first);
15061     if (gameMode == TwoMachinesPlay) {
15062         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15063     }
15064 }
15065
15066 void
15067 ShowThinkingEvent()
15068 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15069 {
15070     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15071     int newState = appData.showThinking
15072         // [HGM] thinking: other features now need thinking output as well
15073         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15074
15075     if (oldState == newState) return;
15076     oldState = newState;
15077     if (gameMode == EditPosition) EditPositionDone(TRUE);
15078     if (oldState) {
15079         SendToProgram("post\n", &first);
15080         if (gameMode == TwoMachinesPlay) {
15081             SendToProgram("post\n", &second);
15082         }
15083     } else {
15084         SendToProgram("nopost\n", &first);
15085         thinkOutput[0] = NULLCHAR;
15086         if (gameMode == TwoMachinesPlay) {
15087             SendToProgram("nopost\n", &second);
15088         }
15089     }
15090 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15091 }
15092
15093 void
15094 AskQuestionEvent(title, question, replyPrefix, which)
15095      char *title; char *question; char *replyPrefix; char *which;
15096 {
15097   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15098   if (pr == NoProc) return;
15099   AskQuestion(title, question, replyPrefix, pr);
15100 }
15101
15102 void
15103 TypeInEvent(char firstChar)
15104 {
15105     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15106         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15107         gameMode == AnalyzeMode || gameMode == EditGame || 
15108         gameMode == EditPosition || gameMode == IcsExamining ||
15109         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15110         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15111                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15112                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15113         gameMode == Training) PopUpMoveDialog(firstChar);
15114 }
15115
15116 void
15117 TypeInDoneEvent(char *move)
15118 {
15119         Board board;
15120         int n, fromX, fromY, toX, toY;
15121         char promoChar;
15122         ChessMove moveType;
15123
15124         // [HGM] FENedit
15125         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15126                 EditPositionPasteFEN(move);
15127                 return;
15128         }
15129         // [HGM] movenum: allow move number to be typed in any mode
15130         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15131           ToNrEvent(2*n-1);
15132           return;
15133         }
15134
15135       if (gameMode != EditGame && currentMove != forwardMostMove && 
15136         gameMode != Training) {
15137         DisplayMoveError(_("Displayed move is not current"));
15138       } else {
15139         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15140           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15141         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15142         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15143           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15144           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15145         } else {
15146           DisplayMoveError(_("Could not parse move"));
15147         }
15148       }
15149 }
15150
15151 void
15152 DisplayMove(moveNumber)
15153      int moveNumber;
15154 {
15155     char message[MSG_SIZ];
15156     char res[MSG_SIZ];
15157     char cpThinkOutput[MSG_SIZ];
15158
15159     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15160
15161     if (moveNumber == forwardMostMove - 1 ||
15162         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15163
15164         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15165
15166         if (strchr(cpThinkOutput, '\n')) {
15167             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15168         }
15169     } else {
15170         *cpThinkOutput = NULLCHAR;
15171     }
15172
15173     /* [AS] Hide thinking from human user */
15174     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15175         *cpThinkOutput = NULLCHAR;
15176         if( thinkOutput[0] != NULLCHAR ) {
15177             int i;
15178
15179             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15180                 cpThinkOutput[i] = '.';
15181             }
15182             cpThinkOutput[i] = NULLCHAR;
15183             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15184         }
15185     }
15186
15187     if (moveNumber == forwardMostMove - 1 &&
15188         gameInfo.resultDetails != NULL) {
15189         if (gameInfo.resultDetails[0] == NULLCHAR) {
15190           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15191         } else {
15192           snprintf(res, MSG_SIZ, " {%s} %s",
15193                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15194         }
15195     } else {
15196         res[0] = NULLCHAR;
15197     }
15198
15199     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15200         DisplayMessage(res, cpThinkOutput);
15201     } else {
15202       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15203                 WhiteOnMove(moveNumber) ? " " : ".. ",
15204                 parseList[moveNumber], res);
15205         DisplayMessage(message, cpThinkOutput);
15206     }
15207 }
15208
15209 void
15210 DisplayComment(moveNumber, text)
15211      int moveNumber;
15212      char *text;
15213 {
15214     char title[MSG_SIZ];
15215
15216     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15217       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15218     } else {
15219       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15220               WhiteOnMove(moveNumber) ? " " : ".. ",
15221               parseList[moveNumber]);
15222     }
15223     if (text != NULL && (appData.autoDisplayComment || commentUp))
15224         CommentPopUp(title, text);
15225 }
15226
15227 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15228  * might be busy thinking or pondering.  It can be omitted if your
15229  * gnuchess is configured to stop thinking immediately on any user
15230  * input.  However, that gnuchess feature depends on the FIONREAD
15231  * ioctl, which does not work properly on some flavors of Unix.
15232  */
15233 void
15234 Attention(cps)
15235      ChessProgramState *cps;
15236 {
15237 #if ATTENTION
15238     if (!cps->useSigint) return;
15239     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15240     switch (gameMode) {
15241       case MachinePlaysWhite:
15242       case MachinePlaysBlack:
15243       case TwoMachinesPlay:
15244       case IcsPlayingWhite:
15245       case IcsPlayingBlack:
15246       case AnalyzeMode:
15247       case AnalyzeFile:
15248         /* Skip if we know it isn't thinking */
15249         if (!cps->maybeThinking) return;
15250         if (appData.debugMode)
15251           fprintf(debugFP, "Interrupting %s\n", cps->which);
15252         InterruptChildProcess(cps->pr);
15253         cps->maybeThinking = FALSE;
15254         break;
15255       default:
15256         break;
15257     }
15258 #endif /*ATTENTION*/
15259 }
15260
15261 int
15262 CheckFlags()
15263 {
15264     if (whiteTimeRemaining <= 0) {
15265         if (!whiteFlag) {
15266             whiteFlag = TRUE;
15267             if (appData.icsActive) {
15268                 if (appData.autoCallFlag &&
15269                     gameMode == IcsPlayingBlack && !blackFlag) {
15270                   SendToICS(ics_prefix);
15271                   SendToICS("flag\n");
15272                 }
15273             } else {
15274                 if (blackFlag) {
15275                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15276                 } else {
15277                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15278                     if (appData.autoCallFlag) {
15279                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15280                         return TRUE;
15281                     }
15282                 }
15283             }
15284         }
15285     }
15286     if (blackTimeRemaining <= 0) {
15287         if (!blackFlag) {
15288             blackFlag = TRUE;
15289             if (appData.icsActive) {
15290                 if (appData.autoCallFlag &&
15291                     gameMode == IcsPlayingWhite && !whiteFlag) {
15292                   SendToICS(ics_prefix);
15293                   SendToICS("flag\n");
15294                 }
15295             } else {
15296                 if (whiteFlag) {
15297                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15298                 } else {
15299                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15300                     if (appData.autoCallFlag) {
15301                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15302                         return TRUE;
15303                     }
15304                 }
15305             }
15306         }
15307     }
15308     return FALSE;
15309 }
15310
15311 void
15312 CheckTimeControl()
15313 {
15314     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15315         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15316
15317     /*
15318      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15319      */
15320     if ( !WhiteOnMove(forwardMostMove) ) {
15321         /* White made time control */
15322         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15323         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15324         /* [HGM] time odds: correct new time quota for time odds! */
15325                                             / WhitePlayer()->timeOdds;
15326         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15327     } else {
15328         lastBlack -= blackTimeRemaining;
15329         /* Black made time control */
15330         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15331                                             / WhitePlayer()->other->timeOdds;
15332         lastWhite = whiteTimeRemaining;
15333     }
15334 }
15335
15336 void
15337 DisplayBothClocks()
15338 {
15339     int wom = gameMode == EditPosition ?
15340       !blackPlaysFirst : WhiteOnMove(currentMove);
15341     DisplayWhiteClock(whiteTimeRemaining, wom);
15342     DisplayBlackClock(blackTimeRemaining, !wom);
15343 }
15344
15345
15346 /* Timekeeping seems to be a portability nightmare.  I think everyone
15347    has ftime(), but I'm really not sure, so I'm including some ifdefs
15348    to use other calls if you don't.  Clocks will be less accurate if
15349    you have neither ftime nor gettimeofday.
15350 */
15351
15352 /* VS 2008 requires the #include outside of the function */
15353 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15354 #include <sys/timeb.h>
15355 #endif
15356
15357 /* Get the current time as a TimeMark */
15358 void
15359 GetTimeMark(tm)
15360      TimeMark *tm;
15361 {
15362 #if HAVE_GETTIMEOFDAY
15363
15364     struct timeval timeVal;
15365     struct timezone timeZone;
15366
15367     gettimeofday(&timeVal, &timeZone);
15368     tm->sec = (long) timeVal.tv_sec;
15369     tm->ms = (int) (timeVal.tv_usec / 1000L);
15370
15371 #else /*!HAVE_GETTIMEOFDAY*/
15372 #if HAVE_FTIME
15373
15374 // include <sys/timeb.h> / moved to just above start of function
15375     struct timeb timeB;
15376
15377     ftime(&timeB);
15378     tm->sec = (long) timeB.time;
15379     tm->ms = (int) timeB.millitm;
15380
15381 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15382     tm->sec = (long) time(NULL);
15383     tm->ms = 0;
15384 #endif
15385 #endif
15386 }
15387
15388 /* Return the difference in milliseconds between two
15389    time marks.  We assume the difference will fit in a long!
15390 */
15391 long
15392 SubtractTimeMarks(tm2, tm1)
15393      TimeMark *tm2, *tm1;
15394 {
15395     return 1000L*(tm2->sec - tm1->sec) +
15396            (long) (tm2->ms - tm1->ms);
15397 }
15398
15399
15400 /*
15401  * Code to manage the game clocks.
15402  *
15403  * In tournament play, black starts the clock and then white makes a move.
15404  * We give the human user a slight advantage if he is playing white---the
15405  * clocks don't run until he makes his first move, so it takes zero time.
15406  * Also, we don't account for network lag, so we could get out of sync
15407  * with GNU Chess's clock -- but then, referees are always right.
15408  */
15409
15410 static TimeMark tickStartTM;
15411 static long intendedTickLength;
15412
15413 long
15414 NextTickLength(timeRemaining)
15415      long timeRemaining;
15416 {
15417     long nominalTickLength, nextTickLength;
15418
15419     if (timeRemaining > 0L && timeRemaining <= 10000L)
15420       nominalTickLength = 100L;
15421     else
15422       nominalTickLength = 1000L;
15423     nextTickLength = timeRemaining % nominalTickLength;
15424     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15425
15426     return nextTickLength;
15427 }
15428
15429 /* Adjust clock one minute up or down */
15430 void
15431 AdjustClock(Boolean which, int dir)
15432 {
15433     if(which) blackTimeRemaining += 60000*dir;
15434     else      whiteTimeRemaining += 60000*dir;
15435     DisplayBothClocks();
15436 }
15437
15438 /* Stop clocks and reset to a fresh time control */
15439 void
15440 ResetClocks()
15441 {
15442     (void) StopClockTimer();
15443     if (appData.icsActive) {
15444         whiteTimeRemaining = blackTimeRemaining = 0;
15445     } else if (searchTime) {
15446         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15447         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15448     } else { /* [HGM] correct new time quote for time odds */
15449         whiteTC = blackTC = fullTimeControlString;
15450         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15451         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15452     }
15453     if (whiteFlag || blackFlag) {
15454         DisplayTitle("");
15455         whiteFlag = blackFlag = FALSE;
15456     }
15457     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15458     DisplayBothClocks();
15459 }
15460
15461 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15462
15463 /* Decrement running clock by amount of time that has passed */
15464 void
15465 DecrementClocks()
15466 {
15467     long timeRemaining;
15468     long lastTickLength, fudge;
15469     TimeMark now;
15470
15471     if (!appData.clockMode) return;
15472     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15473
15474     GetTimeMark(&now);
15475
15476     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15477
15478     /* Fudge if we woke up a little too soon */
15479     fudge = intendedTickLength - lastTickLength;
15480     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15481
15482     if (WhiteOnMove(forwardMostMove)) {
15483         if(whiteNPS >= 0) lastTickLength = 0;
15484         timeRemaining = whiteTimeRemaining -= lastTickLength;
15485         if(timeRemaining < 0 && !appData.icsActive) {
15486             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15487             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15488                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15489                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15490             }
15491         }
15492         DisplayWhiteClock(whiteTimeRemaining - fudge,
15493                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15494     } else {
15495         if(blackNPS >= 0) lastTickLength = 0;
15496         timeRemaining = blackTimeRemaining -= lastTickLength;
15497         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15498             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15499             if(suddenDeath) {
15500                 blackStartMove = forwardMostMove;
15501                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15502             }
15503         }
15504         DisplayBlackClock(blackTimeRemaining - fudge,
15505                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15506     }
15507     if (CheckFlags()) return;
15508
15509     tickStartTM = now;
15510     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15511     StartClockTimer(intendedTickLength);
15512
15513     /* if the time remaining has fallen below the alarm threshold, sound the
15514      * alarm. if the alarm has sounded and (due to a takeback or time control
15515      * with increment) the time remaining has increased to a level above the
15516      * threshold, reset the alarm so it can sound again.
15517      */
15518
15519     if (appData.icsActive && appData.icsAlarm) {
15520
15521         /* make sure we are dealing with the user's clock */
15522         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15523                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15524            )) return;
15525
15526         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15527             alarmSounded = FALSE;
15528         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15529             PlayAlarmSound();
15530             alarmSounded = TRUE;
15531         }
15532     }
15533 }
15534
15535
15536 /* A player has just moved, so stop the previously running
15537    clock and (if in clock mode) start the other one.
15538    We redisplay both clocks in case we're in ICS mode, because
15539    ICS gives us an update to both clocks after every move.
15540    Note that this routine is called *after* forwardMostMove
15541    is updated, so the last fractional tick must be subtracted
15542    from the color that is *not* on move now.
15543 */
15544 void
15545 SwitchClocks(int newMoveNr)
15546 {
15547     long lastTickLength;
15548     TimeMark now;
15549     int flagged = FALSE;
15550
15551     GetTimeMark(&now);
15552
15553     if (StopClockTimer() && appData.clockMode) {
15554         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15555         if (!WhiteOnMove(forwardMostMove)) {
15556             if(blackNPS >= 0) lastTickLength = 0;
15557             blackTimeRemaining -= lastTickLength;
15558            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15559 //         if(pvInfoList[forwardMostMove].time == -1)
15560                  pvInfoList[forwardMostMove].time =               // use GUI time
15561                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15562         } else {
15563            if(whiteNPS >= 0) lastTickLength = 0;
15564            whiteTimeRemaining -= lastTickLength;
15565            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15566 //         if(pvInfoList[forwardMostMove].time == -1)
15567                  pvInfoList[forwardMostMove].time =
15568                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15569         }
15570         flagged = CheckFlags();
15571     }
15572     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15573     CheckTimeControl();
15574
15575     if (flagged || !appData.clockMode) return;
15576
15577     switch (gameMode) {
15578       case MachinePlaysBlack:
15579       case MachinePlaysWhite:
15580       case BeginningOfGame:
15581         if (pausing) return;
15582         break;
15583
15584       case EditGame:
15585       case PlayFromGameFile:
15586       case IcsExamining:
15587         return;
15588
15589       default:
15590         break;
15591     }
15592
15593     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15594         if(WhiteOnMove(forwardMostMove))
15595              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15596         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15597     }
15598
15599     tickStartTM = now;
15600     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15601       whiteTimeRemaining : blackTimeRemaining);
15602     StartClockTimer(intendedTickLength);
15603 }
15604
15605
15606 /* Stop both clocks */
15607 void
15608 StopClocks()
15609 {
15610     long lastTickLength;
15611     TimeMark now;
15612
15613     if (!StopClockTimer()) return;
15614     if (!appData.clockMode) return;
15615
15616     GetTimeMark(&now);
15617
15618     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15619     if (WhiteOnMove(forwardMostMove)) {
15620         if(whiteNPS >= 0) lastTickLength = 0;
15621         whiteTimeRemaining -= lastTickLength;
15622         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15623     } else {
15624         if(blackNPS >= 0) lastTickLength = 0;
15625         blackTimeRemaining -= lastTickLength;
15626         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15627     }
15628     CheckFlags();
15629 }
15630
15631 /* Start clock of player on move.  Time may have been reset, so
15632    if clock is already running, stop and restart it. */
15633 void
15634 StartClocks()
15635 {
15636     (void) StopClockTimer(); /* in case it was running already */
15637     DisplayBothClocks();
15638     if (CheckFlags()) return;
15639
15640     if (!appData.clockMode) return;
15641     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15642
15643     GetTimeMark(&tickStartTM);
15644     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15645       whiteTimeRemaining : blackTimeRemaining);
15646
15647    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15648     whiteNPS = blackNPS = -1;
15649     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15650        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15651         whiteNPS = first.nps;
15652     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15653        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15654         blackNPS = first.nps;
15655     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15656         whiteNPS = second.nps;
15657     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15658         blackNPS = second.nps;
15659     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15660
15661     StartClockTimer(intendedTickLength);
15662 }
15663
15664 char *
15665 TimeString(ms)
15666      long ms;
15667 {
15668     long second, minute, hour, day;
15669     char *sign = "";
15670     static char buf[32];
15671
15672     if (ms > 0 && ms <= 9900) {
15673       /* convert milliseconds to tenths, rounding up */
15674       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15675
15676       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15677       return buf;
15678     }
15679
15680     /* convert milliseconds to seconds, rounding up */
15681     /* use floating point to avoid strangeness of integer division
15682        with negative dividends on many machines */
15683     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15684
15685     if (second < 0) {
15686         sign = "-";
15687         second = -second;
15688     }
15689
15690     day = second / (60 * 60 * 24);
15691     second = second % (60 * 60 * 24);
15692     hour = second / (60 * 60);
15693     second = second % (60 * 60);
15694     minute = second / 60;
15695     second = second % 60;
15696
15697     if (day > 0)
15698       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15699               sign, day, hour, minute, second);
15700     else if (hour > 0)
15701       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15702     else
15703       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15704
15705     return buf;
15706 }
15707
15708
15709 /*
15710  * This is necessary because some C libraries aren't ANSI C compliant yet.
15711  */
15712 char *
15713 StrStr(string, match)
15714      char *string, *match;
15715 {
15716     int i, length;
15717
15718     length = strlen(match);
15719
15720     for (i = strlen(string) - length; i >= 0; i--, string++)
15721       if (!strncmp(match, string, length))
15722         return string;
15723
15724     return NULL;
15725 }
15726
15727 char *
15728 StrCaseStr(string, match)
15729      char *string, *match;
15730 {
15731     int i, j, length;
15732
15733     length = strlen(match);
15734
15735     for (i = strlen(string) - length; i >= 0; i--, string++) {
15736         for (j = 0; j < length; j++) {
15737             if (ToLower(match[j]) != ToLower(string[j]))
15738               break;
15739         }
15740         if (j == length) return string;
15741     }
15742
15743     return NULL;
15744 }
15745
15746 #ifndef _amigados
15747 int
15748 StrCaseCmp(s1, s2)
15749      char *s1, *s2;
15750 {
15751     char c1, c2;
15752
15753     for (;;) {
15754         c1 = ToLower(*s1++);
15755         c2 = ToLower(*s2++);
15756         if (c1 > c2) return 1;
15757         if (c1 < c2) return -1;
15758         if (c1 == NULLCHAR) return 0;
15759     }
15760 }
15761
15762
15763 int
15764 ToLower(c)
15765      int c;
15766 {
15767     return isupper(c) ? tolower(c) : c;
15768 }
15769
15770
15771 int
15772 ToUpper(c)
15773      int c;
15774 {
15775     return islower(c) ? toupper(c) : c;
15776 }
15777 #endif /* !_amigados    */
15778
15779 char *
15780 StrSave(s)
15781      char *s;
15782 {
15783   char *ret;
15784
15785   if ((ret = (char *) malloc(strlen(s) + 1)))
15786     {
15787       safeStrCpy(ret, s, strlen(s)+1);
15788     }
15789   return ret;
15790 }
15791
15792 char *
15793 StrSavePtr(s, savePtr)
15794      char *s, **savePtr;
15795 {
15796     if (*savePtr) {
15797         free(*savePtr);
15798     }
15799     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15800       safeStrCpy(*savePtr, s, strlen(s)+1);
15801     }
15802     return(*savePtr);
15803 }
15804
15805 char *
15806 PGNDate()
15807 {
15808     time_t clock;
15809     struct tm *tm;
15810     char buf[MSG_SIZ];
15811
15812     clock = time((time_t *)NULL);
15813     tm = localtime(&clock);
15814     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15815             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15816     return StrSave(buf);
15817 }
15818
15819
15820 char *
15821 PositionToFEN(move, overrideCastling)
15822      int move;
15823      char *overrideCastling;
15824 {
15825     int i, j, fromX, fromY, toX, toY;
15826     int whiteToPlay;
15827     char buf[MSG_SIZ];
15828     char *p, *q;
15829     int emptycount;
15830     ChessSquare piece;
15831
15832     whiteToPlay = (gameMode == EditPosition) ?
15833       !blackPlaysFirst : (move % 2 == 0);
15834     p = buf;
15835
15836     /* Piece placement data */
15837     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15838         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
15839         emptycount = 0;
15840         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15841             if (boards[move][i][j] == EmptySquare) {
15842                 emptycount++;
15843             } else { ChessSquare piece = boards[move][i][j];
15844                 if (emptycount > 0) {
15845                     if(emptycount<10) /* [HGM] can be >= 10 */
15846                         *p++ = '0' + emptycount;
15847                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15848                     emptycount = 0;
15849                 }
15850                 if(PieceToChar(piece) == '+') {
15851                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15852                     *p++ = '+';
15853                     piece = (ChessSquare)(DEMOTED piece);
15854                 }
15855                 *p++ = PieceToChar(piece);
15856                 if(p[-1] == '~') {
15857                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15858                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15859                     *p++ = '~';
15860                 }
15861             }
15862         }
15863         if (emptycount > 0) {
15864             if(emptycount<10) /* [HGM] can be >= 10 */
15865                 *p++ = '0' + emptycount;
15866             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15867             emptycount = 0;
15868         }
15869         *p++ = '/';
15870     }
15871     *(p - 1) = ' ';
15872
15873     /* [HGM] print Crazyhouse or Shogi holdings */
15874     if( gameInfo.holdingsWidth ) {
15875         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15876         q = p;
15877         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15878             piece = boards[move][i][BOARD_WIDTH-1];
15879             if( piece != EmptySquare )
15880               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15881                   *p++ = PieceToChar(piece);
15882         }
15883         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15884             piece = boards[move][BOARD_HEIGHT-i-1][0];
15885             if( piece != EmptySquare )
15886               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15887                   *p++ = PieceToChar(piece);
15888         }
15889
15890         if( q == p ) *p++ = '-';
15891         *p++ = ']';
15892         *p++ = ' ';
15893     }
15894
15895     /* Active color */
15896     *p++ = whiteToPlay ? 'w' : 'b';
15897     *p++ = ' ';
15898
15899   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15900     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15901   } else {
15902   if(nrCastlingRights) {
15903      q = p;
15904      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15905        /* [HGM] write directly from rights */
15906            if(boards[move][CASTLING][2] != NoRights &&
15907               boards[move][CASTLING][0] != NoRights   )
15908                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15909            if(boards[move][CASTLING][2] != NoRights &&
15910               boards[move][CASTLING][1] != NoRights   )
15911                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15912            if(boards[move][CASTLING][5] != NoRights &&
15913               boards[move][CASTLING][3] != NoRights   )
15914                 *p++ = boards[move][CASTLING][3] + AAA;
15915            if(boards[move][CASTLING][5] != NoRights &&
15916               boards[move][CASTLING][4] != NoRights   )
15917                 *p++ = boards[move][CASTLING][4] + AAA;
15918      } else {
15919
15920         /* [HGM] write true castling rights */
15921         if( nrCastlingRights == 6 ) {
15922             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15923                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15924             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15925                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15926             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15927                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15928             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15929                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15930         }
15931      }
15932      if (q == p) *p++ = '-'; /* No castling rights */
15933      *p++ = ' ';
15934   }
15935
15936   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15937      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15938     /* En passant target square */
15939     if (move > backwardMostMove) {
15940         fromX = moveList[move - 1][0] - AAA;
15941         fromY = moveList[move - 1][1] - ONE;
15942         toX = moveList[move - 1][2] - AAA;
15943         toY = moveList[move - 1][3] - ONE;
15944         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15945             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15946             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15947             fromX == toX) {
15948             /* 2-square pawn move just happened */
15949             *p++ = toX + AAA;
15950             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15951         } else {
15952             *p++ = '-';
15953         }
15954     } else if(move == backwardMostMove) {
15955         // [HGM] perhaps we should always do it like this, and forget the above?
15956         if((signed char)boards[move][EP_STATUS] >= 0) {
15957             *p++ = boards[move][EP_STATUS] + AAA;
15958             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15959         } else {
15960             *p++ = '-';
15961         }
15962     } else {
15963         *p++ = '-';
15964     }
15965     *p++ = ' ';
15966   }
15967   }
15968
15969     /* [HGM] find reversible plies */
15970     {   int i = 0, j=move;
15971
15972         if (appData.debugMode) { int k;
15973             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15974             for(k=backwardMostMove; k<=forwardMostMove; k++)
15975                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15976
15977         }
15978
15979         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15980         if( j == backwardMostMove ) i += initialRulePlies;
15981         sprintf(p, "%d ", i);
15982         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15983     }
15984     /* Fullmove number */
15985     sprintf(p, "%d", (move / 2) + 1);
15986
15987     return StrSave(buf);
15988 }
15989
15990 Boolean
15991 ParseFEN(board, blackPlaysFirst, fen)
15992     Board board;
15993      int *blackPlaysFirst;
15994      char *fen;
15995 {
15996     int i, j;
15997     char *p, c;
15998     int emptycount;
15999     ChessSquare piece;
16000
16001     p = fen;
16002
16003     /* [HGM] by default clear Crazyhouse holdings, if present */
16004     if(gameInfo.holdingsWidth) {
16005        for(i=0; i<BOARD_HEIGHT; i++) {
16006            board[i][0]             = EmptySquare; /* black holdings */
16007            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16008            board[i][1]             = (ChessSquare) 0; /* black counts */
16009            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16010        }
16011     }
16012
16013     /* Piece placement data */
16014     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16015         j = 0;
16016         for (;;) {
16017             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16018                 if (*p == '/') p++;
16019                 emptycount = gameInfo.boardWidth - j;
16020                 while (emptycount--)
16021                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16022                 break;
16023 #if(BOARD_FILES >= 10)
16024             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16025                 p++; emptycount=10;
16026                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16027                 while (emptycount--)
16028                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16029 #endif
16030             } else if (isdigit(*p)) {
16031                 emptycount = *p++ - '0';
16032                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16033                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16034                 while (emptycount--)
16035                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16036             } else if (*p == '+' || isalpha(*p)) {
16037                 if (j >= gameInfo.boardWidth) return FALSE;
16038                 if(*p=='+') {
16039                     piece = CharToPiece(*++p);
16040                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16041                     piece = (ChessSquare) (PROMOTED piece ); p++;
16042                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16043                 } else piece = CharToPiece(*p++);
16044
16045                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16046                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16047                     piece = (ChessSquare) (PROMOTED piece);
16048                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16049                     p++;
16050                 }
16051                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16052             } else {
16053                 return FALSE;
16054             }
16055         }
16056     }
16057     while (*p == '/' || *p == ' ') p++;
16058
16059     /* [HGM] look for Crazyhouse holdings here */
16060     while(*p==' ') p++;
16061     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16062         if(*p == '[') p++;
16063         if(*p == '-' ) p++; /* empty holdings */ else {
16064             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16065             /* if we would allow FEN reading to set board size, we would   */
16066             /* have to add holdings and shift the board read so far here   */
16067             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16068                 p++;
16069                 if((int) piece >= (int) BlackPawn ) {
16070                     i = (int)piece - (int)BlackPawn;
16071                     i = PieceToNumber((ChessSquare)i);
16072                     if( i >= gameInfo.holdingsSize ) return FALSE;
16073                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16074                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16075                 } else {
16076                     i = (int)piece - (int)WhitePawn;
16077                     i = PieceToNumber((ChessSquare)i);
16078                     if( i >= gameInfo.holdingsSize ) return FALSE;
16079                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16080                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16081                 }
16082             }
16083         }
16084         if(*p == ']') p++;
16085     }
16086
16087     while(*p == ' ') p++;
16088
16089     /* Active color */
16090     c = *p++;
16091     if(appData.colorNickNames) {
16092       if( c == appData.colorNickNames[0] ) c = 'w'; else
16093       if( c == appData.colorNickNames[1] ) c = 'b';
16094     }
16095     switch (c) {
16096       case 'w':
16097         *blackPlaysFirst = FALSE;
16098         break;
16099       case 'b':
16100         *blackPlaysFirst = TRUE;
16101         break;
16102       default:
16103         return FALSE;
16104     }
16105
16106     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16107     /* return the extra info in global variiables             */
16108
16109     /* set defaults in case FEN is incomplete */
16110     board[EP_STATUS] = EP_UNKNOWN;
16111     for(i=0; i<nrCastlingRights; i++ ) {
16112         board[CASTLING][i] =
16113             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16114     }   /* assume possible unless obviously impossible */
16115     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16116     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16117     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16118                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16119     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16120     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16121     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16122                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16123     FENrulePlies = 0;
16124
16125     while(*p==' ') p++;
16126     if(nrCastlingRights) {
16127       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16128           /* castling indicator present, so default becomes no castlings */
16129           for(i=0; i<nrCastlingRights; i++ ) {
16130                  board[CASTLING][i] = NoRights;
16131           }
16132       }
16133       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16134              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16135              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16136              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16137         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16138
16139         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16140             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16141             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16142         }
16143         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16144             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16145         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16146                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16147         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16148                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16149         switch(c) {
16150           case'K':
16151               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16152               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16153               board[CASTLING][2] = whiteKingFile;
16154               break;
16155           case'Q':
16156               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16157               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16158               board[CASTLING][2] = whiteKingFile;
16159               break;
16160           case'k':
16161               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16162               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16163               board[CASTLING][5] = blackKingFile;
16164               break;
16165           case'q':
16166               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16167               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16168               board[CASTLING][5] = blackKingFile;
16169           case '-':
16170               break;
16171           default: /* FRC castlings */
16172               if(c >= 'a') { /* black rights */
16173                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16174                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16175                   if(i == BOARD_RGHT) break;
16176                   board[CASTLING][5] = i;
16177                   c -= AAA;
16178                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16179                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16180                   if(c > i)
16181                       board[CASTLING][3] = c;
16182                   else
16183                       board[CASTLING][4] = c;
16184               } else { /* white rights */
16185                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16186                     if(board[0][i] == WhiteKing) break;
16187                   if(i == BOARD_RGHT) break;
16188                   board[CASTLING][2] = i;
16189                   c -= AAA - 'a' + 'A';
16190                   if(board[0][c] >= WhiteKing) break;
16191                   if(c > i)
16192                       board[CASTLING][0] = c;
16193                   else
16194                       board[CASTLING][1] = c;
16195               }
16196         }
16197       }
16198       for(i=0; i<nrCastlingRights; i++)
16199         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16200     if (appData.debugMode) {
16201         fprintf(debugFP, "FEN castling rights:");
16202         for(i=0; i<nrCastlingRights; i++)
16203         fprintf(debugFP, " %d", board[CASTLING][i]);
16204         fprintf(debugFP, "\n");
16205     }
16206
16207       while(*p==' ') p++;
16208     }
16209
16210     /* read e.p. field in games that know e.p. capture */
16211     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16212        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16213       if(*p=='-') {
16214         p++; board[EP_STATUS] = EP_NONE;
16215       } else {
16216          char c = *p++ - AAA;
16217
16218          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16219          if(*p >= '0' && *p <='9') p++;
16220          board[EP_STATUS] = c;
16221       }
16222     }
16223
16224
16225     if(sscanf(p, "%d", &i) == 1) {
16226         FENrulePlies = i; /* 50-move ply counter */
16227         /* (The move number is still ignored)    */
16228     }
16229
16230     return TRUE;
16231 }
16232
16233 void
16234 EditPositionPasteFEN(char *fen)
16235 {
16236   if (fen != NULL) {
16237     Board initial_position;
16238
16239     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16240       DisplayError(_("Bad FEN position in clipboard"), 0);
16241       return ;
16242     } else {
16243       int savedBlackPlaysFirst = blackPlaysFirst;
16244       EditPositionEvent();
16245       blackPlaysFirst = savedBlackPlaysFirst;
16246       CopyBoard(boards[0], initial_position);
16247       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16248       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16249       DisplayBothClocks();
16250       DrawPosition(FALSE, boards[currentMove]);
16251     }
16252   }
16253 }
16254
16255 static char cseq[12] = "\\   ";
16256
16257 Boolean set_cont_sequence(char *new_seq)
16258 {
16259     int len;
16260     Boolean ret;
16261
16262     // handle bad attempts to set the sequence
16263         if (!new_seq)
16264                 return 0; // acceptable error - no debug
16265
16266     len = strlen(new_seq);
16267     ret = (len > 0) && (len < sizeof(cseq));
16268     if (ret)
16269       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16270     else if (appData.debugMode)
16271       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16272     return ret;
16273 }
16274
16275 /*
16276     reformat a source message so words don't cross the width boundary.  internal
16277     newlines are not removed.  returns the wrapped size (no null character unless
16278     included in source message).  If dest is NULL, only calculate the size required
16279     for the dest buffer.  lp argument indicats line position upon entry, and it's
16280     passed back upon exit.
16281 */
16282 int wrap(char *dest, char *src, int count, int width, int *lp)
16283 {
16284     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16285
16286     cseq_len = strlen(cseq);
16287     old_line = line = *lp;
16288     ansi = len = clen = 0;
16289
16290     for (i=0; i < count; i++)
16291     {
16292         if (src[i] == '\033')
16293             ansi = 1;
16294
16295         // if we hit the width, back up
16296         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16297         {
16298             // store i & len in case the word is too long
16299             old_i = i, old_len = len;
16300
16301             // find the end of the last word
16302             while (i && src[i] != ' ' && src[i] != '\n')
16303             {
16304                 i--;
16305                 len--;
16306             }
16307
16308             // word too long?  restore i & len before splitting it
16309             if ((old_i-i+clen) >= width)
16310             {
16311                 i = old_i;
16312                 len = old_len;
16313             }
16314
16315             // extra space?
16316             if (i && src[i-1] == ' ')
16317                 len--;
16318
16319             if (src[i] != ' ' && src[i] != '\n')
16320             {
16321                 i--;
16322                 if (len)
16323                     len--;
16324             }
16325
16326             // now append the newline and continuation sequence
16327             if (dest)
16328                 dest[len] = '\n';
16329             len++;
16330             if (dest)
16331                 strncpy(dest+len, cseq, cseq_len);
16332             len += cseq_len;
16333             line = cseq_len;
16334             clen = cseq_len;
16335             continue;
16336         }
16337
16338         if (dest)
16339             dest[len] = src[i];
16340         len++;
16341         if (!ansi)
16342             line++;
16343         if (src[i] == '\n')
16344             line = 0;
16345         if (src[i] == 'm')
16346             ansi = 0;
16347     }
16348     if (dest && appData.debugMode)
16349     {
16350         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16351             count, width, line, len, *lp);
16352         show_bytes(debugFP, src, count);
16353         fprintf(debugFP, "\ndest: ");
16354         show_bytes(debugFP, dest, len);
16355         fprintf(debugFP, "\n");
16356     }
16357     *lp = dest ? line : old_line;
16358
16359     return len;
16360 }
16361
16362 // [HGM] vari: routines for shelving variations
16363
16364 void
16365 PushInner(int firstMove, int lastMove)
16366 {
16367         int i, j, nrMoves = lastMove - firstMove;
16368
16369         // push current tail of game on stack
16370         savedResult[storedGames] = gameInfo.result;
16371         savedDetails[storedGames] = gameInfo.resultDetails;
16372         gameInfo.resultDetails = NULL;
16373         savedFirst[storedGames] = firstMove;
16374         savedLast [storedGames] = lastMove;
16375         savedFramePtr[storedGames] = framePtr;
16376         framePtr -= nrMoves; // reserve space for the boards
16377         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16378             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16379             for(j=0; j<MOVE_LEN; j++)
16380                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16381             for(j=0; j<2*MOVE_LEN; j++)
16382                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16383             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16384             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16385             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16386             pvInfoList[firstMove+i-1].depth = 0;
16387             commentList[framePtr+i] = commentList[firstMove+i];
16388             commentList[firstMove+i] = NULL;
16389         }
16390
16391         storedGames++;
16392         forwardMostMove = firstMove; // truncate game so we can start variation
16393 }
16394
16395 void
16396 PushTail(int firstMove, int lastMove)
16397 {
16398         if(appData.icsActive) { // only in local mode
16399                 forwardMostMove = currentMove; // mimic old ICS behavior
16400                 return;
16401         }
16402         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16403
16404         PushInner(firstMove, lastMove);
16405         if(storedGames == 1) GreyRevert(FALSE);
16406 }
16407
16408 void
16409 PopInner(Boolean annotate)
16410 {
16411         int i, j, nrMoves;
16412         char buf[8000], moveBuf[20];
16413
16414         storedGames--;
16415         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16416         nrMoves = savedLast[storedGames] - currentMove;
16417         if(annotate) {
16418                 int cnt = 10;
16419                 if(!WhiteOnMove(currentMove))
16420                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16421                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16422                 for(i=currentMove; i<forwardMostMove; i++) {
16423                         if(WhiteOnMove(i))
16424                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16425                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16426                         strcat(buf, moveBuf);
16427                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16428                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16429                 }
16430                 strcat(buf, ")");
16431         }
16432         for(i=1; i<=nrMoves; i++) { // copy last variation back
16433             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16434             for(j=0; j<MOVE_LEN; j++)
16435                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16436             for(j=0; j<2*MOVE_LEN; j++)
16437                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16438             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16439             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16440             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16441             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16442             commentList[currentMove+i] = commentList[framePtr+i];
16443             commentList[framePtr+i] = NULL;
16444         }
16445         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16446         framePtr = savedFramePtr[storedGames];
16447         gameInfo.result = savedResult[storedGames];
16448         if(gameInfo.resultDetails != NULL) {
16449             free(gameInfo.resultDetails);
16450       }
16451         gameInfo.resultDetails = savedDetails[storedGames];
16452         forwardMostMove = currentMove + nrMoves;
16453 }
16454
16455 Boolean
16456 PopTail(Boolean annotate)
16457 {
16458         if(appData.icsActive) return FALSE; // only in local mode
16459         if(!storedGames) return FALSE; // sanity
16460         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16461
16462         PopInner(annotate);
16463
16464         if(storedGames == 0) GreyRevert(TRUE);
16465         return TRUE;
16466 }
16467
16468 void
16469 CleanupTail()
16470 {       // remove all shelved variations
16471         int i;
16472         for(i=0; i<storedGames; i++) {
16473             if(savedDetails[i])
16474                 free(savedDetails[i]);
16475             savedDetails[i] = NULL;
16476         }
16477         for(i=framePtr; i<MAX_MOVES; i++) {
16478                 if(commentList[i]) free(commentList[i]);
16479                 commentList[i] = NULL;
16480         }
16481         framePtr = MAX_MOVES-1;
16482         storedGames = 0;
16483 }
16484
16485 void
16486 LoadVariation(int index, char *text)
16487 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16488         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16489         int level = 0, move;
16490
16491         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16492         // first find outermost bracketing variation
16493         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16494             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16495                 if(*p == '{') wait = '}'; else
16496                 if(*p == '[') wait = ']'; else
16497                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16498                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16499             }
16500             if(*p == wait) wait = NULLCHAR; // closing ]} found
16501             p++;
16502         }
16503         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16504         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16505         end[1] = NULLCHAR; // clip off comment beyond variation
16506         ToNrEvent(currentMove-1);
16507         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16508         // kludge: use ParsePV() to append variation to game
16509         move = currentMove;
16510         ParsePV(start, TRUE, TRUE);
16511         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16512         ClearPremoveHighlights();
16513         CommentPopDown();
16514         ToNrEvent(currentMove+1);
16515 }
16516