Fix AppendComment
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 int flock(int f, int code);
61 #define LOCK_EX 2
62 #define SLASH '\\'
63
64 #else
65
66 #include <sys/file.h>
67 #define DoSleep( n ) if( (n) >= 0) sleep(n)
68 #define SLASH '/'
69
70 #endif
71
72 #include "config.h"
73
74 #include <assert.h>
75 #include <stdio.h>
76 #include <ctype.h>
77 #include <errno.h>
78 #include <sys/types.h>
79 #include <sys/stat.h>
80 #include <math.h>
81 #include <ctype.h>
82
83 #if STDC_HEADERS
84 # include <stdlib.h>
85 # include <string.h>
86 # include <stdarg.h>
87 #else /* not STDC_HEADERS */
88 # if HAVE_STRING_H
89 #  include <string.h>
90 # else /* not HAVE_STRING_H */
91 #  include <strings.h>
92 # endif /* not HAVE_STRING_H */
93 #endif /* not STDC_HEADERS */
94
95 #if HAVE_SYS_FCNTL_H
96 # include <sys/fcntl.h>
97 #else /* not HAVE_SYS_FCNTL_H */
98 # if HAVE_FCNTL_H
99 #  include <fcntl.h>
100 # endif /* HAVE_FCNTL_H */
101 #endif /* not HAVE_SYS_FCNTL_H */
102
103 #if TIME_WITH_SYS_TIME
104 # include <sys/time.h>
105 # include <time.h>
106 #else
107 # if HAVE_SYS_TIME_H
108 #  include <sys/time.h>
109 # else
110 #  include <time.h>
111 # endif
112 #endif
113
114 #if defined(_amigados) && !defined(__GNUC__)
115 struct timezone {
116     int tz_minuteswest;
117     int tz_dsttime;
118 };
119 extern int gettimeofday(struct timeval *, struct timezone *);
120 #endif
121
122 #if HAVE_UNISTD_H
123 # include <unistd.h>
124 #endif
125
126 #include "common.h"
127 #include "frontend.h"
128 #include "backend.h"
129 #include "parser.h"
130 #include "moves.h"
131 #if ZIPPY
132 # include "zippy.h"
133 #endif
134 #include "backendz.h"
135 #include "gettext.h"
136
137 #ifdef ENABLE_NLS
138 # define _(s) gettext (s)
139 # define N_(s) gettext_noop (s)
140 # define T_(s) gettext(s)
141 #else
142 # ifdef WIN32
143 #   define _(s) T_(s)
144 #   define N_(s) s
145 # else
146 #   define _(s) (s)
147 #   define N_(s) s
148 #   define T_(s) s
149 # endif
150 #endif
151
152
153 /* A point in time */
154 typedef struct {
155     long sec;  /* Assuming this is >= 32 bits */
156     int ms;    /* Assuming this is >= 16 bits */
157 } TimeMark;
158
159 int establish P((void));
160 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
161                          char *buf, int count, int error));
162 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
163                       char *buf, int count, int error));
164 void ics_printf P((char *format, ...));
165 void SendToICS P((char *s));
166 void SendToICSDelayed P((char *s, long msdelay));
167 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
168 void HandleMachineMove P((char *message, ChessProgramState *cps));
169 int AutoPlayOneMove P((void));
170 int LoadGameOneMove P((ChessMove readAhead));
171 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
172 int LoadPositionFromFile P((char *filename, int n, char *title));
173 int SavePositionToFile P((char *filename));
174 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
175                                                                                 Board board));
176 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
177 void ShowMove P((int fromX, int fromY, int toX, int toY));
178 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
179                    /*char*/int promoChar));
180 void BackwardInner P((int target));
181 void ForwardInner P((int target));
182 int Adjudicate P((ChessProgramState *cps));
183 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
184 void EditPositionDone P((Boolean fakeRights));
185 void PrintOpponents P((FILE *fp));
186 void PrintPosition P((FILE *fp, int move));
187 void StartChessProgram P((ChessProgramState *cps));
188 void SendToProgram P((char *message, ChessProgramState *cps));
189 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
190 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
191                            char *buf, int count, int error));
192 void SendTimeControl P((ChessProgramState *cps,
193                         int mps, long tc, int inc, int sd, int st));
194 char *TimeControlTagValue P((void));
195 void Attention P((ChessProgramState *cps));
196 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
197 int ResurrectChessProgram P((void));
198 void DisplayComment P((int moveNumber, char *text));
199 void DisplayMove P((int moveNumber));
200
201 void ParseGameHistory P((char *game));
202 void ParseBoard12 P((char *string));
203 void KeepAlive P((void));
204 void StartClocks P((void));
205 void SwitchClocks P((int nr));
206 void StopClocks P((void));
207 void ResetClocks P((void));
208 char *PGNDate P((void));
209 void SetGameInfo P((void));
210 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
211 int RegisterMove P((void));
212 void MakeRegisteredMove P((void));
213 void TruncateGame P((void));
214 int looking_at P((char *, int *, char *));
215 void CopyPlayerNameIntoFileName P((char **, char *));
216 char *SavePart P((char *));
217 int SaveGameOldStyle P((FILE *));
218 int SaveGamePGN P((FILE *));
219 void GetTimeMark P((TimeMark *));
220 long SubtractTimeMarks P((TimeMark *, TimeMark *));
221 int CheckFlags P((void));
222 long NextTickLength P((long));
223 void CheckTimeControl P((void));
224 void show_bytes P((FILE *, char *, int));
225 int string_to_rating P((char *str));
226 void ParseFeatures P((char* args, ChessProgramState *cps));
227 void InitBackEnd3 P((void));
228 void FeatureDone P((ChessProgramState* cps, int val));
229 void InitChessProgram P((ChessProgramState *cps, int setup));
230 void OutputKibitz(int window, char *text);
231 int PerpetualChase(int first, int last);
232 int EngineOutputIsUp();
233 void InitDrawingSizes(int x, int y);
234 void NextMatchGame P((void));
235 int NextTourneyGame P((int nr, int *swap));
236 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
237 FILE *WriteTourneyFile P((char *results, FILE *f));
238 void DisplayTwoMachinesTitle P(());
239
240 #ifdef WIN32
241        extern void ConsoleCreate();
242 #endif
243
244 ChessProgramState *WhitePlayer();
245 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
246 int VerifyDisplayMode P(());
247
248 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
249 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
250 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
251 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
252 void ics_update_width P((int new_width));
253 extern char installDir[MSG_SIZ];
254 VariantClass startVariant; /* [HGM] nicks: initial variant */
255 Boolean abortMatch;
256
257 extern int tinyLayout, smallLayout;
258 ChessProgramStats programStats;
259 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
260 int endPV = -1;
261 static int exiting = 0; /* [HGM] moved to top */
262 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
263 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
264 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
265 int partnerHighlight[2];
266 Boolean partnerBoardValid = 0;
267 char partnerStatus[MSG_SIZ];
268 Boolean partnerUp;
269 Boolean originalFlip;
270 Boolean twoBoards = 0;
271 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
272 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
273 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
274 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
275 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
276 int opponentKibitzes;
277 int lastSavedGame; /* [HGM] save: ID of game */
278 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
279 extern int chatCount;
280 int chattingPartner;
281 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
282 char lastMsg[MSG_SIZ];
283 ChessSquare pieceSweep = EmptySquare;
284 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
285 int promoDefaultAltered;
286
287 /* States for ics_getting_history */
288 #define H_FALSE 0
289 #define H_REQUESTED 1
290 #define H_GOT_REQ_HEADER 2
291 #define H_GOT_UNREQ_HEADER 3
292 #define H_GETTING_MOVES 4
293 #define H_GOT_UNWANTED_HEADER 5
294
295 /* whosays values for GameEnds */
296 #define GE_ICS 0
297 #define GE_ENGINE 1
298 #define GE_PLAYER 2
299 #define GE_FILE 3
300 #define GE_XBOARD 4
301 #define GE_ENGINE1 5
302 #define GE_ENGINE2 6
303
304 /* Maximum number of games in a cmail message */
305 #define CMAIL_MAX_GAMES 20
306
307 /* Different types of move when calling RegisterMove */
308 #define CMAIL_MOVE   0
309 #define CMAIL_RESIGN 1
310 #define CMAIL_DRAW   2
311 #define CMAIL_ACCEPT 3
312
313 /* Different types of result to remember for each game */
314 #define CMAIL_NOT_RESULT 0
315 #define CMAIL_OLD_RESULT 1
316 #define CMAIL_NEW_RESULT 2
317
318 /* Telnet protocol constants */
319 #define TN_WILL 0373
320 #define TN_WONT 0374
321 #define TN_DO   0375
322 #define TN_DONT 0376
323 #define TN_IAC  0377
324 #define TN_ECHO 0001
325 #define TN_SGA  0003
326 #define TN_PORT 23
327
328 char*
329 safeStrCpy( char *dst, const char *src, size_t count )
330 { // [HGM] made safe
331   int i;
332   assert( dst != NULL );
333   assert( src != NULL );
334   assert( count > 0 );
335
336   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
337   if(  i == count && dst[count-1] != NULLCHAR)
338     {
339       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
340       if(appData.debugMode)
341       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
342     }
343
344   return dst;
345 }
346
347 /* Some compiler can't cast u64 to double
348  * This function do the job for us:
349
350  * We use the highest bit for cast, this only
351  * works if the highest bit is not
352  * in use (This should not happen)
353  *
354  * We used this for all compiler
355  */
356 double
357 u64ToDouble(u64 value)
358 {
359   double r;
360   u64 tmp = value & u64Const(0x7fffffffffffffff);
361   r = (double)(s64)tmp;
362   if (value & u64Const(0x8000000000000000))
363        r +=  9.2233720368547758080e18; /* 2^63 */
364  return r;
365 }
366
367 /* Fake up flags for now, as we aren't keeping track of castling
368    availability yet. [HGM] Change of logic: the flag now only
369    indicates the type of castlings allowed by the rule of the game.
370    The actual rights themselves are maintained in the array
371    castlingRights, as part of the game history, and are not probed
372    by this function.
373  */
374 int
375 PosFlags(index)
376 {
377   int flags = F_ALL_CASTLE_OK;
378   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
379   switch (gameInfo.variant) {
380   case VariantSuicide:
381     flags &= ~F_ALL_CASTLE_OK;
382   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
383     flags |= F_IGNORE_CHECK;
384   case VariantLosers:
385     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
386     break;
387   case VariantAtomic:
388     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
389     break;
390   case VariantKriegspiel:
391     flags |= F_KRIEGSPIEL_CAPTURE;
392     break;
393   case VariantCapaRandom:
394   case VariantFischeRandom:
395     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
396   case VariantNoCastle:
397   case VariantShatranj:
398   case VariantCourier:
399   case VariantMakruk:
400   case VariantGrand:
401     flags &= ~F_ALL_CASTLE_OK;
402     break;
403   default:
404     break;
405   }
406   return flags;
407 }
408
409 FILE *gameFileFP, *debugFP;
410
411 /*
412     [AS] Note: sometimes, the sscanf() function is used to parse the input
413     into a fixed-size buffer. Because of this, we must be prepared to
414     receive strings as long as the size of the input buffer, which is currently
415     set to 4K for Windows and 8K for the rest.
416     So, we must either allocate sufficiently large buffers here, or
417     reduce the size of the input buffer in the input reading part.
418 */
419
420 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
421 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
422 char thinkOutput1[MSG_SIZ*10];
423
424 ChessProgramState first, second, pairing;
425
426 /* premove variables */
427 int premoveToX = 0;
428 int premoveToY = 0;
429 int premoveFromX = 0;
430 int premoveFromY = 0;
431 int premovePromoChar = 0;
432 int gotPremove = 0;
433 Boolean alarmSounded;
434 /* end premove variables */
435
436 char *ics_prefix = "$";
437 int ics_type = ICS_GENERIC;
438
439 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
440 int pauseExamForwardMostMove = 0;
441 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
442 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
443 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
444 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
445 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
446 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
447 int whiteFlag = FALSE, blackFlag = FALSE;
448 int userOfferedDraw = FALSE;
449 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
450 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
451 int cmailMoveType[CMAIL_MAX_GAMES];
452 long ics_clock_paused = 0;
453 ProcRef icsPR = NoProc, cmailPR = NoProc;
454 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
455 GameMode gameMode = BeginningOfGame;
456 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
457 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
458 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
459 int hiddenThinkOutputState = 0; /* [AS] */
460 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
461 int adjudicateLossPlies = 6;
462 char white_holding[64], black_holding[64];
463 TimeMark lastNodeCountTime;
464 long lastNodeCount=0;
465 int shiftKey; // [HGM] set by mouse handler
466
467 int have_sent_ICS_logon = 0;
468 int movesPerSession;
469 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
470 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
471 long timeControl_2; /* [AS] Allow separate time controls */
472 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
473 long timeRemaining[2][MAX_MOVES];
474 int matchGame = 0, nextGame = 0, roundNr = 0;
475 Boolean waitingForGame = FALSE;
476 TimeMark programStartTime, pauseStart;
477 char ics_handle[MSG_SIZ];
478 int have_set_title = 0;
479
480 /* animateTraining preserves the state of appData.animate
481  * when Training mode is activated. This allows the
482  * response to be animated when appData.animate == TRUE and
483  * appData.animateDragging == TRUE.
484  */
485 Boolean animateTraining;
486
487 GameInfo gameInfo;
488
489 AppData appData;
490
491 Board boards[MAX_MOVES];
492 /* [HGM] Following 7 needed for accurate legality tests: */
493 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
494 signed char  initialRights[BOARD_FILES];
495 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
496 int   initialRulePlies, FENrulePlies;
497 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
498 int loadFlag = 0;
499 Boolean shuffleOpenings;
500 int mute; // mute all sounds
501
502 // [HGM] vari: next 12 to save and restore variations
503 #define MAX_VARIATIONS 10
504 int framePtr = MAX_MOVES-1; // points to free stack entry
505 int storedGames = 0;
506 int savedFirst[MAX_VARIATIONS];
507 int savedLast[MAX_VARIATIONS];
508 int savedFramePtr[MAX_VARIATIONS];
509 char *savedDetails[MAX_VARIATIONS];
510 ChessMove savedResult[MAX_VARIATIONS];
511
512 void PushTail P((int firstMove, int lastMove));
513 Boolean PopTail P((Boolean annotate));
514 void PushInner P((int firstMove, int lastMove));
515 void PopInner P((Boolean annotate));
516 void CleanupTail P((void));
517
518 ChessSquare  FIDEArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
520         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
522         BlackKing, BlackBishop, BlackKnight, BlackRook }
523 };
524
525 ChessSquare twoKingsArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
527         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
528     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
529         BlackKing, BlackKing, BlackKnight, BlackRook }
530 };
531
532 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
534         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
535     { BlackRook, BlackMan, BlackBishop, BlackQueen,
536         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
537 };
538
539 ChessSquare SpartanArray[2][BOARD_FILES] = {
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
542     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
543         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
544 };
545
546 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
547     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
548         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
549     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
550         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
551 };
552
553 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
554     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
555         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
556     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
557         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
558 };
559
560 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
561     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
562         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
563     { BlackRook, BlackKnight, BlackMan, BlackFerz,
564         BlackKing, BlackMan, BlackKnight, BlackRook }
565 };
566
567
568 #if (BOARD_FILES>=10)
569 ChessSquare ShogiArray[2][BOARD_FILES] = {
570     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
571         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
572     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
573         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
574 };
575
576 ChessSquare XiangqiArray[2][BOARD_FILES] = {
577     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
578         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
580         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
581 };
582
583 ChessSquare CapablancaArray[2][BOARD_FILES] = {
584     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
585         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
587         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
588 };
589
590 ChessSquare GreatArray[2][BOARD_FILES] = {
591     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
592         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
593     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
594         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
595 };
596
597 ChessSquare JanusArray[2][BOARD_FILES] = {
598     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
599         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
600     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
601         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
602 };
603
604 ChessSquare GrandArray[2][BOARD_FILES] = {
605     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
606         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
607     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
608         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
609 };
610
611 #ifdef GOTHIC
612 ChessSquare GothicArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
614         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
616         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
617 };
618 #else // !GOTHIC
619 #define GothicArray CapablancaArray
620 #endif // !GOTHIC
621
622 #ifdef FALCON
623 ChessSquare FalconArray[2][BOARD_FILES] = {
624     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
625         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
626     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
627         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
628 };
629 #else // !FALCON
630 #define FalconArray CapablancaArray
631 #endif // !FALCON
632
633 #else // !(BOARD_FILES>=10)
634 #define XiangqiPosition FIDEArray
635 #define CapablancaArray FIDEArray
636 #define GothicArray FIDEArray
637 #define GreatArray FIDEArray
638 #endif // !(BOARD_FILES>=10)
639
640 #if (BOARD_FILES>=12)
641 ChessSquare CourierArray[2][BOARD_FILES] = {
642     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
643         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
644     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
645         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
646 };
647 #else // !(BOARD_FILES>=12)
648 #define CourierArray CapablancaArray
649 #endif // !(BOARD_FILES>=12)
650
651
652 Board initialPosition;
653
654
655 /* Convert str to a rating. Checks for special cases of "----",
656
657    "++++", etc. Also strips ()'s */
658 int
659 string_to_rating(str)
660   char *str;
661 {
662   while(*str && !isdigit(*str)) ++str;
663   if (!*str)
664     return 0;   /* One of the special "no rating" cases */
665   else
666     return atoi(str);
667 }
668
669 void
670 ClearProgramStats()
671 {
672     /* Init programStats */
673     programStats.movelist[0] = 0;
674     programStats.depth = 0;
675     programStats.nr_moves = 0;
676     programStats.moves_left = 0;
677     programStats.nodes = 0;
678     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
679     programStats.score = 0;
680     programStats.got_only_move = 0;
681     programStats.got_fail = 0;
682     programStats.line_is_book = 0;
683 }
684
685 void
686 CommonEngineInit()
687 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
688     if (appData.firstPlaysBlack) {
689         first.twoMachinesColor = "black\n";
690         second.twoMachinesColor = "white\n";
691     } else {
692         first.twoMachinesColor = "white\n";
693         second.twoMachinesColor = "black\n";
694     }
695
696     first.other = &second;
697     second.other = &first;
698
699     { float norm = 1;
700         if(appData.timeOddsMode) {
701             norm = appData.timeOdds[0];
702             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
703         }
704         first.timeOdds  = appData.timeOdds[0]/norm;
705         second.timeOdds = appData.timeOdds[1]/norm;
706     }
707
708     if(programVersion) free(programVersion);
709     if (appData.noChessProgram) {
710         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
711         sprintf(programVersion, "%s", PACKAGE_STRING);
712     } else {
713       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
714       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
715       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
716     }
717 }
718
719 void
720 UnloadEngine(ChessProgramState *cps)
721 {
722         /* Kill off first chess program */
723         if (cps->isr != NULL)
724           RemoveInputSource(cps->isr);
725         cps->isr = NULL;
726
727         if (cps->pr != NoProc) {
728             ExitAnalyzeMode();
729             DoSleep( appData.delayBeforeQuit );
730             SendToProgram("quit\n", cps);
731             DoSleep( appData.delayAfterQuit );
732             DestroyChildProcess(cps->pr, cps->useSigterm);
733         }
734         cps->pr = NoProc;
735         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
736 }
737
738 void
739 ClearOptions(ChessProgramState *cps)
740 {
741     int i;
742     cps->nrOptions = cps->comboCnt = 0;
743     for(i=0; i<MAX_OPTIONS; i++) {
744         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
745         cps->option[i].textValue = 0;
746     }
747 }
748
749 char *engineNames[] = {
750 "first",
751 "second"
752 };
753
754 void
755 InitEngine(ChessProgramState *cps, int n)
756 {   // [HGM] all engine initialiation put in a function that does one engine
757
758     ClearOptions(cps);
759
760     cps->which = engineNames[n];
761     cps->maybeThinking = FALSE;
762     cps->pr = NoProc;
763     cps->isr = NULL;
764     cps->sendTime = 2;
765     cps->sendDrawOffers = 1;
766
767     cps->program = appData.chessProgram[n];
768     cps->host = appData.host[n];
769     cps->dir = appData.directory[n];
770     cps->initString = appData.engInitString[n];
771     cps->computerString = appData.computerString[n];
772     cps->useSigint  = TRUE;
773     cps->useSigterm = TRUE;
774     cps->reuse = appData.reuse[n];
775     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
776     cps->useSetboard = FALSE;
777     cps->useSAN = FALSE;
778     cps->usePing = FALSE;
779     cps->lastPing = 0;
780     cps->lastPong = 0;
781     cps->usePlayother = FALSE;
782     cps->useColors = TRUE;
783     cps->useUsermove = FALSE;
784     cps->sendICS = FALSE;
785     cps->sendName = appData.icsActive;
786     cps->sdKludge = FALSE;
787     cps->stKludge = FALSE;
788     TidyProgramName(cps->program, cps->host, cps->tidy);
789     cps->matchWins = 0;
790     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
791     cps->analysisSupport = 2; /* detect */
792     cps->analyzing = FALSE;
793     cps->initDone = FALSE;
794
795     /* New features added by Tord: */
796     cps->useFEN960 = FALSE;
797     cps->useOOCastle = TRUE;
798     /* End of new features added by Tord. */
799     cps->fenOverride  = appData.fenOverride[n];
800
801     /* [HGM] time odds: set factor for each machine */
802     cps->timeOdds  = appData.timeOdds[n];
803
804     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
805     cps->accumulateTC = appData.accumulateTC[n];
806     cps->maxNrOfSessions = 1;
807
808     /* [HGM] debug */
809     cps->debug = FALSE;
810
811     cps->supportsNPS = UNKNOWN;
812     cps->memSize = FALSE;
813     cps->maxCores = FALSE;
814     cps->egtFormats[0] = NULLCHAR;
815
816     /* [HGM] options */
817     cps->optionSettings  = appData.engOptions[n];
818
819     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
820     cps->isUCI = appData.isUCI[n]; /* [AS] */
821     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
822
823     if (appData.protocolVersion[n] > PROTOVER
824         || appData.protocolVersion[n] < 1)
825       {
826         char buf[MSG_SIZ];
827         int len;
828
829         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
830                        appData.protocolVersion[n]);
831         if( (len > MSG_SIZ) && appData.debugMode )
832           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
833
834         DisplayFatalError(buf, 0, 2);
835       }
836     else
837       {
838         cps->protocolVersion = appData.protocolVersion[n];
839       }
840
841     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
842 }
843
844 ChessProgramState *savCps;
845
846 void
847 LoadEngine()
848 {
849     int i;
850     if(WaitForEngine(savCps, LoadEngine)) return;
851     CommonEngineInit(); // recalculate time odds
852     if(gameInfo.variant != StringToVariant(appData.variant)) {
853         // we changed variant when loading the engine; this forces us to reset
854         Reset(TRUE, savCps != &first);
855         EditGameEvent(); // for consistency with other path, as Reset changes mode
856     }
857     InitChessProgram(savCps, FALSE);
858     SendToProgram("force\n", savCps);
859     DisplayMessage("", "");
860     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
861     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
862     ThawUI();
863     SetGNUMode();
864 }
865
866 void
867 ReplaceEngine(ChessProgramState *cps, int n)
868 {
869     EditGameEvent();
870     UnloadEngine(cps);
871     appData.noChessProgram = FALSE;
872     appData.clockMode = TRUE;
873     InitEngine(cps, n);
874     UpdateLogos(TRUE);
875     if(n) return; // only startup first engine immediately; second can wait
876     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
877     LoadEngine();
878 }
879
880 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
881 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
882
883 static char resetOptions[] = 
884         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
885         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
886
887 void
888 Load(ChessProgramState *cps, int i)
889 {
890     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
891     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
892         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
893         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
894         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
895         ParseArgsFromString(buf);
896         SwapEngines(i);
897         ReplaceEngine(cps, i);
898         return;
899     }
900     p = engineName;
901     while(q = strchr(p, SLASH)) p = q+1;
902     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
903     if(engineDir[0] != NULLCHAR)
904         appData.directory[i] = engineDir;
905     else if(p != engineName) { // derive directory from engine path, when not given
906         p[-1] = 0;
907         appData.directory[i] = strdup(engineName);
908         p[-1] = SLASH;
909     } else appData.directory[i] = ".";
910     if(params[0]) {
911         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
912         snprintf(command, MSG_SIZ, "%s %s", p, params);
913         p = command;
914     }
915     appData.chessProgram[i] = strdup(p);
916     appData.isUCI[i] = isUCI;
917     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
918     appData.hasOwnBookUCI[i] = hasBook;
919     if(!nickName[0]) useNick = FALSE;
920     if(useNick) ASSIGN(appData.pgnName[i], nickName);
921     if(addToList) {
922         int len;
923         char quote;
924         q = firstChessProgramNames;
925         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
926         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
927         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
928                         quote, p, quote, appData.directory[i], 
929                         useNick ? " -fn \"" : "",
930                         useNick ? nickName : "",
931                         useNick ? "\"" : "",
932                         v1 ? " -firstProtocolVersion 1" : "",
933                         hasBook ? "" : " -fNoOwnBookUCI",
934                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
935                         storeVariant ? " -variant " : "",
936                         storeVariant ? VariantName(gameInfo.variant) : "");
937         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
938         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
939         if(q)   free(q);
940     }
941     ReplaceEngine(cps, i);
942 }
943
944 void
945 InitTimeControls()
946 {
947     int matched, min, sec;
948     /*
949      * Parse timeControl resource
950      */
951     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
952                           appData.movesPerSession)) {
953         char buf[MSG_SIZ];
954         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
955         DisplayFatalError(buf, 0, 2);
956     }
957
958     /*
959      * Parse searchTime resource
960      */
961     if (*appData.searchTime != NULLCHAR) {
962         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
963         if (matched == 1) {
964             searchTime = min * 60;
965         } else if (matched == 2) {
966             searchTime = min * 60 + sec;
967         } else {
968             char buf[MSG_SIZ];
969             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
970             DisplayFatalError(buf, 0, 2);
971         }
972     }
973 }
974
975 void
976 InitBackEnd1()
977 {
978
979     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
980     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
981
982     GetTimeMark(&programStartTime);
983     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
984     appData.seedBase = random() + (random()<<15);
985     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
986
987     ClearProgramStats();
988     programStats.ok_to_send = 1;
989     programStats.seen_stat = 0;
990
991     /*
992      * Initialize game list
993      */
994     ListNew(&gameList);
995
996
997     /*
998      * Internet chess server status
999      */
1000     if (appData.icsActive) {
1001         appData.matchMode = FALSE;
1002         appData.matchGames = 0;
1003 #if ZIPPY
1004         appData.noChessProgram = !appData.zippyPlay;
1005 #else
1006         appData.zippyPlay = FALSE;
1007         appData.zippyTalk = FALSE;
1008         appData.noChessProgram = TRUE;
1009 #endif
1010         if (*appData.icsHelper != NULLCHAR) {
1011             appData.useTelnet = TRUE;
1012             appData.telnetProgram = appData.icsHelper;
1013         }
1014     } else {
1015         appData.zippyTalk = appData.zippyPlay = FALSE;
1016     }
1017
1018     /* [AS] Initialize pv info list [HGM] and game state */
1019     {
1020         int i, j;
1021
1022         for( i=0; i<=framePtr; i++ ) {
1023             pvInfoList[i].depth = -1;
1024             boards[i][EP_STATUS] = EP_NONE;
1025             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1026         }
1027     }
1028
1029     InitTimeControls();
1030
1031     /* [AS] Adjudication threshold */
1032     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1033
1034     InitEngine(&first, 0);
1035     InitEngine(&second, 1);
1036     CommonEngineInit();
1037
1038     pairing.which = "pairing"; // pairing engine
1039     pairing.pr = NoProc;
1040     pairing.isr = NULL;
1041     pairing.program = appData.pairingEngine;
1042     pairing.host = "localhost";
1043     pairing.dir = ".";
1044
1045     if (appData.icsActive) {
1046         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1047     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1048         appData.clockMode = FALSE;
1049         first.sendTime = second.sendTime = 0;
1050     }
1051
1052 #if ZIPPY
1053     /* Override some settings from environment variables, for backward
1054        compatibility.  Unfortunately it's not feasible to have the env
1055        vars just set defaults, at least in xboard.  Ugh.
1056     */
1057     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1058       ZippyInit();
1059     }
1060 #endif
1061
1062     if (!appData.icsActive) {
1063       char buf[MSG_SIZ];
1064       int len;
1065
1066       /* Check for variants that are supported only in ICS mode,
1067          or not at all.  Some that are accepted here nevertheless
1068          have bugs; see comments below.
1069       */
1070       VariantClass variant = StringToVariant(appData.variant);
1071       switch (variant) {
1072       case VariantBughouse:     /* need four players and two boards */
1073       case VariantKriegspiel:   /* need to hide pieces and move details */
1074         /* case VariantFischeRandom: (Fabien: moved below) */
1075         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1076         if( (len > MSG_SIZ) && appData.debugMode )
1077           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1078
1079         DisplayFatalError(buf, 0, 2);
1080         return;
1081
1082       case VariantUnknown:
1083       case VariantLoadable:
1084       case Variant29:
1085       case Variant30:
1086       case Variant31:
1087       case Variant32:
1088       case Variant33:
1089       case Variant34:
1090       case Variant35:
1091       case Variant36:
1092       default:
1093         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1094         if( (len > MSG_SIZ) && appData.debugMode )
1095           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1096
1097         DisplayFatalError(buf, 0, 2);
1098         return;
1099
1100       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1101       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1102       case VariantGothic:     /* [HGM] should work */
1103       case VariantCapablanca: /* [HGM] should work */
1104       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1105       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1106       case VariantKnightmate: /* [HGM] should work */
1107       case VariantCylinder:   /* [HGM] untested */
1108       case VariantFalcon:     /* [HGM] untested */
1109       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1110                                  offboard interposition not understood */
1111       case VariantNormal:     /* definitely works! */
1112       case VariantWildCastle: /* pieces not automatically shuffled */
1113       case VariantNoCastle:   /* pieces not automatically shuffled */
1114       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1115       case VariantLosers:     /* should work except for win condition,
1116                                  and doesn't know captures are mandatory */
1117       case VariantSuicide:    /* should work except for win condition,
1118                                  and doesn't know captures are mandatory */
1119       case VariantGiveaway:   /* should work except for win condition,
1120                                  and doesn't know captures are mandatory */
1121       case VariantTwoKings:   /* should work */
1122       case VariantAtomic:     /* should work except for win condition */
1123       case Variant3Check:     /* should work except for win condition */
1124       case VariantShatranj:   /* should work except for all win conditions */
1125       case VariantMakruk:     /* should work except for draw countdown */
1126       case VariantBerolina:   /* might work if TestLegality is off */
1127       case VariantCapaRandom: /* should work */
1128       case VariantJanus:      /* should work */
1129       case VariantSuper:      /* experimental */
1130       case VariantGreat:      /* experimental, requires legality testing to be off */
1131       case VariantSChess:     /* S-Chess, should work */
1132       case VariantGrand:      /* should work */
1133       case VariantSpartan:    /* should work */
1134         break;
1135       }
1136     }
1137
1138 }
1139
1140 int NextIntegerFromString( char ** str, long * value )
1141 {
1142     int result = -1;
1143     char * s = *str;
1144
1145     while( *s == ' ' || *s == '\t' ) {
1146         s++;
1147     }
1148
1149     *value = 0;
1150
1151     if( *s >= '0' && *s <= '9' ) {
1152         while( *s >= '0' && *s <= '9' ) {
1153             *value = *value * 10 + (*s - '0');
1154             s++;
1155         }
1156
1157         result = 0;
1158     }
1159
1160     *str = s;
1161
1162     return result;
1163 }
1164
1165 int NextTimeControlFromString( char ** str, long * value )
1166 {
1167     long temp;
1168     int result = NextIntegerFromString( str, &temp );
1169
1170     if( result == 0 ) {
1171         *value = temp * 60; /* Minutes */
1172         if( **str == ':' ) {
1173             (*str)++;
1174             result = NextIntegerFromString( str, &temp );
1175             *value += temp; /* Seconds */
1176         }
1177     }
1178
1179     return result;
1180 }
1181
1182 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1183 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1184     int result = -1, type = 0; long temp, temp2;
1185
1186     if(**str != ':') return -1; // old params remain in force!
1187     (*str)++;
1188     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1189     if( NextIntegerFromString( str, &temp ) ) return -1;
1190     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1191
1192     if(**str != '/') {
1193         /* time only: incremental or sudden-death time control */
1194         if(**str == '+') { /* increment follows; read it */
1195             (*str)++;
1196             if(**str == '!') type = *(*str)++; // Bronstein TC
1197             if(result = NextIntegerFromString( str, &temp2)) return -1;
1198             *inc = temp2 * 1000;
1199             if(**str == '.') { // read fraction of increment
1200                 char *start = ++(*str);
1201                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1202                 temp2 *= 1000;
1203                 while(start++ < *str) temp2 /= 10;
1204                 *inc += temp2;
1205             }
1206         } else *inc = 0;
1207         *moves = 0; *tc = temp * 1000; *incType = type;
1208         return 0;
1209     }
1210
1211     (*str)++; /* classical time control */
1212     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1213
1214     if(result == 0) {
1215         *moves = temp;
1216         *tc    = temp2 * 1000;
1217         *inc   = 0;
1218         *incType = type;
1219     }
1220     return result;
1221 }
1222
1223 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1224 {   /* [HGM] get time to add from the multi-session time-control string */
1225     int incType, moves=1; /* kludge to force reading of first session */
1226     long time, increment;
1227     char *s = tcString;
1228
1229     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1230     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1231     do {
1232         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1233         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1234         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1235         if(movenr == -1) return time;    /* last move before new session     */
1236         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1237         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1238         if(!moves) return increment;     /* current session is incremental   */
1239         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1240     } while(movenr >= -1);               /* try again for next session       */
1241
1242     return 0; // no new time quota on this move
1243 }
1244
1245 int
1246 ParseTimeControl(tc, ti, mps)
1247      char *tc;
1248      float ti;
1249      int mps;
1250 {
1251   long tc1;
1252   long tc2;
1253   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1254   int min, sec=0;
1255
1256   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1257   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1258       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1259   if(ti > 0) {
1260
1261     if(mps)
1262       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1263     else 
1264       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1265   } else {
1266     if(mps)
1267       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1268     else 
1269       snprintf(buf, MSG_SIZ, ":%s", mytc);
1270   }
1271   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1272   
1273   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1274     return FALSE;
1275   }
1276
1277   if( *tc == '/' ) {
1278     /* Parse second time control */
1279     tc++;
1280
1281     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1282       return FALSE;
1283     }
1284
1285     if( tc2 == 0 ) {
1286       return FALSE;
1287     }
1288
1289     timeControl_2 = tc2 * 1000;
1290   }
1291   else {
1292     timeControl_2 = 0;
1293   }
1294
1295   if( tc1 == 0 ) {
1296     return FALSE;
1297   }
1298
1299   timeControl = tc1 * 1000;
1300
1301   if (ti >= 0) {
1302     timeIncrement = ti * 1000;  /* convert to ms */
1303     movesPerSession = 0;
1304   } else {
1305     timeIncrement = 0;
1306     movesPerSession = mps;
1307   }
1308   return TRUE;
1309 }
1310
1311 void
1312 InitBackEnd2()
1313 {
1314     if (appData.debugMode) {
1315         fprintf(debugFP, "%s\n", programVersion);
1316     }
1317
1318     set_cont_sequence(appData.wrapContSeq);
1319     if (appData.matchGames > 0) {
1320         appData.matchMode = TRUE;
1321     } else if (appData.matchMode) {
1322         appData.matchGames = 1;
1323     }
1324     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1325         appData.matchGames = appData.sameColorGames;
1326     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1327         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1328         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1329     }
1330     Reset(TRUE, FALSE);
1331     if (appData.noChessProgram || first.protocolVersion == 1) {
1332       InitBackEnd3();
1333     } else {
1334       /* kludge: allow timeout for initial "feature" commands */
1335       FreezeUI();
1336       DisplayMessage("", _("Starting chess program"));
1337       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1338     }
1339 }
1340
1341 int
1342 CalculateIndex(int index, int gameNr)
1343 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1344     int res;
1345     if(index > 0) return index; // fixed nmber
1346     if(index == 0) return 1;
1347     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1348     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1349     return res;
1350 }
1351
1352 int
1353 LoadGameOrPosition(int gameNr)
1354 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1355     if (*appData.loadGameFile != NULLCHAR) {
1356         if (!LoadGameFromFile(appData.loadGameFile,
1357                 CalculateIndex(appData.loadGameIndex, gameNr),
1358                               appData.loadGameFile, FALSE)) {
1359             DisplayFatalError(_("Bad game file"), 0, 1);
1360             return 0;
1361         }
1362     } else if (*appData.loadPositionFile != NULLCHAR) {
1363         if (!LoadPositionFromFile(appData.loadPositionFile,
1364                 CalculateIndex(appData.loadPositionIndex, gameNr),
1365                                   appData.loadPositionFile)) {
1366             DisplayFatalError(_("Bad position file"), 0, 1);
1367             return 0;
1368         }
1369     }
1370     return 1;
1371 }
1372
1373 void
1374 ReserveGame(int gameNr, char resChar)
1375 {
1376     FILE *tf = fopen(appData.tourneyFile, "r+");
1377     char *p, *q, c, buf[MSG_SIZ];
1378     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1379     safeStrCpy(buf, lastMsg, MSG_SIZ);
1380     DisplayMessage(_("Pick new game"), "");
1381     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1382     ParseArgsFromFile(tf);
1383     p = q = appData.results;
1384     if(appData.debugMode) {
1385       char *r = appData.participants;
1386       fprintf(debugFP, "results = '%s'\n", p);
1387       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1388       fprintf(debugFP, "\n");
1389     }
1390     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1391     nextGame = q - p;
1392     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1393     safeStrCpy(q, p, strlen(p) + 2);
1394     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1395     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1396     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1397         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1398         q[nextGame] = '*';
1399     }
1400     fseek(tf, -(strlen(p)+4), SEEK_END);
1401     c = fgetc(tf);
1402     if(c != '"') // depending on DOS or Unix line endings we can be one off
1403          fseek(tf, -(strlen(p)+2), SEEK_END);
1404     else fseek(tf, -(strlen(p)+3), SEEK_END);
1405     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1406     DisplayMessage(buf, "");
1407     free(p); appData.results = q;
1408     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1409        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1410         UnloadEngine(&first);  // next game belongs to other pairing;
1411         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1412     }
1413 }
1414
1415 void
1416 MatchEvent(int mode)
1417 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1418         int dummy;
1419         if(matchMode) { // already in match mode: switch it off
1420             abortMatch = TRUE;
1421             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1422             return;
1423         }
1424 //      if(gameMode != BeginningOfGame) {
1425 //          DisplayError(_("You can only start a match from the initial position."), 0);
1426 //          return;
1427 //      }
1428         abortMatch = FALSE;
1429         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1430         /* Set up machine vs. machine match */
1431         nextGame = 0;
1432         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1433         if(appData.tourneyFile[0]) {
1434             ReserveGame(-1, 0);
1435             if(nextGame > appData.matchGames) {
1436                 char buf[MSG_SIZ];
1437                 if(strchr(appData.results, '*') == NULL) {
1438                     FILE *f;
1439                     appData.tourneyCycles++;
1440                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1441                         fclose(f);
1442                         NextTourneyGame(-1, &dummy);
1443                         ReserveGame(-1, 0);
1444                         if(nextGame <= appData.matchGames) {
1445                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1446                             matchMode = mode;
1447                             ScheduleDelayedEvent(NextMatchGame, 10000);
1448                             return;
1449                         }
1450                     }
1451                 }
1452                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1453                 DisplayError(buf, 0);
1454                 appData.tourneyFile[0] = 0;
1455                 return;
1456             }
1457         } else
1458         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1459             DisplayFatalError(_("Can't have a match with no chess programs"),
1460                               0, 2);
1461             return;
1462         }
1463         matchMode = mode;
1464         matchGame = roundNr = 1;
1465         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1466         NextMatchGame();
1467 }
1468
1469 void
1470 InitBackEnd3 P((void))
1471 {
1472     GameMode initialMode;
1473     char buf[MSG_SIZ];
1474     int err, len;
1475
1476     InitChessProgram(&first, startedFromSetupPosition);
1477
1478     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1479         free(programVersion);
1480         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1481         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1482     }
1483
1484     if (appData.icsActive) {
1485 #ifdef WIN32
1486         /* [DM] Make a console window if needed [HGM] merged ifs */
1487         ConsoleCreate();
1488 #endif
1489         err = establish();
1490         if (err != 0)
1491           {
1492             if (*appData.icsCommPort != NULLCHAR)
1493               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1494                              appData.icsCommPort);
1495             else
1496               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1497                         appData.icsHost, appData.icsPort);
1498
1499             if( (len > MSG_SIZ) && appData.debugMode )
1500               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1501
1502             DisplayFatalError(buf, err, 1);
1503             return;
1504         }
1505         SetICSMode();
1506         telnetISR =
1507           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1508         fromUserISR =
1509           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1510         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1511             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1512     } else if (appData.noChessProgram) {
1513         SetNCPMode();
1514     } else {
1515         SetGNUMode();
1516     }
1517
1518     if (*appData.cmailGameName != NULLCHAR) {
1519         SetCmailMode();
1520         OpenLoopback(&cmailPR);
1521         cmailISR =
1522           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1523     }
1524
1525     ThawUI();
1526     DisplayMessage("", "");
1527     if (StrCaseCmp(appData.initialMode, "") == 0) {
1528       initialMode = BeginningOfGame;
1529       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1530         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1531         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1532         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1533         ModeHighlight();
1534       }
1535     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1536       initialMode = TwoMachinesPlay;
1537     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1538       initialMode = AnalyzeFile;
1539     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1540       initialMode = AnalyzeMode;
1541     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1542       initialMode = MachinePlaysWhite;
1543     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1544       initialMode = MachinePlaysBlack;
1545     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1546       initialMode = EditGame;
1547     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1548       initialMode = EditPosition;
1549     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1550       initialMode = Training;
1551     } else {
1552       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1553       if( (len > MSG_SIZ) && appData.debugMode )
1554         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1555
1556       DisplayFatalError(buf, 0, 2);
1557       return;
1558     }
1559
1560     if (appData.matchMode) {
1561         if(appData.tourneyFile[0]) { // start tourney from command line
1562             FILE *f;
1563             if(f = fopen(appData.tourneyFile, "r")) {
1564                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1565                 fclose(f);
1566                 appData.clockMode = TRUE;
1567                 SetGNUMode();
1568             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1569         }
1570         MatchEvent(TRUE);
1571     } else if (*appData.cmailGameName != NULLCHAR) {
1572         /* Set up cmail mode */
1573         ReloadCmailMsgEvent(TRUE);
1574     } else {
1575         /* Set up other modes */
1576         if (initialMode == AnalyzeFile) {
1577           if (*appData.loadGameFile == NULLCHAR) {
1578             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1579             return;
1580           }
1581         }
1582         if (*appData.loadGameFile != NULLCHAR) {
1583             (void) LoadGameFromFile(appData.loadGameFile,
1584                                     appData.loadGameIndex,
1585                                     appData.loadGameFile, TRUE);
1586         } else if (*appData.loadPositionFile != NULLCHAR) {
1587             (void) LoadPositionFromFile(appData.loadPositionFile,
1588                                         appData.loadPositionIndex,
1589                                         appData.loadPositionFile);
1590             /* [HGM] try to make self-starting even after FEN load */
1591             /* to allow automatic setup of fairy variants with wtm */
1592             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1593                 gameMode = BeginningOfGame;
1594                 setboardSpoiledMachineBlack = 1;
1595             }
1596             /* [HGM] loadPos: make that every new game uses the setup */
1597             /* from file as long as we do not switch variant          */
1598             if(!blackPlaysFirst) {
1599                 startedFromPositionFile = TRUE;
1600                 CopyBoard(filePosition, boards[0]);
1601             }
1602         }
1603         if (initialMode == AnalyzeMode) {
1604           if (appData.noChessProgram) {
1605             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1606             return;
1607           }
1608           if (appData.icsActive) {
1609             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1610             return;
1611           }
1612           AnalyzeModeEvent();
1613         } else if (initialMode == AnalyzeFile) {
1614           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1615           ShowThinkingEvent();
1616           AnalyzeFileEvent();
1617           AnalysisPeriodicEvent(1);
1618         } else if (initialMode == MachinePlaysWhite) {
1619           if (appData.noChessProgram) {
1620             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1621                               0, 2);
1622             return;
1623           }
1624           if (appData.icsActive) {
1625             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1626                               0, 2);
1627             return;
1628           }
1629           MachineWhiteEvent();
1630         } else if (initialMode == MachinePlaysBlack) {
1631           if (appData.noChessProgram) {
1632             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1633                               0, 2);
1634             return;
1635           }
1636           if (appData.icsActive) {
1637             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1638                               0, 2);
1639             return;
1640           }
1641           MachineBlackEvent();
1642         } else if (initialMode == TwoMachinesPlay) {
1643           if (appData.noChessProgram) {
1644             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1645                               0, 2);
1646             return;
1647           }
1648           if (appData.icsActive) {
1649             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1650                               0, 2);
1651             return;
1652           }
1653           TwoMachinesEvent();
1654         } else if (initialMode == EditGame) {
1655           EditGameEvent();
1656         } else if (initialMode == EditPosition) {
1657           EditPositionEvent();
1658         } else if (initialMode == Training) {
1659           if (*appData.loadGameFile == NULLCHAR) {
1660             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1661             return;
1662           }
1663           TrainingEvent();
1664         }
1665     }
1666 }
1667
1668 /*
1669  * Establish will establish a contact to a remote host.port.
1670  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1671  *  used to talk to the host.
1672  * Returns 0 if okay, error code if not.
1673  */
1674 int
1675 establish()
1676 {
1677     char buf[MSG_SIZ];
1678
1679     if (*appData.icsCommPort != NULLCHAR) {
1680         /* Talk to the host through a serial comm port */
1681         return OpenCommPort(appData.icsCommPort, &icsPR);
1682
1683     } else if (*appData.gateway != NULLCHAR) {
1684         if (*appData.remoteShell == NULLCHAR) {
1685             /* Use the rcmd protocol to run telnet program on a gateway host */
1686             snprintf(buf, sizeof(buf), "%s %s %s",
1687                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1688             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1689
1690         } else {
1691             /* Use the rsh program to run telnet program on a gateway host */
1692             if (*appData.remoteUser == NULLCHAR) {
1693                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1694                         appData.gateway, appData.telnetProgram,
1695                         appData.icsHost, appData.icsPort);
1696             } else {
1697                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1698                         appData.remoteShell, appData.gateway,
1699                         appData.remoteUser, appData.telnetProgram,
1700                         appData.icsHost, appData.icsPort);
1701             }
1702             return StartChildProcess(buf, "", &icsPR);
1703
1704         }
1705     } else if (appData.useTelnet) {
1706         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1707
1708     } else {
1709         /* TCP socket interface differs somewhat between
1710            Unix and NT; handle details in the front end.
1711            */
1712         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1713     }
1714 }
1715
1716 void EscapeExpand(char *p, char *q)
1717 {       // [HGM] initstring: routine to shape up string arguments
1718         while(*p++ = *q++) if(p[-1] == '\\')
1719             switch(*q++) {
1720                 case 'n': p[-1] = '\n'; break;
1721                 case 'r': p[-1] = '\r'; break;
1722                 case 't': p[-1] = '\t'; break;
1723                 case '\\': p[-1] = '\\'; break;
1724                 case 0: *p = 0; return;
1725                 default: p[-1] = q[-1]; break;
1726             }
1727 }
1728
1729 void
1730 show_bytes(fp, buf, count)
1731      FILE *fp;
1732      char *buf;
1733      int count;
1734 {
1735     while (count--) {
1736         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1737             fprintf(fp, "\\%03o", *buf & 0xff);
1738         } else {
1739             putc(*buf, fp);
1740         }
1741         buf++;
1742     }
1743     fflush(fp);
1744 }
1745
1746 /* Returns an errno value */
1747 int
1748 OutputMaybeTelnet(pr, message, count, outError)
1749      ProcRef pr;
1750      char *message;
1751      int count;
1752      int *outError;
1753 {
1754     char buf[8192], *p, *q, *buflim;
1755     int left, newcount, outcount;
1756
1757     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1758         *appData.gateway != NULLCHAR) {
1759         if (appData.debugMode) {
1760             fprintf(debugFP, ">ICS: ");
1761             show_bytes(debugFP, message, count);
1762             fprintf(debugFP, "\n");
1763         }
1764         return OutputToProcess(pr, message, count, outError);
1765     }
1766
1767     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1768     p = message;
1769     q = buf;
1770     left = count;
1771     newcount = 0;
1772     while (left) {
1773         if (q >= buflim) {
1774             if (appData.debugMode) {
1775                 fprintf(debugFP, ">ICS: ");
1776                 show_bytes(debugFP, buf, newcount);
1777                 fprintf(debugFP, "\n");
1778             }
1779             outcount = OutputToProcess(pr, buf, newcount, outError);
1780             if (outcount < newcount) return -1; /* to be sure */
1781             q = buf;
1782             newcount = 0;
1783         }
1784         if (*p == '\n') {
1785             *q++ = '\r';
1786             newcount++;
1787         } else if (((unsigned char) *p) == TN_IAC) {
1788             *q++ = (char) TN_IAC;
1789             newcount ++;
1790         }
1791         *q++ = *p++;
1792         newcount++;
1793         left--;
1794     }
1795     if (appData.debugMode) {
1796         fprintf(debugFP, ">ICS: ");
1797         show_bytes(debugFP, buf, newcount);
1798         fprintf(debugFP, "\n");
1799     }
1800     outcount = OutputToProcess(pr, buf, newcount, outError);
1801     if (outcount < newcount) return -1; /* to be sure */
1802     return count;
1803 }
1804
1805 void
1806 read_from_player(isr, closure, message, count, error)
1807      InputSourceRef isr;
1808      VOIDSTAR closure;
1809      char *message;
1810      int count;
1811      int error;
1812 {
1813     int outError, outCount;
1814     static int gotEof = 0;
1815
1816     /* Pass data read from player on to ICS */
1817     if (count > 0) {
1818         gotEof = 0;
1819         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1820         if (outCount < count) {
1821             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1822         }
1823     } else if (count < 0) {
1824         RemoveInputSource(isr);
1825         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1826     } else if (gotEof++ > 0) {
1827         RemoveInputSource(isr);
1828         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1829     }
1830 }
1831
1832 void
1833 KeepAlive()
1834 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1835     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1836     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1837     SendToICS("date\n");
1838     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1839 }
1840
1841 /* added routine for printf style output to ics */
1842 void ics_printf(char *format, ...)
1843 {
1844     char buffer[MSG_SIZ];
1845     va_list args;
1846
1847     va_start(args, format);
1848     vsnprintf(buffer, sizeof(buffer), format, args);
1849     buffer[sizeof(buffer)-1] = '\0';
1850     SendToICS(buffer);
1851     va_end(args);
1852 }
1853
1854 void
1855 SendToICS(s)
1856      char *s;
1857 {
1858     int count, outCount, outError;
1859
1860     if (icsPR == NULL) return;
1861
1862     count = strlen(s);
1863     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1864     if (outCount < count) {
1865         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1866     }
1867 }
1868
1869 /* This is used for sending logon scripts to the ICS. Sending
1870    without a delay causes problems when using timestamp on ICC
1871    (at least on my machine). */
1872 void
1873 SendToICSDelayed(s,msdelay)
1874      char *s;
1875      long msdelay;
1876 {
1877     int count, outCount, outError;
1878
1879     if (icsPR == NULL) return;
1880
1881     count = strlen(s);
1882     if (appData.debugMode) {
1883         fprintf(debugFP, ">ICS: ");
1884         show_bytes(debugFP, s, count);
1885         fprintf(debugFP, "\n");
1886     }
1887     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1888                                       msdelay);
1889     if (outCount < count) {
1890         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1891     }
1892 }
1893
1894
1895 /* Remove all highlighting escape sequences in s
1896    Also deletes any suffix starting with '('
1897    */
1898 char *
1899 StripHighlightAndTitle(s)
1900      char *s;
1901 {
1902     static char retbuf[MSG_SIZ];
1903     char *p = retbuf;
1904
1905     while (*s != NULLCHAR) {
1906         while (*s == '\033') {
1907             while (*s != NULLCHAR && !isalpha(*s)) s++;
1908             if (*s != NULLCHAR) s++;
1909         }
1910         while (*s != NULLCHAR && *s != '\033') {
1911             if (*s == '(' || *s == '[') {
1912                 *p = NULLCHAR;
1913                 return retbuf;
1914             }
1915             *p++ = *s++;
1916         }
1917     }
1918     *p = NULLCHAR;
1919     return retbuf;
1920 }
1921
1922 /* Remove all highlighting escape sequences in s */
1923 char *
1924 StripHighlight(s)
1925      char *s;
1926 {
1927     static char retbuf[MSG_SIZ];
1928     char *p = retbuf;
1929
1930     while (*s != NULLCHAR) {
1931         while (*s == '\033') {
1932             while (*s != NULLCHAR && !isalpha(*s)) s++;
1933             if (*s != NULLCHAR) s++;
1934         }
1935         while (*s != NULLCHAR && *s != '\033') {
1936             *p++ = *s++;
1937         }
1938     }
1939     *p = NULLCHAR;
1940     return retbuf;
1941 }
1942
1943 char *variantNames[] = VARIANT_NAMES;
1944 char *
1945 VariantName(v)
1946      VariantClass v;
1947 {
1948     return variantNames[v];
1949 }
1950
1951
1952 /* Identify a variant from the strings the chess servers use or the
1953    PGN Variant tag names we use. */
1954 VariantClass
1955 StringToVariant(e)
1956      char *e;
1957 {
1958     char *p;
1959     int wnum = -1;
1960     VariantClass v = VariantNormal;
1961     int i, found = FALSE;
1962     char buf[MSG_SIZ];
1963     int len;
1964
1965     if (!e) return v;
1966
1967     /* [HGM] skip over optional board-size prefixes */
1968     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1969         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1970         while( *e++ != '_');
1971     }
1972
1973     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1974         v = VariantNormal;
1975         found = TRUE;
1976     } else
1977     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1978       if (StrCaseStr(e, variantNames[i])) {
1979         v = (VariantClass) i;
1980         found = TRUE;
1981         break;
1982       }
1983     }
1984
1985     if (!found) {
1986       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1987           || StrCaseStr(e, "wild/fr")
1988           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1989         v = VariantFischeRandom;
1990       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1991                  (i = 1, p = StrCaseStr(e, "w"))) {
1992         p += i;
1993         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1994         if (isdigit(*p)) {
1995           wnum = atoi(p);
1996         } else {
1997           wnum = -1;
1998         }
1999         switch (wnum) {
2000         case 0: /* FICS only, actually */
2001         case 1:
2002           /* Castling legal even if K starts on d-file */
2003           v = VariantWildCastle;
2004           break;
2005         case 2:
2006         case 3:
2007         case 4:
2008           /* Castling illegal even if K & R happen to start in
2009              normal positions. */
2010           v = VariantNoCastle;
2011           break;
2012         case 5:
2013         case 7:
2014         case 8:
2015         case 10:
2016         case 11:
2017         case 12:
2018         case 13:
2019         case 14:
2020         case 15:
2021         case 18:
2022         case 19:
2023           /* Castling legal iff K & R start in normal positions */
2024           v = VariantNormal;
2025           break;
2026         case 6:
2027         case 20:
2028         case 21:
2029           /* Special wilds for position setup; unclear what to do here */
2030           v = VariantLoadable;
2031           break;
2032         case 9:
2033           /* Bizarre ICC game */
2034           v = VariantTwoKings;
2035           break;
2036         case 16:
2037           v = VariantKriegspiel;
2038           break;
2039         case 17:
2040           v = VariantLosers;
2041           break;
2042         case 22:
2043           v = VariantFischeRandom;
2044           break;
2045         case 23:
2046           v = VariantCrazyhouse;
2047           break;
2048         case 24:
2049           v = VariantBughouse;
2050           break;
2051         case 25:
2052           v = Variant3Check;
2053           break;
2054         case 26:
2055           /* Not quite the same as FICS suicide! */
2056           v = VariantGiveaway;
2057           break;
2058         case 27:
2059           v = VariantAtomic;
2060           break;
2061         case 28:
2062           v = VariantShatranj;
2063           break;
2064
2065         /* Temporary names for future ICC types.  The name *will* change in
2066            the next xboard/WinBoard release after ICC defines it. */
2067         case 29:
2068           v = Variant29;
2069           break;
2070         case 30:
2071           v = Variant30;
2072           break;
2073         case 31:
2074           v = Variant31;
2075           break;
2076         case 32:
2077           v = Variant32;
2078           break;
2079         case 33:
2080           v = Variant33;
2081           break;
2082         case 34:
2083           v = Variant34;
2084           break;
2085         case 35:
2086           v = Variant35;
2087           break;
2088         case 36:
2089           v = Variant36;
2090           break;
2091         case 37:
2092           v = VariantShogi;
2093           break;
2094         case 38:
2095           v = VariantXiangqi;
2096           break;
2097         case 39:
2098           v = VariantCourier;
2099           break;
2100         case 40:
2101           v = VariantGothic;
2102           break;
2103         case 41:
2104           v = VariantCapablanca;
2105           break;
2106         case 42:
2107           v = VariantKnightmate;
2108           break;
2109         case 43:
2110           v = VariantFairy;
2111           break;
2112         case 44:
2113           v = VariantCylinder;
2114           break;
2115         case 45:
2116           v = VariantFalcon;
2117           break;
2118         case 46:
2119           v = VariantCapaRandom;
2120           break;
2121         case 47:
2122           v = VariantBerolina;
2123           break;
2124         case 48:
2125           v = VariantJanus;
2126           break;
2127         case 49:
2128           v = VariantSuper;
2129           break;
2130         case 50:
2131           v = VariantGreat;
2132           break;
2133         case -1:
2134           /* Found "wild" or "w" in the string but no number;
2135              must assume it's normal chess. */
2136           v = VariantNormal;
2137           break;
2138         default:
2139           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2140           if( (len > MSG_SIZ) && appData.debugMode )
2141             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2142
2143           DisplayError(buf, 0);
2144           v = VariantUnknown;
2145           break;
2146         }
2147       }
2148     }
2149     if (appData.debugMode) {
2150       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2151               e, wnum, VariantName(v));
2152     }
2153     return v;
2154 }
2155
2156 static int leftover_start = 0, leftover_len = 0;
2157 char star_match[STAR_MATCH_N][MSG_SIZ];
2158
2159 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2160    advance *index beyond it, and set leftover_start to the new value of
2161    *index; else return FALSE.  If pattern contains the character '*', it
2162    matches any sequence of characters not containing '\r', '\n', or the
2163    character following the '*' (if any), and the matched sequence(s) are
2164    copied into star_match.
2165    */
2166 int
2167 looking_at(buf, index, pattern)
2168      char *buf;
2169      int *index;
2170      char *pattern;
2171 {
2172     char *bufp = &buf[*index], *patternp = pattern;
2173     int star_count = 0;
2174     char *matchp = star_match[0];
2175
2176     for (;;) {
2177         if (*patternp == NULLCHAR) {
2178             *index = leftover_start = bufp - buf;
2179             *matchp = NULLCHAR;
2180             return TRUE;
2181         }
2182         if (*bufp == NULLCHAR) return FALSE;
2183         if (*patternp == '*') {
2184             if (*bufp == *(patternp + 1)) {
2185                 *matchp = NULLCHAR;
2186                 matchp = star_match[++star_count];
2187                 patternp += 2;
2188                 bufp++;
2189                 continue;
2190             } else if (*bufp == '\n' || *bufp == '\r') {
2191                 patternp++;
2192                 if (*patternp == NULLCHAR)
2193                   continue;
2194                 else
2195                   return FALSE;
2196             } else {
2197                 *matchp++ = *bufp++;
2198                 continue;
2199             }
2200         }
2201         if (*patternp != *bufp) return FALSE;
2202         patternp++;
2203         bufp++;
2204     }
2205 }
2206
2207 void
2208 SendToPlayer(data, length)
2209      char *data;
2210      int length;
2211 {
2212     int error, outCount;
2213     outCount = OutputToProcess(NoProc, data, length, &error);
2214     if (outCount < length) {
2215         DisplayFatalError(_("Error writing to display"), error, 1);
2216     }
2217 }
2218
2219 void
2220 PackHolding(packed, holding)
2221      char packed[];
2222      char *holding;
2223 {
2224     char *p = holding;
2225     char *q = packed;
2226     int runlength = 0;
2227     int curr = 9999;
2228     do {
2229         if (*p == curr) {
2230             runlength++;
2231         } else {
2232             switch (runlength) {
2233               case 0:
2234                 break;
2235               case 1:
2236                 *q++ = curr;
2237                 break;
2238               case 2:
2239                 *q++ = curr;
2240                 *q++ = curr;
2241                 break;
2242               default:
2243                 sprintf(q, "%d", runlength);
2244                 while (*q) q++;
2245                 *q++ = curr;
2246                 break;
2247             }
2248             runlength = 1;
2249             curr = *p;
2250         }
2251     } while (*p++);
2252     *q = NULLCHAR;
2253 }
2254
2255 /* Telnet protocol requests from the front end */
2256 void
2257 TelnetRequest(ddww, option)
2258      unsigned char ddww, option;
2259 {
2260     unsigned char msg[3];
2261     int outCount, outError;
2262
2263     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2264
2265     if (appData.debugMode) {
2266         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2267         switch (ddww) {
2268           case TN_DO:
2269             ddwwStr = "DO";
2270             break;
2271           case TN_DONT:
2272             ddwwStr = "DONT";
2273             break;
2274           case TN_WILL:
2275             ddwwStr = "WILL";
2276             break;
2277           case TN_WONT:
2278             ddwwStr = "WONT";
2279             break;
2280           default:
2281             ddwwStr = buf1;
2282             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2283             break;
2284         }
2285         switch (option) {
2286           case TN_ECHO:
2287             optionStr = "ECHO";
2288             break;
2289           default:
2290             optionStr = buf2;
2291             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2292             break;
2293         }
2294         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2295     }
2296     msg[0] = TN_IAC;
2297     msg[1] = ddww;
2298     msg[2] = option;
2299     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2300     if (outCount < 3) {
2301         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2302     }
2303 }
2304
2305 void
2306 DoEcho()
2307 {
2308     if (!appData.icsActive) return;
2309     TelnetRequest(TN_DO, TN_ECHO);
2310 }
2311
2312 void
2313 DontEcho()
2314 {
2315     if (!appData.icsActive) return;
2316     TelnetRequest(TN_DONT, TN_ECHO);
2317 }
2318
2319 void
2320 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2321 {
2322     /* put the holdings sent to us by the server on the board holdings area */
2323     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2324     char p;
2325     ChessSquare piece;
2326
2327     if(gameInfo.holdingsWidth < 2)  return;
2328     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2329         return; // prevent overwriting by pre-board holdings
2330
2331     if( (int)lowestPiece >= BlackPawn ) {
2332         holdingsColumn = 0;
2333         countsColumn = 1;
2334         holdingsStartRow = BOARD_HEIGHT-1;
2335         direction = -1;
2336     } else {
2337         holdingsColumn = BOARD_WIDTH-1;
2338         countsColumn = BOARD_WIDTH-2;
2339         holdingsStartRow = 0;
2340         direction = 1;
2341     }
2342
2343     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2344         board[i][holdingsColumn] = EmptySquare;
2345         board[i][countsColumn]   = (ChessSquare) 0;
2346     }
2347     while( (p=*holdings++) != NULLCHAR ) {
2348         piece = CharToPiece( ToUpper(p) );
2349         if(piece == EmptySquare) continue;
2350         /*j = (int) piece - (int) WhitePawn;*/
2351         j = PieceToNumber(piece);
2352         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2353         if(j < 0) continue;               /* should not happen */
2354         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2355         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2356         board[holdingsStartRow+j*direction][countsColumn]++;
2357     }
2358 }
2359
2360
2361 void
2362 VariantSwitch(Board board, VariantClass newVariant)
2363 {
2364    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2365    static Board oldBoard;
2366
2367    startedFromPositionFile = FALSE;
2368    if(gameInfo.variant == newVariant) return;
2369
2370    /* [HGM] This routine is called each time an assignment is made to
2371     * gameInfo.variant during a game, to make sure the board sizes
2372     * are set to match the new variant. If that means adding or deleting
2373     * holdings, we shift the playing board accordingly
2374     * This kludge is needed because in ICS observe mode, we get boards
2375     * of an ongoing game without knowing the variant, and learn about the
2376     * latter only later. This can be because of the move list we requested,
2377     * in which case the game history is refilled from the beginning anyway,
2378     * but also when receiving holdings of a crazyhouse game. In the latter
2379     * case we want to add those holdings to the already received position.
2380     */
2381
2382
2383    if (appData.debugMode) {
2384      fprintf(debugFP, "Switch board from %s to %s\n",
2385              VariantName(gameInfo.variant), VariantName(newVariant));
2386      setbuf(debugFP, NULL);
2387    }
2388    shuffleOpenings = 0;       /* [HGM] shuffle */
2389    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2390    switch(newVariant)
2391      {
2392      case VariantShogi:
2393        newWidth = 9;  newHeight = 9;
2394        gameInfo.holdingsSize = 7;
2395      case VariantBughouse:
2396      case VariantCrazyhouse:
2397        newHoldingsWidth = 2; break;
2398      case VariantGreat:
2399        newWidth = 10;
2400      case VariantSuper:
2401        newHoldingsWidth = 2;
2402        gameInfo.holdingsSize = 8;
2403        break;
2404      case VariantGothic:
2405      case VariantCapablanca:
2406      case VariantCapaRandom:
2407        newWidth = 10;
2408      default:
2409        newHoldingsWidth = gameInfo.holdingsSize = 0;
2410      };
2411
2412    if(newWidth  != gameInfo.boardWidth  ||
2413       newHeight != gameInfo.boardHeight ||
2414       newHoldingsWidth != gameInfo.holdingsWidth ) {
2415
2416      /* shift position to new playing area, if needed */
2417      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2418        for(i=0; i<BOARD_HEIGHT; i++)
2419          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2420            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2421              board[i][j];
2422        for(i=0; i<newHeight; i++) {
2423          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2424          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2425        }
2426      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2427        for(i=0; i<BOARD_HEIGHT; i++)
2428          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2429            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2430              board[i][j];
2431      }
2432      gameInfo.boardWidth  = newWidth;
2433      gameInfo.boardHeight = newHeight;
2434      gameInfo.holdingsWidth = newHoldingsWidth;
2435      gameInfo.variant = newVariant;
2436      InitDrawingSizes(-2, 0);
2437    } else gameInfo.variant = newVariant;
2438    CopyBoard(oldBoard, board);   // remember correctly formatted board
2439      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2440    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2441 }
2442
2443 static int loggedOn = FALSE;
2444
2445 /*-- Game start info cache: --*/
2446 int gs_gamenum;
2447 char gs_kind[MSG_SIZ];
2448 static char player1Name[128] = "";
2449 static char player2Name[128] = "";
2450 static char cont_seq[] = "\n\\   ";
2451 static int player1Rating = -1;
2452 static int player2Rating = -1;
2453 /*----------------------------*/
2454
2455 ColorClass curColor = ColorNormal;
2456 int suppressKibitz = 0;
2457
2458 // [HGM] seekgraph
2459 Boolean soughtPending = FALSE;
2460 Boolean seekGraphUp;
2461 #define MAX_SEEK_ADS 200
2462 #define SQUARE 0x80
2463 char *seekAdList[MAX_SEEK_ADS];
2464 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2465 float tcList[MAX_SEEK_ADS];
2466 char colorList[MAX_SEEK_ADS];
2467 int nrOfSeekAds = 0;
2468 int minRating = 1010, maxRating = 2800;
2469 int hMargin = 10, vMargin = 20, h, w;
2470 extern int squareSize, lineGap;
2471
2472 void
2473 PlotSeekAd(int i)
2474 {
2475         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2476         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2477         if(r < minRating+100 && r >=0 ) r = minRating+100;
2478         if(r > maxRating) r = maxRating;
2479         if(tc < 1.) tc = 1.;
2480         if(tc > 95.) tc = 95.;
2481         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2482         y = ((double)r - minRating)/(maxRating - minRating)
2483             * (h-vMargin-squareSize/8-1) + vMargin;
2484         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2485         if(strstr(seekAdList[i], " u ")) color = 1;
2486         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2487            !strstr(seekAdList[i], "bullet") &&
2488            !strstr(seekAdList[i], "blitz") &&
2489            !strstr(seekAdList[i], "standard") ) color = 2;
2490         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2491         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2492 }
2493
2494 void
2495 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2496 {
2497         char buf[MSG_SIZ], *ext = "";
2498         VariantClass v = StringToVariant(type);
2499         if(strstr(type, "wild")) {
2500             ext = type + 4; // append wild number
2501             if(v == VariantFischeRandom) type = "chess960"; else
2502             if(v == VariantLoadable) type = "setup"; else
2503             type = VariantName(v);
2504         }
2505         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2506         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2507             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2508             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2509             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2510             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2511             seekNrList[nrOfSeekAds] = nr;
2512             zList[nrOfSeekAds] = 0;
2513             seekAdList[nrOfSeekAds++] = StrSave(buf);
2514             if(plot) PlotSeekAd(nrOfSeekAds-1);
2515         }
2516 }
2517
2518 void
2519 EraseSeekDot(int i)
2520 {
2521     int x = xList[i], y = yList[i], d=squareSize/4, k;
2522     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2523     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2524     // now replot every dot that overlapped
2525     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2526         int xx = xList[k], yy = yList[k];
2527         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2528             DrawSeekDot(xx, yy, colorList[k]);
2529     }
2530 }
2531
2532 void
2533 RemoveSeekAd(int nr)
2534 {
2535         int i;
2536         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2537             EraseSeekDot(i);
2538             if(seekAdList[i]) free(seekAdList[i]);
2539             seekAdList[i] = seekAdList[--nrOfSeekAds];
2540             seekNrList[i] = seekNrList[nrOfSeekAds];
2541             ratingList[i] = ratingList[nrOfSeekAds];
2542             colorList[i]  = colorList[nrOfSeekAds];
2543             tcList[i] = tcList[nrOfSeekAds];
2544             xList[i]  = xList[nrOfSeekAds];
2545             yList[i]  = yList[nrOfSeekAds];
2546             zList[i]  = zList[nrOfSeekAds];
2547             seekAdList[nrOfSeekAds] = NULL;
2548             break;
2549         }
2550 }
2551
2552 Boolean
2553 MatchSoughtLine(char *line)
2554 {
2555     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2556     int nr, base, inc, u=0; char dummy;
2557
2558     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2559        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2560        (u=1) &&
2561        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2562         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2563         // match: compact and save the line
2564         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2565         return TRUE;
2566     }
2567     return FALSE;
2568 }
2569
2570 int
2571 DrawSeekGraph()
2572 {
2573     int i;
2574     if(!seekGraphUp) return FALSE;
2575     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2576     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2577
2578     DrawSeekBackground(0, 0, w, h);
2579     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2580     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2581     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2582         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2583         yy = h-1-yy;
2584         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2585         if(i%500 == 0) {
2586             char buf[MSG_SIZ];
2587             snprintf(buf, MSG_SIZ, "%d", i);
2588             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2589         }
2590     }
2591     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2592     for(i=1; i<100; i+=(i<10?1:5)) {
2593         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2594         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2595         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2596             char buf[MSG_SIZ];
2597             snprintf(buf, MSG_SIZ, "%d", i);
2598             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2599         }
2600     }
2601     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2602     return TRUE;
2603 }
2604
2605 int SeekGraphClick(ClickType click, int x, int y, int moving)
2606 {
2607     static int lastDown = 0, displayed = 0, lastSecond;
2608     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2609         if(click == Release || moving) return FALSE;
2610         nrOfSeekAds = 0;
2611         soughtPending = TRUE;
2612         SendToICS(ics_prefix);
2613         SendToICS("sought\n"); // should this be "sought all"?
2614     } else { // issue challenge based on clicked ad
2615         int dist = 10000; int i, closest = 0, second = 0;
2616         for(i=0; i<nrOfSeekAds; i++) {
2617             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2618             if(d < dist) { dist = d; closest = i; }
2619             second += (d - zList[i] < 120); // count in-range ads
2620             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2621         }
2622         if(dist < 120) {
2623             char buf[MSG_SIZ];
2624             second = (second > 1);
2625             if(displayed != closest || second != lastSecond) {
2626                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2627                 lastSecond = second; displayed = closest;
2628             }
2629             if(click == Press) {
2630                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2631                 lastDown = closest;
2632                 return TRUE;
2633             } // on press 'hit', only show info
2634             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2635             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2636             SendToICS(ics_prefix);
2637             SendToICS(buf);
2638             return TRUE; // let incoming board of started game pop down the graph
2639         } else if(click == Release) { // release 'miss' is ignored
2640             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2641             if(moving == 2) { // right up-click
2642                 nrOfSeekAds = 0; // refresh graph
2643                 soughtPending = TRUE;
2644                 SendToICS(ics_prefix);
2645                 SendToICS("sought\n"); // should this be "sought all"?
2646             }
2647             return TRUE;
2648         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2649         // press miss or release hit 'pop down' seek graph
2650         seekGraphUp = FALSE;
2651         DrawPosition(TRUE, NULL);
2652     }
2653     return TRUE;
2654 }
2655
2656 void
2657 read_from_ics(isr, closure, data, count, error)
2658      InputSourceRef isr;
2659      VOIDSTAR closure;
2660      char *data;
2661      int count;
2662      int error;
2663 {
2664 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2665 #define STARTED_NONE 0
2666 #define STARTED_MOVES 1
2667 #define STARTED_BOARD 2
2668 #define STARTED_OBSERVE 3
2669 #define STARTED_HOLDINGS 4
2670 #define STARTED_CHATTER 5
2671 #define STARTED_COMMENT 6
2672 #define STARTED_MOVES_NOHIDE 7
2673
2674     static int started = STARTED_NONE;
2675     static char parse[20000];
2676     static int parse_pos = 0;
2677     static char buf[BUF_SIZE + 1];
2678     static int firstTime = TRUE, intfSet = FALSE;
2679     static ColorClass prevColor = ColorNormal;
2680     static int savingComment = FALSE;
2681     static int cmatch = 0; // continuation sequence match
2682     char *bp;
2683     char str[MSG_SIZ];
2684     int i, oldi;
2685     int buf_len;
2686     int next_out;
2687     int tkind;
2688     int backup;    /* [DM] For zippy color lines */
2689     char *p;
2690     char talker[MSG_SIZ]; // [HGM] chat
2691     int channel;
2692
2693     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2694
2695     if (appData.debugMode) {
2696       if (!error) {
2697         fprintf(debugFP, "<ICS: ");
2698         show_bytes(debugFP, data, count);
2699         fprintf(debugFP, "\n");
2700       }
2701     }
2702
2703     if (appData.debugMode) { int f = forwardMostMove;
2704         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2705                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2706                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2707     }
2708     if (count > 0) {
2709         /* If last read ended with a partial line that we couldn't parse,
2710            prepend it to the new read and try again. */
2711         if (leftover_len > 0) {
2712             for (i=0; i<leftover_len; i++)
2713               buf[i] = buf[leftover_start + i];
2714         }
2715
2716     /* copy new characters into the buffer */
2717     bp = buf + leftover_len;
2718     buf_len=leftover_len;
2719     for (i=0; i<count; i++)
2720     {
2721         // ignore these
2722         if (data[i] == '\r')
2723             continue;
2724
2725         // join lines split by ICS?
2726         if (!appData.noJoin)
2727         {
2728             /*
2729                 Joining just consists of finding matches against the
2730                 continuation sequence, and discarding that sequence
2731                 if found instead of copying it.  So, until a match
2732                 fails, there's nothing to do since it might be the
2733                 complete sequence, and thus, something we don't want
2734                 copied.
2735             */
2736             if (data[i] == cont_seq[cmatch])
2737             {
2738                 cmatch++;
2739                 if (cmatch == strlen(cont_seq))
2740                 {
2741                     cmatch = 0; // complete match.  just reset the counter
2742
2743                     /*
2744                         it's possible for the ICS to not include the space
2745                         at the end of the last word, making our [correct]
2746                         join operation fuse two separate words.  the server
2747                         does this when the space occurs at the width setting.
2748                     */
2749                     if (!buf_len || buf[buf_len-1] != ' ')
2750                     {
2751                         *bp++ = ' ';
2752                         buf_len++;
2753                     }
2754                 }
2755                 continue;
2756             }
2757             else if (cmatch)
2758             {
2759                 /*
2760                     match failed, so we have to copy what matched before
2761                     falling through and copying this character.  In reality,
2762                     this will only ever be just the newline character, but
2763                     it doesn't hurt to be precise.
2764                 */
2765                 strncpy(bp, cont_seq, cmatch);
2766                 bp += cmatch;
2767                 buf_len += cmatch;
2768                 cmatch = 0;
2769             }
2770         }
2771
2772         // copy this char
2773         *bp++ = data[i];
2774         buf_len++;
2775     }
2776
2777         buf[buf_len] = NULLCHAR;
2778 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2779         next_out = 0;
2780         leftover_start = 0;
2781
2782         i = 0;
2783         while (i < buf_len) {
2784             /* Deal with part of the TELNET option negotiation
2785                protocol.  We refuse to do anything beyond the
2786                defaults, except that we allow the WILL ECHO option,
2787                which ICS uses to turn off password echoing when we are
2788                directly connected to it.  We reject this option
2789                if localLineEditing mode is on (always on in xboard)
2790                and we are talking to port 23, which might be a real
2791                telnet server that will try to keep WILL ECHO on permanently.
2792              */
2793             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2794                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2795                 unsigned char option;
2796                 oldi = i;
2797                 switch ((unsigned char) buf[++i]) {
2798                   case TN_WILL:
2799                     if (appData.debugMode)
2800                       fprintf(debugFP, "\n<WILL ");
2801                     switch (option = (unsigned char) buf[++i]) {
2802                       case TN_ECHO:
2803                         if (appData.debugMode)
2804                           fprintf(debugFP, "ECHO ");
2805                         /* Reply only if this is a change, according
2806                            to the protocol rules. */
2807                         if (remoteEchoOption) break;
2808                         if (appData.localLineEditing &&
2809                             atoi(appData.icsPort) == TN_PORT) {
2810                             TelnetRequest(TN_DONT, TN_ECHO);
2811                         } else {
2812                             EchoOff();
2813                             TelnetRequest(TN_DO, TN_ECHO);
2814                             remoteEchoOption = TRUE;
2815                         }
2816                         break;
2817                       default:
2818                         if (appData.debugMode)
2819                           fprintf(debugFP, "%d ", option);
2820                         /* Whatever this is, we don't want it. */
2821                         TelnetRequest(TN_DONT, option);
2822                         break;
2823                     }
2824                     break;
2825                   case TN_WONT:
2826                     if (appData.debugMode)
2827                       fprintf(debugFP, "\n<WONT ");
2828                     switch (option = (unsigned char) buf[++i]) {
2829                       case TN_ECHO:
2830                         if (appData.debugMode)
2831                           fprintf(debugFP, "ECHO ");
2832                         /* Reply only if this is a change, according
2833                            to the protocol rules. */
2834                         if (!remoteEchoOption) break;
2835                         EchoOn();
2836                         TelnetRequest(TN_DONT, TN_ECHO);
2837                         remoteEchoOption = FALSE;
2838                         break;
2839                       default:
2840                         if (appData.debugMode)
2841                           fprintf(debugFP, "%d ", (unsigned char) option);
2842                         /* Whatever this is, it must already be turned
2843                            off, because we never agree to turn on
2844                            anything non-default, so according to the
2845                            protocol rules, we don't reply. */
2846                         break;
2847                     }
2848                     break;
2849                   case TN_DO:
2850                     if (appData.debugMode)
2851                       fprintf(debugFP, "\n<DO ");
2852                     switch (option = (unsigned char) buf[++i]) {
2853                       default:
2854                         /* Whatever this is, we refuse to do it. */
2855                         if (appData.debugMode)
2856                           fprintf(debugFP, "%d ", option);
2857                         TelnetRequest(TN_WONT, option);
2858                         break;
2859                     }
2860                     break;
2861                   case TN_DONT:
2862                     if (appData.debugMode)
2863                       fprintf(debugFP, "\n<DONT ");
2864                     switch (option = (unsigned char) buf[++i]) {
2865                       default:
2866                         if (appData.debugMode)
2867                           fprintf(debugFP, "%d ", option);
2868                         /* Whatever this is, we are already not doing
2869                            it, because we never agree to do anything
2870                            non-default, so according to the protocol
2871                            rules, we don't reply. */
2872                         break;
2873                     }
2874                     break;
2875                   case TN_IAC:
2876                     if (appData.debugMode)
2877                       fprintf(debugFP, "\n<IAC ");
2878                     /* Doubled IAC; pass it through */
2879                     i--;
2880                     break;
2881                   default:
2882                     if (appData.debugMode)
2883                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2884                     /* Drop all other telnet commands on the floor */
2885                     break;
2886                 }
2887                 if (oldi > next_out)
2888                   SendToPlayer(&buf[next_out], oldi - next_out);
2889                 if (++i > next_out)
2890                   next_out = i;
2891                 continue;
2892             }
2893
2894             /* OK, this at least will *usually* work */
2895             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2896                 loggedOn = TRUE;
2897             }
2898
2899             if (loggedOn && !intfSet) {
2900                 if (ics_type == ICS_ICC) {
2901                   snprintf(str, MSG_SIZ,
2902                           "/set-quietly interface %s\n/set-quietly style 12\n",
2903                           programVersion);
2904                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2905                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2906                 } else if (ics_type == ICS_CHESSNET) {
2907                   snprintf(str, MSG_SIZ, "/style 12\n");
2908                 } else {
2909                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2910                   strcat(str, programVersion);
2911                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2912                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2913                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2914 #ifdef WIN32
2915                   strcat(str, "$iset nohighlight 1\n");
2916 #endif
2917                   strcat(str, "$iset lock 1\n$style 12\n");
2918                 }
2919                 SendToICS(str);
2920                 NotifyFrontendLogin();
2921                 intfSet = TRUE;
2922             }
2923
2924             if (started == STARTED_COMMENT) {
2925                 /* Accumulate characters in comment */
2926                 parse[parse_pos++] = buf[i];
2927                 if (buf[i] == '\n') {
2928                     parse[parse_pos] = NULLCHAR;
2929                     if(chattingPartner>=0) {
2930                         char mess[MSG_SIZ];
2931                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2932                         OutputChatMessage(chattingPartner, mess);
2933                         chattingPartner = -1;
2934                         next_out = i+1; // [HGM] suppress printing in ICS window
2935                     } else
2936                     if(!suppressKibitz) // [HGM] kibitz
2937                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2938                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2939                         int nrDigit = 0, nrAlph = 0, j;
2940                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2941                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2942                         parse[parse_pos] = NULLCHAR;
2943                         // try to be smart: if it does not look like search info, it should go to
2944                         // ICS interaction window after all, not to engine-output window.
2945                         for(j=0; j<parse_pos; j++) { // count letters and digits
2946                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2947                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2948                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2949                         }
2950                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2951                             int depth=0; float score;
2952                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2953                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2954                                 pvInfoList[forwardMostMove-1].depth = depth;
2955                                 pvInfoList[forwardMostMove-1].score = 100*score;
2956                             }
2957                             OutputKibitz(suppressKibitz, parse);
2958                         } else {
2959                             char tmp[MSG_SIZ];
2960                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2961                             SendToPlayer(tmp, strlen(tmp));
2962                         }
2963                         next_out = i+1; // [HGM] suppress printing in ICS window
2964                     }
2965                     started = STARTED_NONE;
2966                 } else {
2967                     /* Don't match patterns against characters in comment */
2968                     i++;
2969                     continue;
2970                 }
2971             }
2972             if (started == STARTED_CHATTER) {
2973                 if (buf[i] != '\n') {
2974                     /* Don't match patterns against characters in chatter */
2975                     i++;
2976                     continue;
2977                 }
2978                 started = STARTED_NONE;
2979                 if(suppressKibitz) next_out = i+1;
2980             }
2981
2982             /* Kludge to deal with rcmd protocol */
2983             if (firstTime && looking_at(buf, &i, "\001*")) {
2984                 DisplayFatalError(&buf[1], 0, 1);
2985                 continue;
2986             } else {
2987                 firstTime = FALSE;
2988             }
2989
2990             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2991                 ics_type = ICS_ICC;
2992                 ics_prefix = "/";
2993                 if (appData.debugMode)
2994                   fprintf(debugFP, "ics_type %d\n", ics_type);
2995                 continue;
2996             }
2997             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2998                 ics_type = ICS_FICS;
2999                 ics_prefix = "$";
3000                 if (appData.debugMode)
3001                   fprintf(debugFP, "ics_type %d\n", ics_type);
3002                 continue;
3003             }
3004             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3005                 ics_type = ICS_CHESSNET;
3006                 ics_prefix = "/";
3007                 if (appData.debugMode)
3008                   fprintf(debugFP, "ics_type %d\n", ics_type);
3009                 continue;
3010             }
3011
3012             if (!loggedOn &&
3013                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3014                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3015                  looking_at(buf, &i, "will be \"*\""))) {
3016               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3017               continue;
3018             }
3019
3020             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3021               char buf[MSG_SIZ];
3022               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3023               DisplayIcsInteractionTitle(buf);
3024               have_set_title = TRUE;
3025             }
3026
3027             /* skip finger notes */
3028             if (started == STARTED_NONE &&
3029                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3030                  (buf[i] == '1' && buf[i+1] == '0')) &&
3031                 buf[i+2] == ':' && buf[i+3] == ' ') {
3032               started = STARTED_CHATTER;
3033               i += 3;
3034               continue;
3035             }
3036
3037             oldi = i;
3038             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3039             if(appData.seekGraph) {
3040                 if(soughtPending && MatchSoughtLine(buf+i)) {
3041                     i = strstr(buf+i, "rated") - buf;
3042                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3043                     next_out = leftover_start = i;
3044                     started = STARTED_CHATTER;
3045                     suppressKibitz = TRUE;
3046                     continue;
3047                 }
3048                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3049                         && looking_at(buf, &i, "* ads displayed")) {
3050                     soughtPending = FALSE;
3051                     seekGraphUp = TRUE;
3052                     DrawSeekGraph();
3053                     continue;
3054                 }
3055                 if(appData.autoRefresh) {
3056                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3057                         int s = (ics_type == ICS_ICC); // ICC format differs
3058                         if(seekGraphUp)
3059                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3060                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3061                         looking_at(buf, &i, "*% "); // eat prompt
3062                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3063                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3064                         next_out = i; // suppress
3065                         continue;
3066                     }
3067                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3068                         char *p = star_match[0];
3069                         while(*p) {
3070                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3071                             while(*p && *p++ != ' '); // next
3072                         }
3073                         looking_at(buf, &i, "*% "); // eat prompt
3074                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3075                         next_out = i;
3076                         continue;
3077                     }
3078                 }
3079             }
3080
3081             /* skip formula vars */
3082             if (started == STARTED_NONE &&
3083                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3084               started = STARTED_CHATTER;
3085               i += 3;
3086               continue;
3087             }
3088
3089             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3090             if (appData.autoKibitz && started == STARTED_NONE &&
3091                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3092                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3093                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3094                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3095                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3096                         suppressKibitz = TRUE;
3097                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3098                         next_out = i;
3099                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3100                                 && (gameMode == IcsPlayingWhite)) ||
3101                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3102                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3103                             started = STARTED_CHATTER; // own kibitz we simply discard
3104                         else {
3105                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3106                             parse_pos = 0; parse[0] = NULLCHAR;
3107                             savingComment = TRUE;
3108                             suppressKibitz = gameMode != IcsObserving ? 2 :
3109                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3110                         }
3111                         continue;
3112                 } else
3113                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3114                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3115                          && atoi(star_match[0])) {
3116                     // suppress the acknowledgements of our own autoKibitz
3117                     char *p;
3118                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3119                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3120                     SendToPlayer(star_match[0], strlen(star_match[0]));
3121                     if(looking_at(buf, &i, "*% ")) // eat prompt
3122                         suppressKibitz = FALSE;
3123                     next_out = i;
3124                     continue;
3125                 }
3126             } // [HGM] kibitz: end of patch
3127
3128             // [HGM] chat: intercept tells by users for which we have an open chat window
3129             channel = -1;
3130             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3131                                            looking_at(buf, &i, "* whispers:") ||
3132                                            looking_at(buf, &i, "* kibitzes:") ||
3133                                            looking_at(buf, &i, "* shouts:") ||
3134                                            looking_at(buf, &i, "* c-shouts:") ||
3135                                            looking_at(buf, &i, "--> * ") ||
3136                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3137                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3138                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3139                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3140                 int p;
3141                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3142                 chattingPartner = -1;
3143
3144                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3145                 for(p=0; p<MAX_CHAT; p++) {
3146                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3147                     talker[0] = '['; strcat(talker, "] ");
3148                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3149                     chattingPartner = p; break;
3150                     }
3151                 } else
3152                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3153                 for(p=0; p<MAX_CHAT; p++) {
3154                     if(!strcmp("kibitzes", chatPartner[p])) {
3155                         talker[0] = '['; strcat(talker, "] ");
3156                         chattingPartner = p; break;
3157                     }
3158                 } else
3159                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3160                 for(p=0; p<MAX_CHAT; p++) {
3161                     if(!strcmp("whispers", chatPartner[p])) {
3162                         talker[0] = '['; strcat(talker, "] ");
3163                         chattingPartner = p; break;
3164                     }
3165                 } else
3166                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3167                   if(buf[i-8] == '-' && buf[i-3] == 't')
3168                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3169                     if(!strcmp("c-shouts", chatPartner[p])) {
3170                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3171                         chattingPartner = p; break;
3172                     }
3173                   }
3174                   if(chattingPartner < 0)
3175                   for(p=0; p<MAX_CHAT; p++) {
3176                     if(!strcmp("shouts", chatPartner[p])) {
3177                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3178                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3179                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3180                         chattingPartner = p; break;
3181                     }
3182                   }
3183                 }
3184                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3185                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3186                     talker[0] = 0; Colorize(ColorTell, FALSE);
3187                     chattingPartner = p; break;
3188                 }
3189                 if(chattingPartner<0) i = oldi; else {
3190                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3191                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3192                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3193                     started = STARTED_COMMENT;
3194                     parse_pos = 0; parse[0] = NULLCHAR;
3195                     savingComment = 3 + chattingPartner; // counts as TRUE
3196                     suppressKibitz = TRUE;
3197                     continue;
3198                 }
3199             } // [HGM] chat: end of patch
3200
3201           backup = i;
3202             if (appData.zippyTalk || appData.zippyPlay) {
3203                 /* [DM] Backup address for color zippy lines */
3204 #if ZIPPY
3205                if (loggedOn == TRUE)
3206                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3207                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3208 #endif
3209             } // [DM] 'else { ' deleted
3210                 if (
3211                     /* Regular tells and says */
3212                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3213                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3214                     looking_at(buf, &i, "* says: ") ||
3215                     /* Don't color "message" or "messages" output */
3216                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3217                     looking_at(buf, &i, "*. * at *:*: ") ||
3218                     looking_at(buf, &i, "--* (*:*): ") ||
3219                     /* Message notifications (same color as tells) */
3220                     looking_at(buf, &i, "* has left a message ") ||
3221                     looking_at(buf, &i, "* just sent you a message:\n") ||
3222                     /* Whispers and kibitzes */
3223                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3224                     looking_at(buf, &i, "* kibitzes: ") ||
3225                     /* Channel tells */
3226                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3227
3228                   if (tkind == 1 && strchr(star_match[0], ':')) {
3229                       /* Avoid "tells you:" spoofs in channels */
3230                      tkind = 3;
3231                   }
3232                   if (star_match[0][0] == NULLCHAR ||
3233                       strchr(star_match[0], ' ') ||
3234                       (tkind == 3 && strchr(star_match[1], ' '))) {
3235                     /* Reject bogus matches */
3236                     i = oldi;
3237                   } else {
3238                     if (appData.colorize) {
3239                       if (oldi > next_out) {
3240                         SendToPlayer(&buf[next_out], oldi - next_out);
3241                         next_out = oldi;
3242                       }
3243                       switch (tkind) {
3244                       case 1:
3245                         Colorize(ColorTell, FALSE);
3246                         curColor = ColorTell;
3247                         break;
3248                       case 2:
3249                         Colorize(ColorKibitz, FALSE);
3250                         curColor = ColorKibitz;
3251                         break;
3252                       case 3:
3253                         p = strrchr(star_match[1], '(');
3254                         if (p == NULL) {
3255                           p = star_match[1];
3256                         } else {
3257                           p++;
3258                         }
3259                         if (atoi(p) == 1) {
3260                           Colorize(ColorChannel1, FALSE);
3261                           curColor = ColorChannel1;
3262                         } else {
3263                           Colorize(ColorChannel, FALSE);
3264                           curColor = ColorChannel;
3265                         }
3266                         break;
3267                       case 5:
3268                         curColor = ColorNormal;
3269                         break;
3270                       }
3271                     }
3272                     if (started == STARTED_NONE && appData.autoComment &&
3273                         (gameMode == IcsObserving ||
3274                          gameMode == IcsPlayingWhite ||
3275                          gameMode == IcsPlayingBlack)) {
3276                       parse_pos = i - oldi;
3277                       memcpy(parse, &buf[oldi], parse_pos);
3278                       parse[parse_pos] = NULLCHAR;
3279                       started = STARTED_COMMENT;
3280                       savingComment = TRUE;
3281                     } else {
3282                       started = STARTED_CHATTER;
3283                       savingComment = FALSE;
3284                     }
3285                     loggedOn = TRUE;
3286                     continue;
3287                   }
3288                 }
3289
3290                 if (looking_at(buf, &i, "* s-shouts: ") ||
3291                     looking_at(buf, &i, "* c-shouts: ")) {
3292                     if (appData.colorize) {
3293                         if (oldi > next_out) {
3294                             SendToPlayer(&buf[next_out], oldi - next_out);
3295                             next_out = oldi;
3296                         }
3297                         Colorize(ColorSShout, FALSE);
3298                         curColor = ColorSShout;
3299                     }
3300                     loggedOn = TRUE;
3301                     started = STARTED_CHATTER;
3302                     continue;
3303                 }
3304
3305                 if (looking_at(buf, &i, "--->")) {
3306                     loggedOn = TRUE;
3307                     continue;
3308                 }
3309
3310                 if (looking_at(buf, &i, "* shouts: ") ||
3311                     looking_at(buf, &i, "--> ")) {
3312                     if (appData.colorize) {
3313                         if (oldi > next_out) {
3314                             SendToPlayer(&buf[next_out], oldi - next_out);
3315                             next_out = oldi;
3316                         }
3317                         Colorize(ColorShout, FALSE);
3318                         curColor = ColorShout;
3319                     }
3320                     loggedOn = TRUE;
3321                     started = STARTED_CHATTER;
3322                     continue;
3323                 }
3324
3325                 if (looking_at( buf, &i, "Challenge:")) {
3326                     if (appData.colorize) {
3327                         if (oldi > next_out) {
3328                             SendToPlayer(&buf[next_out], oldi - next_out);
3329                             next_out = oldi;
3330                         }
3331                         Colorize(ColorChallenge, FALSE);
3332                         curColor = ColorChallenge;
3333                     }
3334                     loggedOn = TRUE;
3335                     continue;
3336                 }
3337
3338                 if (looking_at(buf, &i, "* offers you") ||
3339                     looking_at(buf, &i, "* offers to be") ||
3340                     looking_at(buf, &i, "* would like to") ||
3341                     looking_at(buf, &i, "* requests to") ||
3342                     looking_at(buf, &i, "Your opponent offers") ||
3343                     looking_at(buf, &i, "Your opponent requests")) {
3344
3345                     if (appData.colorize) {
3346                         if (oldi > next_out) {
3347                             SendToPlayer(&buf[next_out], oldi - next_out);
3348                             next_out = oldi;
3349                         }
3350                         Colorize(ColorRequest, FALSE);
3351                         curColor = ColorRequest;
3352                     }
3353                     continue;
3354                 }
3355
3356                 if (looking_at(buf, &i, "* (*) seeking")) {
3357                     if (appData.colorize) {
3358                         if (oldi > next_out) {
3359                             SendToPlayer(&buf[next_out], oldi - next_out);
3360                             next_out = oldi;
3361                         }
3362                         Colorize(ColorSeek, FALSE);
3363                         curColor = ColorSeek;
3364                     }
3365                     continue;
3366             }
3367
3368           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3369
3370             if (looking_at(buf, &i, "\\   ")) {
3371                 if (prevColor != ColorNormal) {
3372                     if (oldi > next_out) {
3373                         SendToPlayer(&buf[next_out], oldi - next_out);
3374                         next_out = oldi;
3375                     }
3376                     Colorize(prevColor, TRUE);
3377                     curColor = prevColor;
3378                 }
3379                 if (savingComment) {
3380                     parse_pos = i - oldi;
3381                     memcpy(parse, &buf[oldi], parse_pos);
3382                     parse[parse_pos] = NULLCHAR;
3383                     started = STARTED_COMMENT;
3384                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3385                         chattingPartner = savingComment - 3; // kludge to remember the box
3386                 } else {
3387                     started = STARTED_CHATTER;
3388                 }
3389                 continue;
3390             }
3391
3392             if (looking_at(buf, &i, "Black Strength :") ||
3393                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3394                 looking_at(buf, &i, "<10>") ||
3395                 looking_at(buf, &i, "#@#")) {
3396                 /* Wrong board style */
3397                 loggedOn = TRUE;
3398                 SendToICS(ics_prefix);
3399                 SendToICS("set style 12\n");
3400                 SendToICS(ics_prefix);
3401                 SendToICS("refresh\n");
3402                 continue;
3403             }
3404
3405             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3406                 ICSInitScript();
3407                 have_sent_ICS_logon = 1;
3408                 continue;
3409             }
3410
3411             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3412                 (looking_at(buf, &i, "\n<12> ") ||
3413                  looking_at(buf, &i, "<12> "))) {
3414                 loggedOn = TRUE;
3415                 if (oldi > next_out) {
3416                     SendToPlayer(&buf[next_out], oldi - next_out);
3417                 }
3418                 next_out = i;
3419                 started = STARTED_BOARD;
3420                 parse_pos = 0;
3421                 continue;
3422             }
3423
3424             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3425                 looking_at(buf, &i, "<b1> ")) {
3426                 if (oldi > next_out) {
3427                     SendToPlayer(&buf[next_out], oldi - next_out);
3428                 }
3429                 next_out = i;
3430                 started = STARTED_HOLDINGS;
3431                 parse_pos = 0;
3432                 continue;
3433             }
3434
3435             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3436                 loggedOn = TRUE;
3437                 /* Header for a move list -- first line */
3438
3439                 switch (ics_getting_history) {
3440                   case H_FALSE:
3441                     switch (gameMode) {
3442                       case IcsIdle:
3443                       case BeginningOfGame:
3444                         /* User typed "moves" or "oldmoves" while we
3445                            were idle.  Pretend we asked for these
3446                            moves and soak them up so user can step
3447                            through them and/or save them.
3448                            */
3449                         Reset(FALSE, TRUE);
3450                         gameMode = IcsObserving;
3451                         ModeHighlight();
3452                         ics_gamenum = -1;
3453                         ics_getting_history = H_GOT_UNREQ_HEADER;
3454                         break;
3455                       case EditGame: /*?*/
3456                       case EditPosition: /*?*/
3457                         /* Should above feature work in these modes too? */
3458                         /* For now it doesn't */
3459                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3460                         break;
3461                       default:
3462                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3463                         break;
3464                     }
3465                     break;
3466                   case H_REQUESTED:
3467                     /* Is this the right one? */
3468                     if (gameInfo.white && gameInfo.black &&
3469                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3470                         strcmp(gameInfo.black, star_match[2]) == 0) {
3471                         /* All is well */
3472                         ics_getting_history = H_GOT_REQ_HEADER;
3473                     }
3474                     break;
3475                   case H_GOT_REQ_HEADER:
3476                   case H_GOT_UNREQ_HEADER:
3477                   case H_GOT_UNWANTED_HEADER:
3478                   case H_GETTING_MOVES:
3479                     /* Should not happen */
3480                     DisplayError(_("Error gathering move list: two headers"), 0);
3481                     ics_getting_history = H_FALSE;
3482                     break;
3483                 }
3484
3485                 /* Save player ratings into gameInfo if needed */
3486                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3487                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3488                     (gameInfo.whiteRating == -1 ||
3489                      gameInfo.blackRating == -1)) {
3490
3491                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3492                     gameInfo.blackRating = string_to_rating(star_match[3]);
3493                     if (appData.debugMode)
3494                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3495                               gameInfo.whiteRating, gameInfo.blackRating);
3496                 }
3497                 continue;
3498             }
3499
3500             if (looking_at(buf, &i,
3501               "* * match, initial time: * minute*, increment: * second")) {
3502                 /* Header for a move list -- second line */
3503                 /* Initial board will follow if this is a wild game */
3504                 if (gameInfo.event != NULL) free(gameInfo.event);
3505                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3506                 gameInfo.event = StrSave(str);
3507                 /* [HGM] we switched variant. Translate boards if needed. */
3508                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3509                 continue;
3510             }
3511
3512             if (looking_at(buf, &i, "Move  ")) {
3513                 /* Beginning of a move list */
3514                 switch (ics_getting_history) {
3515                   case H_FALSE:
3516                     /* Normally should not happen */
3517                     /* Maybe user hit reset while we were parsing */
3518                     break;
3519                   case H_REQUESTED:
3520                     /* Happens if we are ignoring a move list that is not
3521                      * the one we just requested.  Common if the user
3522                      * tries to observe two games without turning off
3523                      * getMoveList */
3524                     break;
3525                   case H_GETTING_MOVES:
3526                     /* Should not happen */
3527                     DisplayError(_("Error gathering move list: nested"), 0);
3528                     ics_getting_history = H_FALSE;
3529                     break;
3530                   case H_GOT_REQ_HEADER:
3531                     ics_getting_history = H_GETTING_MOVES;
3532                     started = STARTED_MOVES;
3533                     parse_pos = 0;
3534                     if (oldi > next_out) {
3535                         SendToPlayer(&buf[next_out], oldi - next_out);
3536                     }
3537                     break;
3538                   case H_GOT_UNREQ_HEADER:
3539                     ics_getting_history = H_GETTING_MOVES;
3540                     started = STARTED_MOVES_NOHIDE;
3541                     parse_pos = 0;
3542                     break;
3543                   case H_GOT_UNWANTED_HEADER:
3544                     ics_getting_history = H_FALSE;
3545                     break;
3546                 }
3547                 continue;
3548             }
3549
3550             if (looking_at(buf, &i, "% ") ||
3551                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3552                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3553                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3554                     soughtPending = FALSE;
3555                     seekGraphUp = TRUE;
3556                     DrawSeekGraph();
3557                 }
3558                 if(suppressKibitz) next_out = i;
3559                 savingComment = FALSE;
3560                 suppressKibitz = 0;
3561                 switch (started) {
3562                   case STARTED_MOVES:
3563                   case STARTED_MOVES_NOHIDE:
3564                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3565                     parse[parse_pos + i - oldi] = NULLCHAR;
3566                     ParseGameHistory(parse);
3567 #if ZIPPY
3568                     if (appData.zippyPlay && first.initDone) {
3569                         FeedMovesToProgram(&first, forwardMostMove);
3570                         if (gameMode == IcsPlayingWhite) {
3571                             if (WhiteOnMove(forwardMostMove)) {
3572                                 if (first.sendTime) {
3573                                   if (first.useColors) {
3574                                     SendToProgram("black\n", &first);
3575                                   }
3576                                   SendTimeRemaining(&first, TRUE);
3577                                 }
3578                                 if (first.useColors) {
3579                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3580                                 }
3581                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3582                                 first.maybeThinking = TRUE;
3583                             } else {
3584                                 if (first.usePlayother) {
3585                                   if (first.sendTime) {
3586                                     SendTimeRemaining(&first, TRUE);
3587                                   }
3588                                   SendToProgram("playother\n", &first);
3589                                   firstMove = FALSE;
3590                                 } else {
3591                                   firstMove = TRUE;
3592                                 }
3593                             }
3594                         } else if (gameMode == IcsPlayingBlack) {
3595                             if (!WhiteOnMove(forwardMostMove)) {
3596                                 if (first.sendTime) {
3597                                   if (first.useColors) {
3598                                     SendToProgram("white\n", &first);
3599                                   }
3600                                   SendTimeRemaining(&first, FALSE);
3601                                 }
3602                                 if (first.useColors) {
3603                                   SendToProgram("black\n", &first);
3604                                 }
3605                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3606                                 first.maybeThinking = TRUE;
3607                             } else {
3608                                 if (first.usePlayother) {
3609                                   if (first.sendTime) {
3610                                     SendTimeRemaining(&first, FALSE);
3611                                   }
3612                                   SendToProgram("playother\n", &first);
3613                                   firstMove = FALSE;
3614                                 } else {
3615                                   firstMove = TRUE;
3616                                 }
3617                             }
3618                         }
3619                     }
3620 #endif
3621                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3622                         /* Moves came from oldmoves or moves command
3623                            while we weren't doing anything else.
3624                            */
3625                         currentMove = forwardMostMove;
3626                         ClearHighlights();/*!!could figure this out*/
3627                         flipView = appData.flipView;
3628                         DrawPosition(TRUE, boards[currentMove]);
3629                         DisplayBothClocks();
3630                         snprintf(str, MSG_SIZ, "%s vs. %s",
3631                                 gameInfo.white, gameInfo.black);
3632                         DisplayTitle(str);
3633                         gameMode = IcsIdle;
3634                     } else {
3635                         /* Moves were history of an active game */
3636                         if (gameInfo.resultDetails != NULL) {
3637                             free(gameInfo.resultDetails);
3638                             gameInfo.resultDetails = NULL;
3639                         }
3640                     }
3641                     HistorySet(parseList, backwardMostMove,
3642                                forwardMostMove, currentMove-1);
3643                     DisplayMove(currentMove - 1);
3644                     if (started == STARTED_MOVES) next_out = i;
3645                     started = STARTED_NONE;
3646                     ics_getting_history = H_FALSE;
3647                     break;
3648
3649                   case STARTED_OBSERVE:
3650                     started = STARTED_NONE;
3651                     SendToICS(ics_prefix);
3652                     SendToICS("refresh\n");
3653                     break;
3654
3655                   default:
3656                     break;
3657                 }
3658                 if(bookHit) { // [HGM] book: simulate book reply
3659                     static char bookMove[MSG_SIZ]; // a bit generous?
3660
3661                     programStats.nodes = programStats.depth = programStats.time =
3662                     programStats.score = programStats.got_only_move = 0;
3663                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3664
3665                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3666                     strcat(bookMove, bookHit);
3667                     HandleMachineMove(bookMove, &first);
3668                 }
3669                 continue;
3670             }
3671
3672             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3673                  started == STARTED_HOLDINGS ||
3674                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3675                 /* Accumulate characters in move list or board */
3676                 parse[parse_pos++] = buf[i];
3677             }
3678
3679             /* Start of game messages.  Mostly we detect start of game
3680                when the first board image arrives.  On some versions
3681                of the ICS, though, we need to do a "refresh" after starting
3682                to observe in order to get the current board right away. */
3683             if (looking_at(buf, &i, "Adding game * to observation list")) {
3684                 started = STARTED_OBSERVE;
3685                 continue;
3686             }
3687
3688             /* Handle auto-observe */
3689             if (appData.autoObserve &&
3690                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3691                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3692                 char *player;
3693                 /* Choose the player that was highlighted, if any. */
3694                 if (star_match[0][0] == '\033' ||
3695                     star_match[1][0] != '\033') {
3696                     player = star_match[0];
3697                 } else {
3698                     player = star_match[2];
3699                 }
3700                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3701                         ics_prefix, StripHighlightAndTitle(player));
3702                 SendToICS(str);
3703
3704                 /* Save ratings from notify string */
3705                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3706                 player1Rating = string_to_rating(star_match[1]);
3707                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3708                 player2Rating = string_to_rating(star_match[3]);
3709
3710                 if (appData.debugMode)
3711                   fprintf(debugFP,
3712                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3713                           player1Name, player1Rating,
3714                           player2Name, player2Rating);
3715
3716                 continue;
3717             }
3718
3719             /* Deal with automatic examine mode after a game,
3720                and with IcsObserving -> IcsExamining transition */
3721             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3722                 looking_at(buf, &i, "has made you an examiner of game *")) {
3723
3724                 int gamenum = atoi(star_match[0]);
3725                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3726                     gamenum == ics_gamenum) {
3727                     /* We were already playing or observing this game;
3728                        no need to refetch history */
3729                     gameMode = IcsExamining;
3730                     if (pausing) {
3731                         pauseExamForwardMostMove = forwardMostMove;
3732                     } else if (currentMove < forwardMostMove) {
3733                         ForwardInner(forwardMostMove);
3734                     }
3735                 } else {
3736                     /* I don't think this case really can happen */
3737                     SendToICS(ics_prefix);
3738                     SendToICS("refresh\n");
3739                 }
3740                 continue;
3741             }
3742
3743             /* Error messages */
3744 //          if (ics_user_moved) {
3745             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3746                 if (looking_at(buf, &i, "Illegal move") ||
3747                     looking_at(buf, &i, "Not a legal move") ||
3748                     looking_at(buf, &i, "Your king is in check") ||
3749                     looking_at(buf, &i, "It isn't your turn") ||
3750                     looking_at(buf, &i, "It is not your move")) {
3751                     /* Illegal move */
3752                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3753                         currentMove = forwardMostMove-1;
3754                         DisplayMove(currentMove - 1); /* before DMError */
3755                         DrawPosition(FALSE, boards[currentMove]);
3756                         SwitchClocks(forwardMostMove-1); // [HGM] race
3757                         DisplayBothClocks();
3758                     }
3759                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3760                     ics_user_moved = 0;
3761                     continue;
3762                 }
3763             }
3764
3765             if (looking_at(buf, &i, "still have time") ||
3766                 looking_at(buf, &i, "not out of time") ||
3767                 looking_at(buf, &i, "either player is out of time") ||
3768                 looking_at(buf, &i, "has timeseal; checking")) {
3769                 /* We must have called his flag a little too soon */
3770                 whiteFlag = blackFlag = FALSE;
3771                 continue;
3772             }
3773
3774             if (looking_at(buf, &i, "added * seconds to") ||
3775                 looking_at(buf, &i, "seconds were added to")) {
3776                 /* Update the clocks */
3777                 SendToICS(ics_prefix);
3778                 SendToICS("refresh\n");
3779                 continue;
3780             }
3781
3782             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3783                 ics_clock_paused = TRUE;
3784                 StopClocks();
3785                 continue;
3786             }
3787
3788             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3789                 ics_clock_paused = FALSE;
3790                 StartClocks();
3791                 continue;
3792             }
3793
3794             /* Grab player ratings from the Creating: message.
3795                Note we have to check for the special case when
3796                the ICS inserts things like [white] or [black]. */
3797             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3798                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3799                 /* star_matches:
3800                    0    player 1 name (not necessarily white)
3801                    1    player 1 rating
3802                    2    empty, white, or black (IGNORED)
3803                    3    player 2 name (not necessarily black)
3804                    4    player 2 rating
3805
3806                    The names/ratings are sorted out when the game
3807                    actually starts (below).
3808                 */
3809                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3810                 player1Rating = string_to_rating(star_match[1]);
3811                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3812                 player2Rating = string_to_rating(star_match[4]);
3813
3814                 if (appData.debugMode)
3815                   fprintf(debugFP,
3816                           "Ratings from 'Creating:' %s %d, %s %d\n",
3817                           player1Name, player1Rating,
3818                           player2Name, player2Rating);
3819
3820                 continue;
3821             }
3822
3823             /* Improved generic start/end-of-game messages */
3824             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3825                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3826                 /* If tkind == 0: */
3827                 /* star_match[0] is the game number */
3828                 /*           [1] is the white player's name */
3829                 /*           [2] is the black player's name */
3830                 /* For end-of-game: */
3831                 /*           [3] is the reason for the game end */
3832                 /*           [4] is a PGN end game-token, preceded by " " */
3833                 /* For start-of-game: */
3834                 /*           [3] begins with "Creating" or "Continuing" */
3835                 /*           [4] is " *" or empty (don't care). */
3836                 int gamenum = atoi(star_match[0]);
3837                 char *whitename, *blackname, *why, *endtoken;
3838                 ChessMove endtype = EndOfFile;
3839
3840                 if (tkind == 0) {
3841                   whitename = star_match[1];
3842                   blackname = star_match[2];
3843                   why = star_match[3];
3844                   endtoken = star_match[4];
3845                 } else {
3846                   whitename = star_match[1];
3847                   blackname = star_match[3];
3848                   why = star_match[5];
3849                   endtoken = star_match[6];
3850                 }
3851
3852                 /* Game start messages */
3853                 if (strncmp(why, "Creating ", 9) == 0 ||
3854                     strncmp(why, "Continuing ", 11) == 0) {
3855                     gs_gamenum = gamenum;
3856                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3857                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3858 #if ZIPPY
3859                     if (appData.zippyPlay) {
3860                         ZippyGameStart(whitename, blackname);
3861                     }
3862 #endif /*ZIPPY*/
3863                     partnerBoardValid = FALSE; // [HGM] bughouse
3864                     continue;
3865                 }
3866
3867                 /* Game end messages */
3868                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3869                     ics_gamenum != gamenum) {
3870                     continue;
3871                 }
3872                 while (endtoken[0] == ' ') endtoken++;
3873                 switch (endtoken[0]) {
3874                   case '*':
3875                   default:
3876                     endtype = GameUnfinished;
3877                     break;
3878                   case '0':
3879                     endtype = BlackWins;
3880                     break;
3881                   case '1':
3882                     if (endtoken[1] == '/')
3883                       endtype = GameIsDrawn;
3884                     else
3885                       endtype = WhiteWins;
3886                     break;
3887                 }
3888                 GameEnds(endtype, why, GE_ICS);
3889 #if ZIPPY
3890                 if (appData.zippyPlay && first.initDone) {
3891                     ZippyGameEnd(endtype, why);
3892                     if (first.pr == NULL) {
3893                       /* Start the next process early so that we'll
3894                          be ready for the next challenge */
3895                       StartChessProgram(&first);
3896                     }
3897                     /* Send "new" early, in case this command takes
3898                        a long time to finish, so that we'll be ready
3899                        for the next challenge. */
3900                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3901                     Reset(TRUE, TRUE);
3902                 }
3903 #endif /*ZIPPY*/
3904                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3905                 continue;
3906             }
3907
3908             if (looking_at(buf, &i, "Removing game * from observation") ||
3909                 looking_at(buf, &i, "no longer observing game *") ||
3910                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3911                 if (gameMode == IcsObserving &&
3912                     atoi(star_match[0]) == ics_gamenum)
3913                   {
3914                       /* icsEngineAnalyze */
3915                       if (appData.icsEngineAnalyze) {
3916                             ExitAnalyzeMode();
3917                             ModeHighlight();
3918                       }
3919                       StopClocks();
3920                       gameMode = IcsIdle;
3921                       ics_gamenum = -1;
3922                       ics_user_moved = FALSE;
3923                   }
3924                 continue;
3925             }
3926
3927             if (looking_at(buf, &i, "no longer examining game *")) {
3928                 if (gameMode == IcsExamining &&
3929                     atoi(star_match[0]) == ics_gamenum)
3930                   {
3931                       gameMode = IcsIdle;
3932                       ics_gamenum = -1;
3933                       ics_user_moved = FALSE;
3934                   }
3935                 continue;
3936             }
3937
3938             /* Advance leftover_start past any newlines we find,
3939                so only partial lines can get reparsed */
3940             if (looking_at(buf, &i, "\n")) {
3941                 prevColor = curColor;
3942                 if (curColor != ColorNormal) {
3943                     if (oldi > next_out) {
3944                         SendToPlayer(&buf[next_out], oldi - next_out);
3945                         next_out = oldi;
3946                     }
3947                     Colorize(ColorNormal, FALSE);
3948                     curColor = ColorNormal;
3949                 }
3950                 if (started == STARTED_BOARD) {
3951                     started = STARTED_NONE;
3952                     parse[parse_pos] = NULLCHAR;
3953                     ParseBoard12(parse);
3954                     ics_user_moved = 0;
3955
3956                     /* Send premove here */
3957                     if (appData.premove) {
3958                       char str[MSG_SIZ];
3959                       if (currentMove == 0 &&
3960                           gameMode == IcsPlayingWhite &&
3961                           appData.premoveWhite) {
3962                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3963                         if (appData.debugMode)
3964                           fprintf(debugFP, "Sending premove:\n");
3965                         SendToICS(str);
3966                       } else if (currentMove == 1 &&
3967                                  gameMode == IcsPlayingBlack &&
3968                                  appData.premoveBlack) {
3969                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3970                         if (appData.debugMode)
3971                           fprintf(debugFP, "Sending premove:\n");
3972                         SendToICS(str);
3973                       } else if (gotPremove) {
3974                         gotPremove = 0;
3975                         ClearPremoveHighlights();
3976                         if (appData.debugMode)
3977                           fprintf(debugFP, "Sending premove:\n");
3978                           UserMoveEvent(premoveFromX, premoveFromY,
3979                                         premoveToX, premoveToY,
3980                                         premovePromoChar);
3981                       }
3982                     }
3983
3984                     /* Usually suppress following prompt */
3985                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3986                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3987                         if (looking_at(buf, &i, "*% ")) {
3988                             savingComment = FALSE;
3989                             suppressKibitz = 0;
3990                         }
3991                     }
3992                     next_out = i;
3993                 } else if (started == STARTED_HOLDINGS) {
3994                     int gamenum;
3995                     char new_piece[MSG_SIZ];
3996                     started = STARTED_NONE;
3997                     parse[parse_pos] = NULLCHAR;
3998                     if (appData.debugMode)
3999                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4000                                                         parse, currentMove);
4001                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4002                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4003                         if (gameInfo.variant == VariantNormal) {
4004                           /* [HGM] We seem to switch variant during a game!
4005                            * Presumably no holdings were displayed, so we have
4006                            * to move the position two files to the right to
4007                            * create room for them!
4008                            */
4009                           VariantClass newVariant;
4010                           switch(gameInfo.boardWidth) { // base guess on board width
4011                                 case 9:  newVariant = VariantShogi; break;
4012                                 case 10: newVariant = VariantGreat; break;
4013                                 default: newVariant = VariantCrazyhouse; break;
4014                           }
4015                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4016                           /* Get a move list just to see the header, which
4017                              will tell us whether this is really bug or zh */
4018                           if (ics_getting_history == H_FALSE) {
4019                             ics_getting_history = H_REQUESTED;
4020                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4021                             SendToICS(str);
4022                           }
4023                         }
4024                         new_piece[0] = NULLCHAR;
4025                         sscanf(parse, "game %d white [%s black [%s <- %s",
4026                                &gamenum, white_holding, black_holding,
4027                                new_piece);
4028                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4029                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4030                         /* [HGM] copy holdings to board holdings area */
4031                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4032                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4033                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4034 #if ZIPPY
4035                         if (appData.zippyPlay && first.initDone) {
4036                             ZippyHoldings(white_holding, black_holding,
4037                                           new_piece);
4038                         }
4039 #endif /*ZIPPY*/
4040                         if (tinyLayout || smallLayout) {
4041                             char wh[16], bh[16];
4042                             PackHolding(wh, white_holding);
4043                             PackHolding(bh, black_holding);
4044                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4045                                     gameInfo.white, gameInfo.black);
4046                         } else {
4047                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4048                                     gameInfo.white, white_holding,
4049                                     gameInfo.black, black_holding);
4050                         }
4051                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4052                         DrawPosition(FALSE, boards[currentMove]);
4053                         DisplayTitle(str);
4054                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4055                         sscanf(parse, "game %d white [%s black [%s <- %s",
4056                                &gamenum, white_holding, black_holding,
4057                                new_piece);
4058                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4059                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4060                         /* [HGM] copy holdings to partner-board holdings area */
4061                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4062                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4063                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4064                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4065                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4066                       }
4067                     }
4068                     /* Suppress following prompt */
4069                     if (looking_at(buf, &i, "*% ")) {
4070                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4071                         savingComment = FALSE;
4072                         suppressKibitz = 0;
4073                     }
4074                     next_out = i;
4075                 }
4076                 continue;
4077             }
4078
4079             i++;                /* skip unparsed character and loop back */
4080         }
4081
4082         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4083 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4084 //          SendToPlayer(&buf[next_out], i - next_out);
4085             started != STARTED_HOLDINGS && leftover_start > next_out) {
4086             SendToPlayer(&buf[next_out], leftover_start - next_out);
4087             next_out = i;
4088         }
4089
4090         leftover_len = buf_len - leftover_start;
4091         /* if buffer ends with something we couldn't parse,
4092            reparse it after appending the next read */
4093
4094     } else if (count == 0) {
4095         RemoveInputSource(isr);
4096         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4097     } else {
4098         DisplayFatalError(_("Error reading from ICS"), error, 1);
4099     }
4100 }
4101
4102
4103 /* Board style 12 looks like this:
4104
4105    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4106
4107  * The "<12> " is stripped before it gets to this routine.  The two
4108  * trailing 0's (flip state and clock ticking) are later addition, and
4109  * some chess servers may not have them, or may have only the first.
4110  * Additional trailing fields may be added in the future.
4111  */
4112
4113 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4114
4115 #define RELATION_OBSERVING_PLAYED    0
4116 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4117 #define RELATION_PLAYING_MYMOVE      1
4118 #define RELATION_PLAYING_NOTMYMOVE  -1
4119 #define RELATION_EXAMINING           2
4120 #define RELATION_ISOLATED_BOARD     -3
4121 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4122
4123 void
4124 ParseBoard12(string)
4125      char *string;
4126 {
4127     GameMode newGameMode;
4128     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4129     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4130     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4131     char to_play, board_chars[200];
4132     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4133     char black[32], white[32];
4134     Board board;
4135     int prevMove = currentMove;
4136     int ticking = 2;
4137     ChessMove moveType;
4138     int fromX, fromY, toX, toY;
4139     char promoChar;
4140     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4141     char *bookHit = NULL; // [HGM] book
4142     Boolean weird = FALSE, reqFlag = FALSE;
4143
4144     fromX = fromY = toX = toY = -1;
4145
4146     newGame = FALSE;
4147
4148     if (appData.debugMode)
4149       fprintf(debugFP, _("Parsing board: %s\n"), string);
4150
4151     move_str[0] = NULLCHAR;
4152     elapsed_time[0] = NULLCHAR;
4153     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4154         int  i = 0, j;
4155         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4156             if(string[i] == ' ') { ranks++; files = 0; }
4157             else files++;
4158             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4159             i++;
4160         }
4161         for(j = 0; j <i; j++) board_chars[j] = string[j];
4162         board_chars[i] = '\0';
4163         string += i + 1;
4164     }
4165     n = sscanf(string, PATTERN, &to_play, &double_push,
4166                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4167                &gamenum, white, black, &relation, &basetime, &increment,
4168                &white_stren, &black_stren, &white_time, &black_time,
4169                &moveNum, str, elapsed_time, move_str, &ics_flip,
4170                &ticking);
4171
4172     if (n < 21) {
4173         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4174         DisplayError(str, 0);
4175         return;
4176     }
4177
4178     /* Convert the move number to internal form */
4179     moveNum = (moveNum - 1) * 2;
4180     if (to_play == 'B') moveNum++;
4181     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4182       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4183                         0, 1);
4184       return;
4185     }
4186
4187     switch (relation) {
4188       case RELATION_OBSERVING_PLAYED:
4189       case RELATION_OBSERVING_STATIC:
4190         if (gamenum == -1) {
4191             /* Old ICC buglet */
4192             relation = RELATION_OBSERVING_STATIC;
4193         }
4194         newGameMode = IcsObserving;
4195         break;
4196       case RELATION_PLAYING_MYMOVE:
4197       case RELATION_PLAYING_NOTMYMOVE:
4198         newGameMode =
4199           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4200             IcsPlayingWhite : IcsPlayingBlack;
4201         break;
4202       case RELATION_EXAMINING:
4203         newGameMode = IcsExamining;
4204         break;
4205       case RELATION_ISOLATED_BOARD:
4206       default:
4207         /* Just display this board.  If user was doing something else,
4208            we will forget about it until the next board comes. */
4209         newGameMode = IcsIdle;
4210         break;
4211       case RELATION_STARTING_POSITION:
4212         newGameMode = gameMode;
4213         break;
4214     }
4215
4216     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4217          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4218       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4219       char *toSqr;
4220       for (k = 0; k < ranks; k++) {
4221         for (j = 0; j < files; j++)
4222           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4223         if(gameInfo.holdingsWidth > 1) {
4224              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4225              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4226         }
4227       }
4228       CopyBoard(partnerBoard, board);
4229       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4230         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4231         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4232       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4233       if(toSqr = strchr(str, '-')) {
4234         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4235         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4236       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4237       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4238       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4239       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4240       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4241       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4242                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4243       DisplayMessage(partnerStatus, "");
4244         partnerBoardValid = TRUE;
4245       return;
4246     }
4247
4248     /* Modify behavior for initial board display on move listing
4249        of wild games.
4250        */
4251     switch (ics_getting_history) {
4252       case H_FALSE:
4253       case H_REQUESTED:
4254         break;
4255       case H_GOT_REQ_HEADER:
4256       case H_GOT_UNREQ_HEADER:
4257         /* This is the initial position of the current game */
4258         gamenum = ics_gamenum;
4259         moveNum = 0;            /* old ICS bug workaround */
4260         if (to_play == 'B') {
4261           startedFromSetupPosition = TRUE;
4262           blackPlaysFirst = TRUE;
4263           moveNum = 1;
4264           if (forwardMostMove == 0) forwardMostMove = 1;
4265           if (backwardMostMove == 0) backwardMostMove = 1;
4266           if (currentMove == 0) currentMove = 1;
4267         }
4268         newGameMode = gameMode;
4269         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4270         break;
4271       case H_GOT_UNWANTED_HEADER:
4272         /* This is an initial board that we don't want */
4273         return;
4274       case H_GETTING_MOVES:
4275         /* Should not happen */
4276         DisplayError(_("Error gathering move list: extra board"), 0);
4277         ics_getting_history = H_FALSE;
4278         return;
4279     }
4280
4281    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4282                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4283      /* [HGM] We seem to have switched variant unexpectedly
4284       * Try to guess new variant from board size
4285       */
4286           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4287           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4288           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4289           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4290           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4291           if(!weird) newVariant = VariantNormal;
4292           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4293           /* Get a move list just to see the header, which
4294              will tell us whether this is really bug or zh */
4295           if (ics_getting_history == H_FALSE) {
4296             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4297             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4298             SendToICS(str);
4299           }
4300     }
4301
4302     /* Take action if this is the first board of a new game, or of a
4303        different game than is currently being displayed.  */
4304     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4305         relation == RELATION_ISOLATED_BOARD) {
4306
4307         /* Forget the old game and get the history (if any) of the new one */
4308         if (gameMode != BeginningOfGame) {
4309           Reset(TRUE, TRUE);
4310         }
4311         newGame = TRUE;
4312         if (appData.autoRaiseBoard) BoardToTop();
4313         prevMove = -3;
4314         if (gamenum == -1) {
4315             newGameMode = IcsIdle;
4316         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4317                    appData.getMoveList && !reqFlag) {
4318             /* Need to get game history */
4319             ics_getting_history = H_REQUESTED;
4320             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4321             SendToICS(str);
4322         }
4323
4324         /* Initially flip the board to have black on the bottom if playing
4325            black or if the ICS flip flag is set, but let the user change
4326            it with the Flip View button. */
4327         flipView = appData.autoFlipView ?
4328           (newGameMode == IcsPlayingBlack) || ics_flip :
4329           appData.flipView;
4330
4331         /* Done with values from previous mode; copy in new ones */
4332         gameMode = newGameMode;
4333         ModeHighlight();
4334         ics_gamenum = gamenum;
4335         if (gamenum == gs_gamenum) {
4336             int klen = strlen(gs_kind);
4337             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4338             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4339             gameInfo.event = StrSave(str);
4340         } else {
4341             gameInfo.event = StrSave("ICS game");
4342         }
4343         gameInfo.site = StrSave(appData.icsHost);
4344         gameInfo.date = PGNDate();
4345         gameInfo.round = StrSave("-");
4346         gameInfo.white = StrSave(white);
4347         gameInfo.black = StrSave(black);
4348         timeControl = basetime * 60 * 1000;
4349         timeControl_2 = 0;
4350         timeIncrement = increment * 1000;
4351         movesPerSession = 0;
4352         gameInfo.timeControl = TimeControlTagValue();
4353         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4354   if (appData.debugMode) {
4355     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4356     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4357     setbuf(debugFP, NULL);
4358   }
4359
4360         gameInfo.outOfBook = NULL;
4361
4362         /* Do we have the ratings? */
4363         if (strcmp(player1Name, white) == 0 &&
4364             strcmp(player2Name, black) == 0) {
4365             if (appData.debugMode)
4366               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4367                       player1Rating, player2Rating);
4368             gameInfo.whiteRating = player1Rating;
4369             gameInfo.blackRating = player2Rating;
4370         } else if (strcmp(player2Name, white) == 0 &&
4371                    strcmp(player1Name, black) == 0) {
4372             if (appData.debugMode)
4373               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4374                       player2Rating, player1Rating);
4375             gameInfo.whiteRating = player2Rating;
4376             gameInfo.blackRating = player1Rating;
4377         }
4378         player1Name[0] = player2Name[0] = NULLCHAR;
4379
4380         /* Silence shouts if requested */
4381         if (appData.quietPlay &&
4382             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4383             SendToICS(ics_prefix);
4384             SendToICS("set shout 0\n");
4385         }
4386     }
4387
4388     /* Deal with midgame name changes */
4389     if (!newGame) {
4390         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4391             if (gameInfo.white) free(gameInfo.white);
4392             gameInfo.white = StrSave(white);
4393         }
4394         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4395             if (gameInfo.black) free(gameInfo.black);
4396             gameInfo.black = StrSave(black);
4397         }
4398     }
4399
4400     /* Throw away game result if anything actually changes in examine mode */
4401     if (gameMode == IcsExamining && !newGame) {
4402         gameInfo.result = GameUnfinished;
4403         if (gameInfo.resultDetails != NULL) {
4404             free(gameInfo.resultDetails);
4405             gameInfo.resultDetails = NULL;
4406         }
4407     }
4408
4409     /* In pausing && IcsExamining mode, we ignore boards coming
4410        in if they are in a different variation than we are. */
4411     if (pauseExamInvalid) return;
4412     if (pausing && gameMode == IcsExamining) {
4413         if (moveNum <= pauseExamForwardMostMove) {
4414             pauseExamInvalid = TRUE;
4415             forwardMostMove = pauseExamForwardMostMove;
4416             return;
4417         }
4418     }
4419
4420   if (appData.debugMode) {
4421     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4422   }
4423     /* Parse the board */
4424     for (k = 0; k < ranks; k++) {
4425       for (j = 0; j < files; j++)
4426         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4427       if(gameInfo.holdingsWidth > 1) {
4428            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4429            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4430       }
4431     }
4432     CopyBoard(boards[moveNum], board);
4433     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4434     if (moveNum == 0) {
4435         startedFromSetupPosition =
4436           !CompareBoards(board, initialPosition);
4437         if(startedFromSetupPosition)
4438             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4439     }
4440
4441     /* [HGM] Set castling rights. Take the outermost Rooks,
4442        to make it also work for FRC opening positions. Note that board12
4443        is really defective for later FRC positions, as it has no way to
4444        indicate which Rook can castle if they are on the same side of King.
4445        For the initial position we grant rights to the outermost Rooks,
4446        and remember thos rights, and we then copy them on positions
4447        later in an FRC game. This means WB might not recognize castlings with
4448        Rooks that have moved back to their original position as illegal,
4449        but in ICS mode that is not its job anyway.
4450     */
4451     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4452     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4453
4454         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4455             if(board[0][i] == WhiteRook) j = i;
4456         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4457         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4458             if(board[0][i] == WhiteRook) j = i;
4459         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4460         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4461             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4462         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4463         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4464             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4465         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4466
4467         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4468         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4469             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4470         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4471             if(board[BOARD_HEIGHT-1][k] == bKing)
4472                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4473         if(gameInfo.variant == VariantTwoKings) {
4474             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4475             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4476             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4477         }
4478     } else { int r;
4479         r = boards[moveNum][CASTLING][0] = initialRights[0];
4480         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4481         r = boards[moveNum][CASTLING][1] = initialRights[1];
4482         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4483         r = boards[moveNum][CASTLING][3] = initialRights[3];
4484         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4485         r = boards[moveNum][CASTLING][4] = initialRights[4];
4486         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4487         /* wildcastle kludge: always assume King has rights */
4488         r = boards[moveNum][CASTLING][2] = initialRights[2];
4489         r = boards[moveNum][CASTLING][5] = initialRights[5];
4490     }
4491     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4492     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4493
4494
4495     if (ics_getting_history == H_GOT_REQ_HEADER ||
4496         ics_getting_history == H_GOT_UNREQ_HEADER) {
4497         /* This was an initial position from a move list, not
4498            the current position */
4499         return;
4500     }
4501
4502     /* Update currentMove and known move number limits */
4503     newMove = newGame || moveNum > forwardMostMove;
4504
4505     if (newGame) {
4506         forwardMostMove = backwardMostMove = currentMove = moveNum;
4507         if (gameMode == IcsExamining && moveNum == 0) {
4508           /* Workaround for ICS limitation: we are not told the wild
4509              type when starting to examine a game.  But if we ask for
4510              the move list, the move list header will tell us */
4511             ics_getting_history = H_REQUESTED;
4512             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4513             SendToICS(str);
4514         }
4515     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4516                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4517 #if ZIPPY
4518         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4519         /* [HGM] applied this also to an engine that is silently watching        */
4520         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4521             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4522             gameInfo.variant == currentlyInitializedVariant) {
4523           takeback = forwardMostMove - moveNum;
4524           for (i = 0; i < takeback; i++) {
4525             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4526             SendToProgram("undo\n", &first);
4527           }
4528         }
4529 #endif
4530
4531         forwardMostMove = moveNum;
4532         if (!pausing || currentMove > forwardMostMove)
4533           currentMove = forwardMostMove;
4534     } else {
4535         /* New part of history that is not contiguous with old part */
4536         if (pausing && gameMode == IcsExamining) {
4537             pauseExamInvalid = TRUE;
4538             forwardMostMove = pauseExamForwardMostMove;
4539             return;
4540         }
4541         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4542 #if ZIPPY
4543             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4544                 // [HGM] when we will receive the move list we now request, it will be
4545                 // fed to the engine from the first move on. So if the engine is not
4546                 // in the initial position now, bring it there.
4547                 InitChessProgram(&first, 0);
4548             }
4549 #endif
4550             ics_getting_history = H_REQUESTED;
4551             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4552             SendToICS(str);
4553         }
4554         forwardMostMove = backwardMostMove = currentMove = moveNum;
4555     }
4556
4557     /* Update the clocks */
4558     if (strchr(elapsed_time, '.')) {
4559       /* Time is in ms */
4560       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4561       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4562     } else {
4563       /* Time is in seconds */
4564       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4565       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4566     }
4567
4568
4569 #if ZIPPY
4570     if (appData.zippyPlay && newGame &&
4571         gameMode != IcsObserving && gameMode != IcsIdle &&
4572         gameMode != IcsExamining)
4573       ZippyFirstBoard(moveNum, basetime, increment);
4574 #endif
4575
4576     /* Put the move on the move list, first converting
4577        to canonical algebraic form. */
4578     if (moveNum > 0) {
4579   if (appData.debugMode) {
4580     if (appData.debugMode) { int f = forwardMostMove;
4581         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4582                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4583                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4584     }
4585     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4586     fprintf(debugFP, "moveNum = %d\n", moveNum);
4587     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4588     setbuf(debugFP, NULL);
4589   }
4590         if (moveNum <= backwardMostMove) {
4591             /* We don't know what the board looked like before
4592                this move.  Punt. */
4593           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4594             strcat(parseList[moveNum - 1], " ");
4595             strcat(parseList[moveNum - 1], elapsed_time);
4596             moveList[moveNum - 1][0] = NULLCHAR;
4597         } else if (strcmp(move_str, "none") == 0) {
4598             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4599             /* Again, we don't know what the board looked like;
4600                this is really the start of the game. */
4601             parseList[moveNum - 1][0] = NULLCHAR;
4602             moveList[moveNum - 1][0] = NULLCHAR;
4603             backwardMostMove = moveNum;
4604             startedFromSetupPosition = TRUE;
4605             fromX = fromY = toX = toY = -1;
4606         } else {
4607           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4608           //                 So we parse the long-algebraic move string in stead of the SAN move
4609           int valid; char buf[MSG_SIZ], *prom;
4610
4611           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4612                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4613           // str looks something like "Q/a1-a2"; kill the slash
4614           if(str[1] == '/')
4615             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4616           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4617           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4618                 strcat(buf, prom); // long move lacks promo specification!
4619           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4620                 if(appData.debugMode)
4621                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4622                 safeStrCpy(move_str, buf, MSG_SIZ);
4623           }
4624           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4625                                 &fromX, &fromY, &toX, &toY, &promoChar)
4626                || ParseOneMove(buf, moveNum - 1, &moveType,
4627                                 &fromX, &fromY, &toX, &toY, &promoChar);
4628           // end of long SAN patch
4629           if (valid) {
4630             (void) CoordsToAlgebraic(boards[moveNum - 1],
4631                                      PosFlags(moveNum - 1),
4632                                      fromY, fromX, toY, toX, promoChar,
4633                                      parseList[moveNum-1]);
4634             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4635               case MT_NONE:
4636               case MT_STALEMATE:
4637               default:
4638                 break;
4639               case MT_CHECK:
4640                 if(gameInfo.variant != VariantShogi)
4641                     strcat(parseList[moveNum - 1], "+");
4642                 break;
4643               case MT_CHECKMATE:
4644               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4645                 strcat(parseList[moveNum - 1], "#");
4646                 break;
4647             }
4648             strcat(parseList[moveNum - 1], " ");
4649             strcat(parseList[moveNum - 1], elapsed_time);
4650             /* currentMoveString is set as a side-effect of ParseOneMove */
4651             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4652             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4653             strcat(moveList[moveNum - 1], "\n");
4654
4655             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4656                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4657               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4658                 ChessSquare old, new = boards[moveNum][k][j];
4659                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4660                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4661                   if(old == new) continue;
4662                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4663                   else if(new == WhiteWazir || new == BlackWazir) {
4664                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4665                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4666                       else boards[moveNum][k][j] = old; // preserve type of Gold
4667                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4668                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4669               }
4670           } else {
4671             /* Move from ICS was illegal!?  Punt. */
4672             if (appData.debugMode) {
4673               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4674               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4675             }
4676             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4677             strcat(parseList[moveNum - 1], " ");
4678             strcat(parseList[moveNum - 1], elapsed_time);
4679             moveList[moveNum - 1][0] = NULLCHAR;
4680             fromX = fromY = toX = toY = -1;
4681           }
4682         }
4683   if (appData.debugMode) {
4684     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4685     setbuf(debugFP, NULL);
4686   }
4687
4688 #if ZIPPY
4689         /* Send move to chess program (BEFORE animating it). */
4690         if (appData.zippyPlay && !newGame && newMove &&
4691            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4692
4693             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4694                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4695                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4696                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4697                             move_str);
4698                     DisplayError(str, 0);
4699                 } else {
4700                     if (first.sendTime) {
4701                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4702                     }
4703                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4704                     if (firstMove && !bookHit) {
4705                         firstMove = FALSE;
4706                         if (first.useColors) {
4707                           SendToProgram(gameMode == IcsPlayingWhite ?
4708                                         "white\ngo\n" :
4709                                         "black\ngo\n", &first);
4710                         } else {
4711                           SendToProgram("go\n", &first);
4712                         }
4713                         first.maybeThinking = TRUE;
4714                     }
4715                 }
4716             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4717               if (moveList[moveNum - 1][0] == NULLCHAR) {
4718                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4719                 DisplayError(str, 0);
4720               } else {
4721                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4722                 SendMoveToProgram(moveNum - 1, &first);
4723               }
4724             }
4725         }
4726 #endif
4727     }
4728
4729     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4730         /* If move comes from a remote source, animate it.  If it
4731            isn't remote, it will have already been animated. */
4732         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4733             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4734         }
4735         if (!pausing && appData.highlightLastMove) {
4736             SetHighlights(fromX, fromY, toX, toY);
4737         }
4738     }
4739
4740     /* Start the clocks */
4741     whiteFlag = blackFlag = FALSE;
4742     appData.clockMode = !(basetime == 0 && increment == 0);
4743     if (ticking == 0) {
4744       ics_clock_paused = TRUE;
4745       StopClocks();
4746     } else if (ticking == 1) {
4747       ics_clock_paused = FALSE;
4748     }
4749     if (gameMode == IcsIdle ||
4750         relation == RELATION_OBSERVING_STATIC ||
4751         relation == RELATION_EXAMINING ||
4752         ics_clock_paused)
4753       DisplayBothClocks();
4754     else
4755       StartClocks();
4756
4757     /* Display opponents and material strengths */
4758     if (gameInfo.variant != VariantBughouse &&
4759         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4760         if (tinyLayout || smallLayout) {
4761             if(gameInfo.variant == VariantNormal)
4762               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4763                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4764                     basetime, increment);
4765             else
4766               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4767                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4768                     basetime, increment, (int) gameInfo.variant);
4769         } else {
4770             if(gameInfo.variant == VariantNormal)
4771               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4772                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4773                     basetime, increment);
4774             else
4775               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4776                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4777                     basetime, increment, VariantName(gameInfo.variant));
4778         }
4779         DisplayTitle(str);
4780   if (appData.debugMode) {
4781     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4782   }
4783     }
4784
4785
4786     /* Display the board */
4787     if (!pausing && !appData.noGUI) {
4788
4789       if (appData.premove)
4790           if (!gotPremove ||
4791              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4792              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4793               ClearPremoveHighlights();
4794
4795       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4796         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4797       DrawPosition(j, boards[currentMove]);
4798
4799       DisplayMove(moveNum - 1);
4800       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4801             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4802               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4803         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4804       }
4805     }
4806
4807     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4808 #if ZIPPY
4809     if(bookHit) { // [HGM] book: simulate book reply
4810         static char bookMove[MSG_SIZ]; // a bit generous?
4811
4812         programStats.nodes = programStats.depth = programStats.time =
4813         programStats.score = programStats.got_only_move = 0;
4814         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4815
4816         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4817         strcat(bookMove, bookHit);
4818         HandleMachineMove(bookMove, &first);
4819     }
4820 #endif
4821 }
4822
4823 void
4824 GetMoveListEvent()
4825 {
4826     char buf[MSG_SIZ];
4827     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4828         ics_getting_history = H_REQUESTED;
4829         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4830         SendToICS(buf);
4831     }
4832 }
4833
4834 void
4835 AnalysisPeriodicEvent(force)
4836      int force;
4837 {
4838     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4839          && !force) || !appData.periodicUpdates)
4840       return;
4841
4842     /* Send . command to Crafty to collect stats */
4843     SendToProgram(".\n", &first);
4844
4845     /* Don't send another until we get a response (this makes
4846        us stop sending to old Crafty's which don't understand
4847        the "." command (sending illegal cmds resets node count & time,
4848        which looks bad)) */
4849     programStats.ok_to_send = 0;
4850 }
4851
4852 void ics_update_width(new_width)
4853         int new_width;
4854 {
4855         ics_printf("set width %d\n", new_width);
4856 }
4857
4858 void
4859 SendMoveToProgram(moveNum, cps)
4860      int moveNum;
4861      ChessProgramState *cps;
4862 {
4863     char buf[MSG_SIZ];
4864
4865     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4866         // null move in variant where engine does not understand it (for analysis purposes)
4867         SendBoard(cps, moveNum + 1); // send position after move in stead.
4868         return;
4869     }
4870     if (cps->useUsermove) {
4871       SendToProgram("usermove ", cps);
4872     }
4873     if (cps->useSAN) {
4874       char *space;
4875       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4876         int len = space - parseList[moveNum];
4877         memcpy(buf, parseList[moveNum], len);
4878         buf[len++] = '\n';
4879         buf[len] = NULLCHAR;
4880       } else {
4881         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4882       }
4883       SendToProgram(buf, cps);
4884     } else {
4885       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4886         AlphaRank(moveList[moveNum], 4);
4887         SendToProgram(moveList[moveNum], cps);
4888         AlphaRank(moveList[moveNum], 4); // and back
4889       } else
4890       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4891        * the engine. It would be nice to have a better way to identify castle
4892        * moves here. */
4893       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4894                                                                          && cps->useOOCastle) {
4895         int fromX = moveList[moveNum][0] - AAA;
4896         int fromY = moveList[moveNum][1] - ONE;
4897         int toX = moveList[moveNum][2] - AAA;
4898         int toY = moveList[moveNum][3] - ONE;
4899         if((boards[moveNum][fromY][fromX] == WhiteKing
4900             && boards[moveNum][toY][toX] == WhiteRook)
4901            || (boards[moveNum][fromY][fromX] == BlackKing
4902                && boards[moveNum][toY][toX] == BlackRook)) {
4903           if(toX > fromX) SendToProgram("O-O\n", cps);
4904           else SendToProgram("O-O-O\n", cps);
4905         }
4906         else SendToProgram(moveList[moveNum], cps);
4907       } else
4908       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4909         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4910           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4911           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4912                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4913         } else
4914           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4915                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4916         SendToProgram(buf, cps);
4917       }
4918       else SendToProgram(moveList[moveNum], cps);
4919       /* End of additions by Tord */
4920     }
4921
4922     /* [HGM] setting up the opening has brought engine in force mode! */
4923     /*       Send 'go' if we are in a mode where machine should play. */
4924     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4925         (gameMode == TwoMachinesPlay   ||
4926 #if ZIPPY
4927          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4928 #endif
4929          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4930         SendToProgram("go\n", cps);
4931   if (appData.debugMode) {
4932     fprintf(debugFP, "(extra)\n");
4933   }
4934     }
4935     setboardSpoiledMachineBlack = 0;
4936 }
4937
4938 void
4939 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4940      ChessMove moveType;
4941      int fromX, fromY, toX, toY;
4942      char promoChar;
4943 {
4944     char user_move[MSG_SIZ];
4945
4946     switch (moveType) {
4947       default:
4948         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4949                 (int)moveType, fromX, fromY, toX, toY);
4950         DisplayError(user_move + strlen("say "), 0);
4951         break;
4952       case WhiteKingSideCastle:
4953       case BlackKingSideCastle:
4954       case WhiteQueenSideCastleWild:
4955       case BlackQueenSideCastleWild:
4956       /* PUSH Fabien */
4957       case WhiteHSideCastleFR:
4958       case BlackHSideCastleFR:
4959       /* POP Fabien */
4960         snprintf(user_move, MSG_SIZ, "o-o\n");
4961         break;
4962       case WhiteQueenSideCastle:
4963       case BlackQueenSideCastle:
4964       case WhiteKingSideCastleWild:
4965       case BlackKingSideCastleWild:
4966       /* PUSH Fabien */
4967       case WhiteASideCastleFR:
4968       case BlackASideCastleFR:
4969       /* POP Fabien */
4970         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4971         break;
4972       case WhiteNonPromotion:
4973       case BlackNonPromotion:
4974         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4975         break;
4976       case WhitePromotion:
4977       case BlackPromotion:
4978         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4979           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4980                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4981                 PieceToChar(WhiteFerz));
4982         else if(gameInfo.variant == VariantGreat)
4983           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4984                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4985                 PieceToChar(WhiteMan));
4986         else
4987           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4988                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4989                 promoChar);
4990         break;
4991       case WhiteDrop:
4992       case BlackDrop:
4993       drop:
4994         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4995                  ToUpper(PieceToChar((ChessSquare) fromX)),
4996                  AAA + toX, ONE + toY);
4997         break;
4998       case IllegalMove:  /* could be a variant we don't quite understand */
4999         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5000       case NormalMove:
5001       case WhiteCapturesEnPassant:
5002       case BlackCapturesEnPassant:
5003         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5004                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5005         break;
5006     }
5007     SendToICS(user_move);
5008     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5009         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5010 }
5011
5012 void
5013 UploadGameEvent()
5014 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5015     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5016     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5017     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5018         DisplayError("You cannot do this while you are playing or observing", 0);
5019         return;
5020     }
5021     if(gameMode != IcsExamining) { // is this ever not the case?
5022         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5023
5024         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5025           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5026         } else { // on FICS we must first go to general examine mode
5027           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5028         }
5029         if(gameInfo.variant != VariantNormal) {
5030             // try figure out wild number, as xboard names are not always valid on ICS
5031             for(i=1; i<=36; i++) {
5032               snprintf(buf, MSG_SIZ, "wild/%d", i);
5033                 if(StringToVariant(buf) == gameInfo.variant) break;
5034             }
5035             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5036             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5037             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5038         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5039         SendToICS(ics_prefix);
5040         SendToICS(buf);
5041         if(startedFromSetupPosition || backwardMostMove != 0) {
5042           fen = PositionToFEN(backwardMostMove, NULL);
5043           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5044             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5045             SendToICS(buf);
5046           } else { // FICS: everything has to set by separate bsetup commands
5047             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5048             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5049             SendToICS(buf);
5050             if(!WhiteOnMove(backwardMostMove)) {
5051                 SendToICS("bsetup tomove black\n");
5052             }
5053             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5054             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5055             SendToICS(buf);
5056             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5057             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5058             SendToICS(buf);
5059             i = boards[backwardMostMove][EP_STATUS];
5060             if(i >= 0) { // set e.p.
5061               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5062                 SendToICS(buf);
5063             }
5064             bsetup++;
5065           }
5066         }
5067       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5068             SendToICS("bsetup done\n"); // switch to normal examining.
5069     }
5070     for(i = backwardMostMove; i<last; i++) {
5071         char buf[20];
5072         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5073         SendToICS(buf);
5074     }
5075     SendToICS(ics_prefix);
5076     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5077 }
5078
5079 void
5080 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5081      int rf, ff, rt, ft;
5082      char promoChar;
5083      char move[7];
5084 {
5085     if (rf == DROP_RANK) {
5086       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5087       sprintf(move, "%c@%c%c\n",
5088                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5089     } else {
5090         if (promoChar == 'x' || promoChar == NULLCHAR) {
5091           sprintf(move, "%c%c%c%c\n",
5092                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5093         } else {
5094             sprintf(move, "%c%c%c%c%c\n",
5095                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5096         }
5097     }
5098 }
5099
5100 void
5101 ProcessICSInitScript(f)
5102      FILE *f;
5103 {
5104     char buf[MSG_SIZ];
5105
5106     while (fgets(buf, MSG_SIZ, f)) {
5107         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5108     }
5109
5110     fclose(f);
5111 }
5112
5113
5114 static int lastX, lastY, selectFlag, dragging;
5115
5116 void
5117 Sweep(int step)
5118 {
5119     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5120     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5121     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5122     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5123     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5124     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5125     do {
5126         promoSweep -= step;
5127         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5128         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5129         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5130         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5131         if(!step) step = 1;
5132     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5133             appData.testLegality && (promoSweep == king ||
5134             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5135     ChangeDragPiece(promoSweep);
5136 }
5137
5138 int PromoScroll(int x, int y)
5139 {
5140   int step = 0;
5141
5142   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5143   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5144   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5145   if(!step) return FALSE;
5146   lastX = x; lastY = y;
5147   if((promoSweep < BlackPawn) == flipView) step = -step;
5148   if(step > 0) selectFlag = 1;
5149   if(!selectFlag) Sweep(step);
5150   return FALSE;
5151 }
5152
5153 void
5154 NextPiece(int step)
5155 {
5156     ChessSquare piece = boards[currentMove][toY][toX];
5157     do {
5158         pieceSweep -= step;
5159         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5160         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5161         if(!step) step = -1;
5162     } while(PieceToChar(pieceSweep) == '.');
5163     boards[currentMove][toY][toX] = pieceSweep;
5164     DrawPosition(FALSE, boards[currentMove]);
5165     boards[currentMove][toY][toX] = piece;
5166 }
5167 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5168 void
5169 AlphaRank(char *move, int n)
5170 {
5171 //    char *p = move, c; int x, y;
5172
5173     if (appData.debugMode) {
5174         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5175     }
5176
5177     if(move[1]=='*' &&
5178        move[2]>='0' && move[2]<='9' &&
5179        move[3]>='a' && move[3]<='x'    ) {
5180         move[1] = '@';
5181         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5182         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5183     } else
5184     if(move[0]>='0' && move[0]<='9' &&
5185        move[1]>='a' && move[1]<='x' &&
5186        move[2]>='0' && move[2]<='9' &&
5187        move[3]>='a' && move[3]<='x'    ) {
5188         /* input move, Shogi -> normal */
5189         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5190         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5191         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5192         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5193     } else
5194     if(move[1]=='@' &&
5195        move[3]>='0' && move[3]<='9' &&
5196        move[2]>='a' && move[2]<='x'    ) {
5197         move[1] = '*';
5198         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5199         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5200     } else
5201     if(
5202        move[0]>='a' && move[0]<='x' &&
5203        move[3]>='0' && move[3]<='9' &&
5204        move[2]>='a' && move[2]<='x'    ) {
5205          /* output move, normal -> Shogi */
5206         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5207         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5208         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5209         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5210         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5211     }
5212     if (appData.debugMode) {
5213         fprintf(debugFP, "   out = '%s'\n", move);
5214     }
5215 }
5216
5217 char yy_textstr[8000];
5218
5219 /* Parser for moves from gnuchess, ICS, or user typein box */
5220 Boolean
5221 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5222      char *move;
5223      int moveNum;
5224      ChessMove *moveType;
5225      int *fromX, *fromY, *toX, *toY;
5226      char *promoChar;
5227 {
5228     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5229
5230     switch (*moveType) {
5231       case WhitePromotion:
5232       case BlackPromotion:
5233       case WhiteNonPromotion:
5234       case BlackNonPromotion:
5235       case NormalMove:
5236       case WhiteCapturesEnPassant:
5237       case BlackCapturesEnPassant:
5238       case WhiteKingSideCastle:
5239       case WhiteQueenSideCastle:
5240       case BlackKingSideCastle:
5241       case BlackQueenSideCastle:
5242       case WhiteKingSideCastleWild:
5243       case WhiteQueenSideCastleWild:
5244       case BlackKingSideCastleWild:
5245       case BlackQueenSideCastleWild:
5246       /* Code added by Tord: */
5247       case WhiteHSideCastleFR:
5248       case WhiteASideCastleFR:
5249       case BlackHSideCastleFR:
5250       case BlackASideCastleFR:
5251       /* End of code added by Tord */
5252       case IllegalMove:         /* bug or odd chess variant */
5253         *fromX = currentMoveString[0] - AAA;
5254         *fromY = currentMoveString[1] - ONE;
5255         *toX = currentMoveString[2] - AAA;
5256         *toY = currentMoveString[3] - ONE;
5257         *promoChar = currentMoveString[4];
5258         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5259             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5260     if (appData.debugMode) {
5261         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5262     }
5263             *fromX = *fromY = *toX = *toY = 0;
5264             return FALSE;
5265         }
5266         if (appData.testLegality) {
5267           return (*moveType != IllegalMove);
5268         } else {
5269           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5270                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5271         }
5272
5273       case WhiteDrop:
5274       case BlackDrop:
5275         *fromX = *moveType == WhiteDrop ?
5276           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5277           (int) CharToPiece(ToLower(currentMoveString[0]));
5278         *fromY = DROP_RANK;
5279         *toX = currentMoveString[2] - AAA;
5280         *toY = currentMoveString[3] - ONE;
5281         *promoChar = NULLCHAR;
5282         return TRUE;
5283
5284       case AmbiguousMove:
5285       case ImpossibleMove:
5286       case EndOfFile:
5287       case ElapsedTime:
5288       case Comment:
5289       case PGNTag:
5290       case NAG:
5291       case WhiteWins:
5292       case BlackWins:
5293       case GameIsDrawn:
5294       default:
5295     if (appData.debugMode) {
5296         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5297     }
5298         /* bug? */
5299         *fromX = *fromY = *toX = *toY = 0;
5300         *promoChar = NULLCHAR;
5301         return FALSE;
5302     }
5303 }
5304
5305 Boolean pushed = FALSE;
5306 char *lastParseAttempt;
5307
5308 void
5309 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5310 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5311   int fromX, fromY, toX, toY; char promoChar;
5312   ChessMove moveType;
5313   Boolean valid;
5314   int nr = 0;
5315
5316   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5317     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5318     pushed = TRUE;
5319   }
5320   endPV = forwardMostMove;
5321   do {
5322     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5323     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5324     lastParseAttempt = pv;
5325     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5326 if(appData.debugMode){
5327 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5328 }
5329     if(!valid && nr == 0 &&
5330        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5331         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5332         // Hande case where played move is different from leading PV move
5333         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5334         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5335         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5336         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5337           endPV += 2; // if position different, keep this
5338           moveList[endPV-1][0] = fromX + AAA;
5339           moveList[endPV-1][1] = fromY + ONE;
5340           moveList[endPV-1][2] = toX + AAA;
5341           moveList[endPV-1][3] = toY + ONE;
5342           parseList[endPV-1][0] = NULLCHAR;
5343           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5344         }
5345       }
5346     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5347     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5348     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5349     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5350         valid++; // allow comments in PV
5351         continue;
5352     }
5353     nr++;
5354     if(endPV+1 > framePtr) break; // no space, truncate
5355     if(!valid) break;
5356     endPV++;
5357     CopyBoard(boards[endPV], boards[endPV-1]);
5358     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5359     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5360     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5361     CoordsToAlgebraic(boards[endPV - 1],
5362                              PosFlags(endPV - 1),
5363                              fromY, fromX, toY, toX, promoChar,
5364                              parseList[endPV - 1]);
5365   } while(valid);
5366   if(atEnd == 2) return; // used hidden, for PV conversion
5367   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5368   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5369   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5370                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5371   DrawPosition(TRUE, boards[currentMove]);
5372 }
5373
5374 int
5375 MultiPV(ChessProgramState *cps)
5376 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5377         int i;
5378         for(i=0; i<cps->nrOptions; i++)
5379             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5380                 return i;
5381         return -1;
5382 }
5383
5384 Boolean
5385 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5386 {
5387         int startPV, multi, lineStart, origIndex = index;
5388         char *p, buf2[MSG_SIZ];
5389
5390         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5391         lastX = x; lastY = y;
5392         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5393         lineStart = startPV = index;
5394         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5395         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5396         index = startPV;
5397         do{ while(buf[index] && buf[index] != '\n') index++;
5398         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5399         buf[index] = 0;
5400         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5401                 int n = first.option[multi].value;
5402                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5403                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5404                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5405                 first.option[multi].value = n;
5406                 *start = *end = 0;
5407                 return FALSE;
5408         }
5409         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5410         *start = startPV; *end = index-1;
5411         return TRUE;
5412 }
5413
5414 char *
5415 PvToSAN(char *pv)
5416 {
5417         static char buf[10*MSG_SIZ];
5418         int i, k=0, savedEnd=endPV;
5419         *buf = NULLCHAR;
5420         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5421         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5422         for(i = forwardMostMove; i<endPV; i++){
5423             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5424             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5425             k += strlen(buf+k);
5426         }
5427         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5428         if(forwardMostMove < savedEnd) PopInner(0);
5429         endPV = savedEnd;
5430         return buf;
5431 }
5432
5433 Boolean
5434 LoadPV(int x, int y)
5435 { // called on right mouse click to load PV
5436   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5437   lastX = x; lastY = y;
5438   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5439   return TRUE;
5440 }
5441
5442 void
5443 UnLoadPV()
5444 {
5445   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5446   if(endPV < 0) return;
5447   endPV = -1;
5448   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5449         Boolean saveAnimate = appData.animate;
5450         if(pushed) {
5451             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5452                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5453             } else storedGames--; // abandon shelved tail of original game
5454         }
5455         pushed = FALSE;
5456         forwardMostMove = currentMove;
5457         currentMove = oldFMM;
5458         appData.animate = FALSE;
5459         ToNrEvent(forwardMostMove);
5460         appData.animate = saveAnimate;
5461   }
5462   currentMove = forwardMostMove;
5463   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5464   ClearPremoveHighlights();
5465   DrawPosition(TRUE, boards[currentMove]);
5466 }
5467
5468 void
5469 MovePV(int x, int y, int h)
5470 { // step through PV based on mouse coordinates (called on mouse move)
5471   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5472
5473   // we must somehow check if right button is still down (might be released off board!)
5474   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5475   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5476   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5477   if(!step) return;
5478   lastX = x; lastY = y;
5479
5480   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5481   if(endPV < 0) return;
5482   if(y < margin) step = 1; else
5483   if(y > h - margin) step = -1;
5484   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5485   currentMove += step;
5486   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5487   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5488                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5489   DrawPosition(FALSE, boards[currentMove]);
5490 }
5491
5492
5493 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5494 // All positions will have equal probability, but the current method will not provide a unique
5495 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5496 #define DARK 1
5497 #define LITE 2
5498 #define ANY 3
5499
5500 int squaresLeft[4];
5501 int piecesLeft[(int)BlackPawn];
5502 int seed, nrOfShuffles;
5503
5504 void GetPositionNumber()
5505 {       // sets global variable seed
5506         int i;
5507
5508         seed = appData.defaultFrcPosition;
5509         if(seed < 0) { // randomize based on time for negative FRC position numbers
5510                 for(i=0; i<50; i++) seed += random();
5511                 seed = random() ^ random() >> 8 ^ random() << 8;
5512                 if(seed<0) seed = -seed;
5513         }
5514 }
5515
5516 int put(Board board, int pieceType, int rank, int n, int shade)
5517 // put the piece on the (n-1)-th empty squares of the given shade
5518 {
5519         int i;
5520
5521         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5522                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5523                         board[rank][i] = (ChessSquare) pieceType;
5524                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5525                         squaresLeft[ANY]--;
5526                         piecesLeft[pieceType]--;
5527                         return i;
5528                 }
5529         }
5530         return -1;
5531 }
5532
5533
5534 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5535 // calculate where the next piece goes, (any empty square), and put it there
5536 {
5537         int i;
5538
5539         i = seed % squaresLeft[shade];
5540         nrOfShuffles *= squaresLeft[shade];
5541         seed /= squaresLeft[shade];
5542         put(board, pieceType, rank, i, shade);
5543 }
5544
5545 void AddTwoPieces(Board board, int pieceType, int rank)
5546 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5547 {
5548         int i, n=squaresLeft[ANY], j=n-1, k;
5549
5550         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5551         i = seed % k;  // pick one
5552         nrOfShuffles *= k;
5553         seed /= k;
5554         while(i >= j) i -= j--;
5555         j = n - 1 - j; i += j;
5556         put(board, pieceType, rank, j, ANY);
5557         put(board, pieceType, rank, i, ANY);
5558 }
5559
5560 void SetUpShuffle(Board board, int number)
5561 {
5562         int i, p, first=1;
5563
5564         GetPositionNumber(); nrOfShuffles = 1;
5565
5566         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5567         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5568         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5569
5570         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5571
5572         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5573             p = (int) board[0][i];
5574             if(p < (int) BlackPawn) piecesLeft[p] ++;
5575             board[0][i] = EmptySquare;
5576         }
5577
5578         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5579             // shuffles restricted to allow normal castling put KRR first
5580             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5581                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5582             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5583                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5584             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5585                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5586             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5587                 put(board, WhiteRook, 0, 0, ANY);
5588             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5589         }
5590
5591         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5592             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5593             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5594                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5595                 while(piecesLeft[p] >= 2) {
5596                     AddOnePiece(board, p, 0, LITE);
5597                     AddOnePiece(board, p, 0, DARK);
5598                 }
5599                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5600             }
5601
5602         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5603             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5604             // but we leave King and Rooks for last, to possibly obey FRC restriction
5605             if(p == (int)WhiteRook) continue;
5606             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5607             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5608         }
5609
5610         // now everything is placed, except perhaps King (Unicorn) and Rooks
5611
5612         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5613             // Last King gets castling rights
5614             while(piecesLeft[(int)WhiteUnicorn]) {
5615                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5616                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5617             }
5618
5619             while(piecesLeft[(int)WhiteKing]) {
5620                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5621                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5622             }
5623
5624
5625         } else {
5626             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5627             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5628         }
5629
5630         // Only Rooks can be left; simply place them all
5631         while(piecesLeft[(int)WhiteRook]) {
5632                 i = put(board, WhiteRook, 0, 0, ANY);
5633                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5634                         if(first) {
5635                                 first=0;
5636                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5637                         }
5638                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5639                 }
5640         }
5641         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5642             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5643         }
5644
5645         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5646 }
5647
5648 int SetCharTable( char *table, const char * map )
5649 /* [HGM] moved here from winboard.c because of its general usefulness */
5650 /*       Basically a safe strcpy that uses the last character as King */
5651 {
5652     int result = FALSE; int NrPieces;
5653
5654     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5655                     && NrPieces >= 12 && !(NrPieces&1)) {
5656         int i; /* [HGM] Accept even length from 12 to 34 */
5657
5658         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5659         for( i=0; i<NrPieces/2-1; i++ ) {
5660             table[i] = map[i];
5661             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5662         }
5663         table[(int) WhiteKing]  = map[NrPieces/2-1];
5664         table[(int) BlackKing]  = map[NrPieces-1];
5665
5666         result = TRUE;
5667     }
5668
5669     return result;
5670 }
5671
5672 void Prelude(Board board)
5673 {       // [HGM] superchess: random selection of exo-pieces
5674         int i, j, k; ChessSquare p;
5675         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5676
5677         GetPositionNumber(); // use FRC position number
5678
5679         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5680             SetCharTable(pieceToChar, appData.pieceToCharTable);
5681             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5682                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5683         }
5684
5685         j = seed%4;                 seed /= 4;
5686         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5687         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5688         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5689         j = seed%3 + (seed%3 >= j); seed /= 3;
5690         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5691         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5692         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5693         j = seed%3;                 seed /= 3;
5694         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5695         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5696         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5697         j = seed%2 + (seed%2 >= j); seed /= 2;
5698         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5699         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5700         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5701         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5702         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5703         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5704         put(board, exoPieces[0],    0, 0, ANY);
5705         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5706 }
5707
5708 void
5709 InitPosition(redraw)
5710      int redraw;
5711 {
5712     ChessSquare (* pieces)[BOARD_FILES];
5713     int i, j, pawnRow, overrule,
5714     oldx = gameInfo.boardWidth,
5715     oldy = gameInfo.boardHeight,
5716     oldh = gameInfo.holdingsWidth;
5717     static int oldv;
5718
5719     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5720
5721     /* [AS] Initialize pv info list [HGM] and game status */
5722     {
5723         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5724             pvInfoList[i].depth = 0;
5725             boards[i][EP_STATUS] = EP_NONE;
5726             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5727         }
5728
5729         initialRulePlies = 0; /* 50-move counter start */
5730
5731         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5732         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5733     }
5734
5735
5736     /* [HGM] logic here is completely changed. In stead of full positions */
5737     /* the initialized data only consist of the two backranks. The switch */
5738     /* selects which one we will use, which is than copied to the Board   */
5739     /* initialPosition, which for the rest is initialized by Pawns and    */
5740     /* empty squares. This initial position is then copied to boards[0],  */
5741     /* possibly after shuffling, so that it remains available.            */
5742
5743     gameInfo.holdingsWidth = 0; /* default board sizes */
5744     gameInfo.boardWidth    = 8;
5745     gameInfo.boardHeight   = 8;
5746     gameInfo.holdingsSize  = 0;
5747     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5748     for(i=0; i<BOARD_FILES-2; i++)
5749       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5750     initialPosition[EP_STATUS] = EP_NONE;
5751     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5752     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5753          SetCharTable(pieceNickName, appData.pieceNickNames);
5754     else SetCharTable(pieceNickName, "............");
5755     pieces = FIDEArray;
5756
5757     switch (gameInfo.variant) {
5758     case VariantFischeRandom:
5759       shuffleOpenings = TRUE;
5760     default:
5761       break;
5762     case VariantShatranj:
5763       pieces = ShatranjArray;
5764       nrCastlingRights = 0;
5765       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5766       break;
5767     case VariantMakruk:
5768       pieces = makrukArray;
5769       nrCastlingRights = 0;
5770       startedFromSetupPosition = TRUE;
5771       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5772       break;
5773     case VariantTwoKings:
5774       pieces = twoKingsArray;
5775       break;
5776     case VariantGrand:
5777       pieces = GrandArray;
5778       nrCastlingRights = 0;
5779       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5780       gameInfo.boardWidth = 10;
5781       gameInfo.boardHeight = 10;
5782       gameInfo.holdingsSize = 7;
5783       break;
5784     case VariantCapaRandom:
5785       shuffleOpenings = TRUE;
5786     case VariantCapablanca:
5787       pieces = CapablancaArray;
5788       gameInfo.boardWidth = 10;
5789       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5790       break;
5791     case VariantGothic:
5792       pieces = GothicArray;
5793       gameInfo.boardWidth = 10;
5794       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5795       break;
5796     case VariantSChess:
5797       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5798       gameInfo.holdingsSize = 7;
5799       break;
5800     case VariantJanus:
5801       pieces = JanusArray;
5802       gameInfo.boardWidth = 10;
5803       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5804       nrCastlingRights = 6;
5805         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5806         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5807         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5808         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5809         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5810         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5811       break;
5812     case VariantFalcon:
5813       pieces = FalconArray;
5814       gameInfo.boardWidth = 10;
5815       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5816       break;
5817     case VariantXiangqi:
5818       pieces = XiangqiArray;
5819       gameInfo.boardWidth  = 9;
5820       gameInfo.boardHeight = 10;
5821       nrCastlingRights = 0;
5822       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5823       break;
5824     case VariantShogi:
5825       pieces = ShogiArray;
5826       gameInfo.boardWidth  = 9;
5827       gameInfo.boardHeight = 9;
5828       gameInfo.holdingsSize = 7;
5829       nrCastlingRights = 0;
5830       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5831       break;
5832     case VariantCourier:
5833       pieces = CourierArray;
5834       gameInfo.boardWidth  = 12;
5835       nrCastlingRights = 0;
5836       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5837       break;
5838     case VariantKnightmate:
5839       pieces = KnightmateArray;
5840       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5841       break;
5842     case VariantSpartan:
5843       pieces = SpartanArray;
5844       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5845       break;
5846     case VariantFairy:
5847       pieces = fairyArray;
5848       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5849       break;
5850     case VariantGreat:
5851       pieces = GreatArray;
5852       gameInfo.boardWidth = 10;
5853       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5854       gameInfo.holdingsSize = 8;
5855       break;
5856     case VariantSuper:
5857       pieces = FIDEArray;
5858       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5859       gameInfo.holdingsSize = 8;
5860       startedFromSetupPosition = TRUE;
5861       break;
5862     case VariantCrazyhouse:
5863     case VariantBughouse:
5864       pieces = FIDEArray;
5865       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5866       gameInfo.holdingsSize = 5;
5867       break;
5868     case VariantWildCastle:
5869       pieces = FIDEArray;
5870       /* !!?shuffle with kings guaranteed to be on d or e file */
5871       shuffleOpenings = 1;
5872       break;
5873     case VariantNoCastle:
5874       pieces = FIDEArray;
5875       nrCastlingRights = 0;
5876       /* !!?unconstrained back-rank shuffle */
5877       shuffleOpenings = 1;
5878       break;
5879     }
5880
5881     overrule = 0;
5882     if(appData.NrFiles >= 0) {
5883         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5884         gameInfo.boardWidth = appData.NrFiles;
5885     }
5886     if(appData.NrRanks >= 0) {
5887         gameInfo.boardHeight = appData.NrRanks;
5888     }
5889     if(appData.holdingsSize >= 0) {
5890         i = appData.holdingsSize;
5891         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5892         gameInfo.holdingsSize = i;
5893     }
5894     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5895     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5896         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5897
5898     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5899     if(pawnRow < 1) pawnRow = 1;
5900     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5901
5902     /* User pieceToChar list overrules defaults */
5903     if(appData.pieceToCharTable != NULL)
5904         SetCharTable(pieceToChar, appData.pieceToCharTable);
5905
5906     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5907
5908         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5909             s = (ChessSquare) 0; /* account holding counts in guard band */
5910         for( i=0; i<BOARD_HEIGHT; i++ )
5911             initialPosition[i][j] = s;
5912
5913         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5914         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5915         initialPosition[pawnRow][j] = WhitePawn;
5916         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5917         if(gameInfo.variant == VariantXiangqi) {
5918             if(j&1) {
5919                 initialPosition[pawnRow][j] =
5920                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5921                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5922                    initialPosition[2][j] = WhiteCannon;
5923                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5924                 }
5925             }
5926         }
5927         if(gameInfo.variant == VariantGrand) {
5928             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5929                initialPosition[0][j] = WhiteRook;
5930                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5931             }
5932         }
5933         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5934     }
5935     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5936
5937             j=BOARD_LEFT+1;
5938             initialPosition[1][j] = WhiteBishop;
5939             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5940             j=BOARD_RGHT-2;
5941             initialPosition[1][j] = WhiteRook;
5942             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5943     }
5944
5945     if( nrCastlingRights == -1) {
5946         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5947         /*       This sets default castling rights from none to normal corners   */
5948         /* Variants with other castling rights must set them themselves above    */
5949         nrCastlingRights = 6;
5950
5951         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5952         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5953         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5954         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5955         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5956         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5957      }
5958
5959      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5960      if(gameInfo.variant == VariantGreat) { // promotion commoners
5961         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5962         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5963         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5964         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5965      }
5966      if( gameInfo.variant == VariantSChess ) {
5967       initialPosition[1][0] = BlackMarshall;
5968       initialPosition[2][0] = BlackAngel;
5969       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5970       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5971       initialPosition[1][1] = initialPosition[2][1] = 
5972       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5973      }
5974   if (appData.debugMode) {
5975     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5976   }
5977     if(shuffleOpenings) {
5978         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5979         startedFromSetupPosition = TRUE;
5980     }
5981     if(startedFromPositionFile) {
5982       /* [HGM] loadPos: use PositionFile for every new game */
5983       CopyBoard(initialPosition, filePosition);
5984       for(i=0; i<nrCastlingRights; i++)
5985           initialRights[i] = filePosition[CASTLING][i];
5986       startedFromSetupPosition = TRUE;
5987     }
5988
5989     CopyBoard(boards[0], initialPosition);
5990
5991     if(oldx != gameInfo.boardWidth ||
5992        oldy != gameInfo.boardHeight ||
5993        oldv != gameInfo.variant ||
5994        oldh != gameInfo.holdingsWidth
5995                                          )
5996             InitDrawingSizes(-2 ,0);
5997
5998     oldv = gameInfo.variant;
5999     if (redraw)
6000       DrawPosition(TRUE, boards[currentMove]);
6001 }
6002
6003 void
6004 SendBoard(cps, moveNum)
6005      ChessProgramState *cps;
6006      int moveNum;
6007 {
6008     char message[MSG_SIZ];
6009
6010     if (cps->useSetboard) {
6011       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6012       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6013       SendToProgram(message, cps);
6014       free(fen);
6015
6016     } else {
6017       ChessSquare *bp;
6018       int i, j;
6019       /* Kludge to set black to move, avoiding the troublesome and now
6020        * deprecated "black" command.
6021        */
6022       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6023         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6024
6025       SendToProgram("edit\n", cps);
6026       SendToProgram("#\n", cps);
6027       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6028         bp = &boards[moveNum][i][BOARD_LEFT];
6029         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6030           if ((int) *bp < (int) BlackPawn) {
6031             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6032                     AAA + j, ONE + i);
6033             if(message[0] == '+' || message[0] == '~') {
6034               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6035                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6036                         AAA + j, ONE + i);
6037             }
6038             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6039                 message[1] = BOARD_RGHT   - 1 - j + '1';
6040                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6041             }
6042             SendToProgram(message, cps);
6043           }
6044         }
6045       }
6046
6047       SendToProgram("c\n", cps);
6048       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6049         bp = &boards[moveNum][i][BOARD_LEFT];
6050         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6051           if (((int) *bp != (int) EmptySquare)
6052               && ((int) *bp >= (int) BlackPawn)) {
6053             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6054                     AAA + j, ONE + i);
6055             if(message[0] == '+' || message[0] == '~') {
6056               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6057                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6058                         AAA + j, ONE + i);
6059             }
6060             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6061                 message[1] = BOARD_RGHT   - 1 - j + '1';
6062                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6063             }
6064             SendToProgram(message, cps);
6065           }
6066         }
6067       }
6068
6069       SendToProgram(".\n", cps);
6070     }
6071     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6072 }
6073
6074 ChessSquare
6075 DefaultPromoChoice(int white)
6076 {
6077     ChessSquare result;
6078     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6079         result = WhiteFerz; // no choice
6080     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6081         result= WhiteKing; // in Suicide Q is the last thing we want
6082     else if(gameInfo.variant == VariantSpartan)
6083         result = white ? WhiteQueen : WhiteAngel;
6084     else result = WhiteQueen;
6085     if(!white) result = WHITE_TO_BLACK result;
6086     return result;
6087 }
6088
6089 static int autoQueen; // [HGM] oneclick
6090
6091 int
6092 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6093 {
6094     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6095     /* [HGM] add Shogi promotions */
6096     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6097     ChessSquare piece;
6098     ChessMove moveType;
6099     Boolean premove;
6100
6101     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6102     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6103
6104     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6105       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6106         return FALSE;
6107
6108     piece = boards[currentMove][fromY][fromX];
6109     if(gameInfo.variant == VariantShogi) {
6110         promotionZoneSize = BOARD_HEIGHT/3;
6111         highestPromotingPiece = (int)WhiteFerz;
6112     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6113         promotionZoneSize = 3;
6114     }
6115
6116     // Treat Lance as Pawn when it is not representing Amazon
6117     if(gameInfo.variant != VariantSuper) {
6118         if(piece == WhiteLance) piece = WhitePawn; else
6119         if(piece == BlackLance) piece = BlackPawn;
6120     }
6121
6122     // next weed out all moves that do not touch the promotion zone at all
6123     if((int)piece >= BlackPawn) {
6124         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6125              return FALSE;
6126         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6127     } else {
6128         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6129            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6130     }
6131
6132     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6133
6134     // weed out mandatory Shogi promotions
6135     if(gameInfo.variant == VariantShogi) {
6136         if(piece >= BlackPawn) {
6137             if(toY == 0 && piece == BlackPawn ||
6138                toY == 0 && piece == BlackQueen ||
6139                toY <= 1 && piece == BlackKnight) {
6140                 *promoChoice = '+';
6141                 return FALSE;
6142             }
6143         } else {
6144             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6145                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6146                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6147                 *promoChoice = '+';
6148                 return FALSE;
6149             }
6150         }
6151     }
6152
6153     // weed out obviously illegal Pawn moves
6154     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6155         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6156         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6157         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6158         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6159         // note we are not allowed to test for valid (non-)capture, due to premove
6160     }
6161
6162     // we either have a choice what to promote to, or (in Shogi) whether to promote
6163     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6164         *promoChoice = PieceToChar(BlackFerz);  // no choice
6165         return FALSE;
6166     }
6167     // no sense asking what we must promote to if it is going to explode...
6168     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6169         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6170         return FALSE;
6171     }
6172     // give caller the default choice even if we will not make it
6173     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6174     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6175     if(        sweepSelect && gameInfo.variant != VariantGreat
6176                            && gameInfo.variant != VariantGrand
6177                            && gameInfo.variant != VariantSuper) return FALSE;
6178     if(autoQueen) return FALSE; // predetermined
6179
6180     // suppress promotion popup on illegal moves that are not premoves
6181     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6182               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6183     if(appData.testLegality && !premove) {
6184         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6185                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6186         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6187             return FALSE;
6188     }
6189
6190     return TRUE;
6191 }
6192
6193 int
6194 InPalace(row, column)
6195      int row, column;
6196 {   /* [HGM] for Xiangqi */
6197     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6198          column < (BOARD_WIDTH + 4)/2 &&
6199          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6200     return FALSE;
6201 }
6202
6203 int
6204 PieceForSquare (x, y)
6205      int x;
6206      int y;
6207 {
6208   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6209      return -1;
6210   else
6211      return boards[currentMove][y][x];
6212 }
6213
6214 int
6215 OKToStartUserMove(x, y)
6216      int x, y;
6217 {
6218     ChessSquare from_piece;
6219     int white_piece;
6220
6221     if (matchMode) return FALSE;
6222     if (gameMode == EditPosition) return TRUE;
6223
6224     if (x >= 0 && y >= 0)
6225       from_piece = boards[currentMove][y][x];
6226     else
6227       from_piece = EmptySquare;
6228
6229     if (from_piece == EmptySquare) return FALSE;
6230
6231     white_piece = (int)from_piece >= (int)WhitePawn &&
6232       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6233
6234     switch (gameMode) {
6235       case AnalyzeFile:
6236       case TwoMachinesPlay:
6237       case EndOfGame:
6238         return FALSE;
6239
6240       case IcsObserving:
6241       case IcsIdle:
6242         return FALSE;
6243
6244       case MachinePlaysWhite:
6245       case IcsPlayingBlack:
6246         if (appData.zippyPlay) return FALSE;
6247         if (white_piece) {
6248             DisplayMoveError(_("You are playing Black"));
6249             return FALSE;
6250         }
6251         break;
6252
6253       case MachinePlaysBlack:
6254       case IcsPlayingWhite:
6255         if (appData.zippyPlay) return FALSE;
6256         if (!white_piece) {
6257             DisplayMoveError(_("You are playing White"));
6258             return FALSE;
6259         }
6260         break;
6261
6262       case PlayFromGameFile:
6263             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6264       case EditGame:
6265         if (!white_piece && WhiteOnMove(currentMove)) {
6266             DisplayMoveError(_("It is White's turn"));
6267             return FALSE;
6268         }
6269         if (white_piece && !WhiteOnMove(currentMove)) {
6270             DisplayMoveError(_("It is Black's turn"));
6271             return FALSE;
6272         }
6273         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6274             /* Editing correspondence game history */
6275             /* Could disallow this or prompt for confirmation */
6276             cmailOldMove = -1;
6277         }
6278         break;
6279
6280       case BeginningOfGame:
6281         if (appData.icsActive) return FALSE;
6282         if (!appData.noChessProgram) {
6283             if (!white_piece) {
6284                 DisplayMoveError(_("You are playing White"));
6285                 return FALSE;
6286             }
6287         }
6288         break;
6289
6290       case Training:
6291         if (!white_piece && WhiteOnMove(currentMove)) {
6292             DisplayMoveError(_("It is White's turn"));
6293             return FALSE;
6294         }
6295         if (white_piece && !WhiteOnMove(currentMove)) {
6296             DisplayMoveError(_("It is Black's turn"));
6297             return FALSE;
6298         }
6299         break;
6300
6301       default:
6302       case IcsExamining:
6303         break;
6304     }
6305     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6306         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6307         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6308         && gameMode != AnalyzeFile && gameMode != Training) {
6309         DisplayMoveError(_("Displayed position is not current"));
6310         return FALSE;
6311     }
6312     return TRUE;
6313 }
6314
6315 Boolean
6316 OnlyMove(int *x, int *y, Boolean captures) {
6317     DisambiguateClosure cl;
6318     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6319     switch(gameMode) {
6320       case MachinePlaysBlack:
6321       case IcsPlayingWhite:
6322       case BeginningOfGame:
6323         if(!WhiteOnMove(currentMove)) return FALSE;
6324         break;
6325       case MachinePlaysWhite:
6326       case IcsPlayingBlack:
6327         if(WhiteOnMove(currentMove)) return FALSE;
6328         break;
6329       case EditGame:
6330         break;
6331       default:
6332         return FALSE;
6333     }
6334     cl.pieceIn = EmptySquare;
6335     cl.rfIn = *y;
6336     cl.ffIn = *x;
6337     cl.rtIn = -1;
6338     cl.ftIn = -1;
6339     cl.promoCharIn = NULLCHAR;
6340     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6341     if( cl.kind == NormalMove ||
6342         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6343         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6344         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6345       fromX = cl.ff;
6346       fromY = cl.rf;
6347       *x = cl.ft;
6348       *y = cl.rt;
6349       return TRUE;
6350     }
6351     if(cl.kind != ImpossibleMove) return FALSE;
6352     cl.pieceIn = EmptySquare;
6353     cl.rfIn = -1;
6354     cl.ffIn = -1;
6355     cl.rtIn = *y;
6356     cl.ftIn = *x;
6357     cl.promoCharIn = NULLCHAR;
6358     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6359     if( cl.kind == NormalMove ||
6360         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6361         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6362         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6363       fromX = cl.ff;
6364       fromY = cl.rf;
6365       *x = cl.ft;
6366       *y = cl.rt;
6367       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6368       return TRUE;
6369     }
6370     return FALSE;
6371 }
6372
6373 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6374 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6375 int lastLoadGameUseList = FALSE;
6376 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6377 ChessMove lastLoadGameStart = EndOfFile;
6378
6379 void
6380 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6381      int fromX, fromY, toX, toY;
6382      int promoChar;
6383 {
6384     ChessMove moveType;
6385     ChessSquare pdown, pup;
6386
6387     /* Check if the user is playing in turn.  This is complicated because we
6388        let the user "pick up" a piece before it is his turn.  So the piece he
6389        tried to pick up may have been captured by the time he puts it down!
6390        Therefore we use the color the user is supposed to be playing in this
6391        test, not the color of the piece that is currently on the starting
6392        square---except in EditGame mode, where the user is playing both
6393        sides; fortunately there the capture race can't happen.  (It can
6394        now happen in IcsExamining mode, but that's just too bad.  The user
6395        will get a somewhat confusing message in that case.)
6396        */
6397
6398     switch (gameMode) {
6399       case AnalyzeFile:
6400       case TwoMachinesPlay:
6401       case EndOfGame:
6402       case IcsObserving:
6403       case IcsIdle:
6404         /* We switched into a game mode where moves are not accepted,
6405            perhaps while the mouse button was down. */
6406         return;
6407
6408       case MachinePlaysWhite:
6409         /* User is moving for Black */
6410         if (WhiteOnMove(currentMove)) {
6411             DisplayMoveError(_("It is White's turn"));
6412             return;
6413         }
6414         break;
6415
6416       case MachinePlaysBlack:
6417         /* User is moving for White */
6418         if (!WhiteOnMove(currentMove)) {
6419             DisplayMoveError(_("It is Black's turn"));
6420             return;
6421         }
6422         break;
6423
6424       case PlayFromGameFile:
6425             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6426       case EditGame:
6427       case IcsExamining:
6428       case BeginningOfGame:
6429       case AnalyzeMode:
6430       case Training:
6431         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6432         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6433             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6434             /* User is moving for Black */
6435             if (WhiteOnMove(currentMove)) {
6436                 DisplayMoveError(_("It is White's turn"));
6437                 return;
6438             }
6439         } else {
6440             /* User is moving for White */
6441             if (!WhiteOnMove(currentMove)) {
6442                 DisplayMoveError(_("It is Black's turn"));
6443                 return;
6444             }
6445         }
6446         break;
6447
6448       case IcsPlayingBlack:
6449         /* User is moving for Black */
6450         if (WhiteOnMove(currentMove)) {
6451             if (!appData.premove) {
6452                 DisplayMoveError(_("It is White's turn"));
6453             } else if (toX >= 0 && toY >= 0) {
6454                 premoveToX = toX;
6455                 premoveToY = toY;
6456                 premoveFromX = fromX;
6457                 premoveFromY = fromY;
6458                 premovePromoChar = promoChar;
6459                 gotPremove = 1;
6460                 if (appData.debugMode)
6461                     fprintf(debugFP, "Got premove: fromX %d,"
6462                             "fromY %d, toX %d, toY %d\n",
6463                             fromX, fromY, toX, toY);
6464             }
6465             return;
6466         }
6467         break;
6468
6469       case IcsPlayingWhite:
6470         /* User is moving for White */
6471         if (!WhiteOnMove(currentMove)) {
6472             if (!appData.premove) {
6473                 DisplayMoveError(_("It is Black's turn"));
6474             } else if (toX >= 0 && toY >= 0) {
6475                 premoveToX = toX;
6476                 premoveToY = toY;
6477                 premoveFromX = fromX;
6478                 premoveFromY = fromY;
6479                 premovePromoChar = promoChar;
6480                 gotPremove = 1;
6481                 if (appData.debugMode)
6482                     fprintf(debugFP, "Got premove: fromX %d,"
6483                             "fromY %d, toX %d, toY %d\n",
6484                             fromX, fromY, toX, toY);
6485             }
6486             return;
6487         }
6488         break;
6489
6490       default:
6491         break;
6492
6493       case EditPosition:
6494         /* EditPosition, empty square, or different color piece;
6495            click-click move is possible */
6496         if (toX == -2 || toY == -2) {
6497             boards[0][fromY][fromX] = EmptySquare;
6498             DrawPosition(FALSE, boards[currentMove]);
6499             return;
6500         } else if (toX >= 0 && toY >= 0) {
6501             boards[0][toY][toX] = boards[0][fromY][fromX];
6502             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6503                 if(boards[0][fromY][0] != EmptySquare) {
6504                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6505                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6506                 }
6507             } else
6508             if(fromX == BOARD_RGHT+1) {
6509                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6510                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6511                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6512                 }
6513             } else
6514             boards[0][fromY][fromX] = EmptySquare;
6515             DrawPosition(FALSE, boards[currentMove]);
6516             return;
6517         }
6518         return;
6519     }
6520
6521     if(toX < 0 || toY < 0) return;
6522     pdown = boards[currentMove][fromY][fromX];
6523     pup = boards[currentMove][toY][toX];
6524
6525     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6526     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6527          if( pup != EmptySquare ) return;
6528          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6529            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6530                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6531            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6532            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6533            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6534            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6535          fromY = DROP_RANK;
6536     }
6537
6538     /* [HGM] always test for legality, to get promotion info */
6539     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6540                                          fromY, fromX, toY, toX, promoChar);
6541
6542     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6543
6544     /* [HGM] but possibly ignore an IllegalMove result */
6545     if (appData.testLegality) {
6546         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6547             DisplayMoveError(_("Illegal move"));
6548             return;
6549         }
6550     }
6551
6552     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6553 }
6554
6555 /* Common tail of UserMoveEvent and DropMenuEvent */
6556 int
6557 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6558      ChessMove moveType;
6559      int fromX, fromY, toX, toY;
6560      /*char*/int promoChar;
6561 {
6562     char *bookHit = 0;
6563
6564     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6565         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6566         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6567         if(WhiteOnMove(currentMove)) {
6568             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6569         } else {
6570             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6571         }
6572     }
6573
6574     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6575        move type in caller when we know the move is a legal promotion */
6576     if(moveType == NormalMove && promoChar)
6577         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6578
6579     /* [HGM] <popupFix> The following if has been moved here from
6580        UserMoveEvent(). Because it seemed to belong here (why not allow
6581        piece drops in training games?), and because it can only be
6582        performed after it is known to what we promote. */
6583     if (gameMode == Training) {
6584       /* compare the move played on the board to the next move in the
6585        * game. If they match, display the move and the opponent's response.
6586        * If they don't match, display an error message.
6587        */
6588       int saveAnimate;
6589       Board testBoard;
6590       CopyBoard(testBoard, boards[currentMove]);
6591       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6592
6593       if (CompareBoards(testBoard, boards[currentMove+1])) {
6594         ForwardInner(currentMove+1);
6595
6596         /* Autoplay the opponent's response.
6597          * if appData.animate was TRUE when Training mode was entered,
6598          * the response will be animated.
6599          */
6600         saveAnimate = appData.animate;
6601         appData.animate = animateTraining;
6602         ForwardInner(currentMove+1);
6603         appData.animate = saveAnimate;
6604
6605         /* check for the end of the game */
6606         if (currentMove >= forwardMostMove) {
6607           gameMode = PlayFromGameFile;
6608           ModeHighlight();
6609           SetTrainingModeOff();
6610           DisplayInformation(_("End of game"));
6611         }
6612       } else {
6613         DisplayError(_("Incorrect move"), 0);
6614       }
6615       return 1;
6616     }
6617
6618   /* Ok, now we know that the move is good, so we can kill
6619      the previous line in Analysis Mode */
6620   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6621                                 && currentMove < forwardMostMove) {
6622     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6623     else forwardMostMove = currentMove;
6624   }
6625
6626   /* If we need the chess program but it's dead, restart it */
6627   ResurrectChessProgram();
6628
6629   /* A user move restarts a paused game*/
6630   if (pausing)
6631     PauseEvent();
6632
6633   thinkOutput[0] = NULLCHAR;
6634
6635   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6636
6637   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6638     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6639     return 1;
6640   }
6641
6642   if (gameMode == BeginningOfGame) {
6643     if (appData.noChessProgram) {
6644       gameMode = EditGame;
6645       SetGameInfo();
6646     } else {
6647       char buf[MSG_SIZ];
6648       gameMode = MachinePlaysBlack;
6649       StartClocks();
6650       SetGameInfo();
6651       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6652       DisplayTitle(buf);
6653       if (first.sendName) {
6654         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6655         SendToProgram(buf, &first);
6656       }
6657       StartClocks();
6658     }
6659     ModeHighlight();
6660   }
6661
6662   /* Relay move to ICS or chess engine */
6663   if (appData.icsActive) {
6664     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6665         gameMode == IcsExamining) {
6666       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6667         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6668         SendToICS("draw ");
6669         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6670       }
6671       // also send plain move, in case ICS does not understand atomic claims
6672       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6673       ics_user_moved = 1;
6674     }
6675   } else {
6676     if (first.sendTime && (gameMode == BeginningOfGame ||
6677                            gameMode == MachinePlaysWhite ||
6678                            gameMode == MachinePlaysBlack)) {
6679       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6680     }
6681     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6682          // [HGM] book: if program might be playing, let it use book
6683         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6684         first.maybeThinking = TRUE;
6685     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6686         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6687         SendBoard(&first, currentMove+1);
6688     } else SendMoveToProgram(forwardMostMove-1, &first);
6689     if (currentMove == cmailOldMove + 1) {
6690       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6691     }
6692   }
6693
6694   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6695
6696   switch (gameMode) {
6697   case EditGame:
6698     if(appData.testLegality)
6699     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6700     case MT_NONE:
6701     case MT_CHECK:
6702       break;
6703     case MT_CHECKMATE:
6704     case MT_STAINMATE:
6705       if (WhiteOnMove(currentMove)) {
6706         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6707       } else {
6708         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6709       }
6710       break;
6711     case MT_STALEMATE:
6712       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6713       break;
6714     }
6715     break;
6716
6717   case MachinePlaysBlack:
6718   case MachinePlaysWhite:
6719     /* disable certain menu options while machine is thinking */
6720     SetMachineThinkingEnables();
6721     break;
6722
6723   default:
6724     break;
6725   }
6726
6727   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6728   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6729
6730   if(bookHit) { // [HGM] book: simulate book reply
6731         static char bookMove[MSG_SIZ]; // a bit generous?
6732
6733         programStats.nodes = programStats.depth = programStats.time =
6734         programStats.score = programStats.got_only_move = 0;
6735         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6736
6737         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6738         strcat(bookMove, bookHit);
6739         HandleMachineMove(bookMove, &first);
6740   }
6741   return 1;
6742 }
6743
6744 void
6745 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6746      Board board;
6747      int flags;
6748      ChessMove kind;
6749      int rf, ff, rt, ft;
6750      VOIDSTAR closure;
6751 {
6752     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6753     Markers *m = (Markers *) closure;
6754     if(rf == fromY && ff == fromX)
6755         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6756                          || kind == WhiteCapturesEnPassant
6757                          || kind == BlackCapturesEnPassant);
6758     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6759 }
6760
6761 void
6762 MarkTargetSquares(int clear)
6763 {
6764   int x, y;
6765   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6766      !appData.testLegality || gameMode == EditPosition) return;
6767   if(clear) {
6768     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6769   } else {
6770     int capt = 0;
6771     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6772     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6773       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6774       if(capt)
6775       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6776     }
6777   }
6778   DrawPosition(TRUE, NULL);
6779 }
6780
6781 int
6782 Explode(Board board, int fromX, int fromY, int toX, int toY)
6783 {
6784     if(gameInfo.variant == VariantAtomic &&
6785        (board[toY][toX] != EmptySquare ||                     // capture?
6786         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6787                          board[fromY][fromX] == BlackPawn   )
6788       )) {
6789         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6790         return TRUE;
6791     }
6792     return FALSE;
6793 }
6794
6795 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6796
6797 int CanPromote(ChessSquare piece, int y)
6798 {
6799         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6800         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6801         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6802            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6803            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6804                                                   gameInfo.variant == VariantMakruk) return FALSE;
6805         return (piece == BlackPawn && y == 1 ||
6806                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6807                 piece == BlackLance && y == 1 ||
6808                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6809 }
6810
6811 void LeftClick(ClickType clickType, int xPix, int yPix)
6812 {
6813     int x, y;
6814     Boolean saveAnimate;
6815     static int second = 0, promotionChoice = 0, clearFlag = 0;
6816     char promoChoice = NULLCHAR;
6817     ChessSquare piece;
6818
6819     if(appData.seekGraph && appData.icsActive && loggedOn &&
6820         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6821         SeekGraphClick(clickType, xPix, yPix, 0);
6822         return;
6823     }
6824
6825     if (clickType == Press) ErrorPopDown();
6826
6827     x = EventToSquare(xPix, BOARD_WIDTH);
6828     y = EventToSquare(yPix, BOARD_HEIGHT);
6829     if (!flipView && y >= 0) {
6830         y = BOARD_HEIGHT - 1 - y;
6831     }
6832     if (flipView && x >= 0) {
6833         x = BOARD_WIDTH - 1 - x;
6834     }
6835
6836     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6837         defaultPromoChoice = promoSweep;
6838         promoSweep = EmptySquare;   // terminate sweep
6839         promoDefaultAltered = TRUE;
6840         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6841     }
6842
6843     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6844         if(clickType == Release) return; // ignore upclick of click-click destination
6845         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6846         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6847         if(gameInfo.holdingsWidth &&
6848                 (WhiteOnMove(currentMove)
6849                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6850                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6851             // click in right holdings, for determining promotion piece
6852             ChessSquare p = boards[currentMove][y][x];
6853             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6854             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6855             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6856                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6857                 fromX = fromY = -1;
6858                 return;
6859             }
6860         }
6861         DrawPosition(FALSE, boards[currentMove]);
6862         return;
6863     }
6864
6865     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6866     if(clickType == Press
6867             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6868               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6869               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6870         return;
6871
6872     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6873         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6874
6875     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6876         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6877                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6878         defaultPromoChoice = DefaultPromoChoice(side);
6879     }
6880
6881     autoQueen = appData.alwaysPromoteToQueen;
6882
6883     if (fromX == -1) {
6884       int originalY = y;
6885       gatingPiece = EmptySquare;
6886       if (clickType != Press) {
6887         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6888             DragPieceEnd(xPix, yPix); dragging = 0;
6889             DrawPosition(FALSE, NULL);
6890         }
6891         return;
6892       }
6893       fromX = x; fromY = y; toX = toY = -1;
6894       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6895          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6896          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6897             /* First square */
6898             if (OKToStartUserMove(fromX, fromY)) {
6899                 second = 0;
6900                 MarkTargetSquares(0);
6901                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6902                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6903                     promoSweep = defaultPromoChoice;
6904                     selectFlag = 0; lastX = xPix; lastY = yPix;
6905                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6906                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6907                 }
6908                 if (appData.highlightDragging) {
6909                     SetHighlights(fromX, fromY, -1, -1);
6910                 }
6911             } else fromX = fromY = -1;
6912             return;
6913         }
6914     }
6915
6916     /* fromX != -1 */
6917     if (clickType == Press && gameMode != EditPosition) {
6918         ChessSquare fromP;
6919         ChessSquare toP;
6920         int frc;
6921
6922         // ignore off-board to clicks
6923         if(y < 0 || x < 0) return;
6924
6925         /* Check if clicking again on the same color piece */
6926         fromP = boards[currentMove][fromY][fromX];
6927         toP = boards[currentMove][y][x];
6928         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6929         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6930              WhitePawn <= toP && toP <= WhiteKing &&
6931              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6932              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6933             (BlackPawn <= fromP && fromP <= BlackKing &&
6934              BlackPawn <= toP && toP <= BlackKing &&
6935              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6936              !(fromP == BlackKing && toP == BlackRook && frc))) {
6937             /* Clicked again on same color piece -- changed his mind */
6938             second = (x == fromX && y == fromY);
6939             promoDefaultAltered = FALSE;
6940             MarkTargetSquares(1);
6941            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6942             if (appData.highlightDragging) {
6943                 SetHighlights(x, y, -1, -1);
6944             } else {
6945                 ClearHighlights();
6946             }
6947             if (OKToStartUserMove(x, y)) {
6948                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6949                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6950                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6951                  gatingPiece = boards[currentMove][fromY][fromX];
6952                 else gatingPiece = EmptySquare;
6953                 fromX = x;
6954                 fromY = y; dragging = 1;
6955                 MarkTargetSquares(0);
6956                 DragPieceBegin(xPix, yPix, FALSE);
6957                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6958                     promoSweep = defaultPromoChoice;
6959                     selectFlag = 0; lastX = xPix; lastY = yPix;
6960                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6961                 }
6962             }
6963            }
6964            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6965            second = FALSE; 
6966         }
6967         // ignore clicks on holdings
6968         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6969     }
6970
6971     if (clickType == Release && x == fromX && y == fromY) {
6972         DragPieceEnd(xPix, yPix); dragging = 0;
6973         if(clearFlag) {
6974             // a deferred attempt to click-click move an empty square on top of a piece
6975             boards[currentMove][y][x] = EmptySquare;
6976             ClearHighlights();
6977             DrawPosition(FALSE, boards[currentMove]);
6978             fromX = fromY = -1; clearFlag = 0;
6979             return;
6980         }
6981         if (appData.animateDragging) {
6982             /* Undo animation damage if any */
6983             DrawPosition(FALSE, NULL);
6984         }
6985         if (second) {
6986             /* Second up/down in same square; just abort move */
6987             second = 0;
6988             fromX = fromY = -1;
6989             gatingPiece = EmptySquare;
6990             ClearHighlights();
6991             gotPremove = 0;
6992             ClearPremoveHighlights();
6993         } else {
6994             /* First upclick in same square; start click-click mode */
6995             SetHighlights(x, y, -1, -1);
6996         }
6997         return;
6998     }
6999
7000     clearFlag = 0;
7001
7002     /* we now have a different from- and (possibly off-board) to-square */
7003     /* Completed move */
7004     toX = x;
7005     toY = y;
7006     saveAnimate = appData.animate;
7007     MarkTargetSquares(1);
7008     if (clickType == Press) {
7009         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7010             // must be Edit Position mode with empty-square selected
7011             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7012             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7013             return;
7014         }
7015         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7016             ChessSquare piece = boards[currentMove][fromY][fromX];
7017             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7018             promoSweep = defaultPromoChoice;
7019             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7020             selectFlag = 0; lastX = xPix; lastY = yPix;
7021             Sweep(0); // Pawn that is going to promote: preview promotion piece
7022             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7023             DrawPosition(FALSE, boards[currentMove]);
7024             return;
7025         }
7026         /* Finish clickclick move */
7027         if (appData.animate || appData.highlightLastMove) {
7028             SetHighlights(fromX, fromY, toX, toY);
7029         } else {
7030             ClearHighlights();
7031         }
7032     } else {
7033         /* Finish drag move */
7034         if (appData.highlightLastMove) {
7035             SetHighlights(fromX, fromY, toX, toY);
7036         } else {
7037             ClearHighlights();
7038         }
7039         DragPieceEnd(xPix, yPix); dragging = 0;
7040         /* Don't animate move and drag both */
7041         appData.animate = FALSE;
7042     }
7043
7044     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7045     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7046         ChessSquare piece = boards[currentMove][fromY][fromX];
7047         if(gameMode == EditPosition && piece != EmptySquare &&
7048            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7049             int n;
7050
7051             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7052                 n = PieceToNumber(piece - (int)BlackPawn);
7053                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7054                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7055                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7056             } else
7057             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7058                 n = PieceToNumber(piece);
7059                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7060                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7061                 boards[currentMove][n][BOARD_WIDTH-2]++;
7062             }
7063             boards[currentMove][fromY][fromX] = EmptySquare;
7064         }
7065         ClearHighlights();
7066         fromX = fromY = -1;
7067         DrawPosition(TRUE, boards[currentMove]);
7068         return;
7069     }
7070
7071     // off-board moves should not be highlighted
7072     if(x < 0 || y < 0) ClearHighlights();
7073
7074     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7075
7076     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7077         SetHighlights(fromX, fromY, toX, toY);
7078         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7079             // [HGM] super: promotion to captured piece selected from holdings
7080             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7081             promotionChoice = TRUE;
7082             // kludge follows to temporarily execute move on display, without promoting yet
7083             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7084             boards[currentMove][toY][toX] = p;
7085             DrawPosition(FALSE, boards[currentMove]);
7086             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7087             boards[currentMove][toY][toX] = q;
7088             DisplayMessage("Click in holdings to choose piece", "");
7089             return;
7090         }
7091         PromotionPopUp();
7092     } else {
7093         int oldMove = currentMove;
7094         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7095         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7096         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7097         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7098            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7099             DrawPosition(TRUE, boards[currentMove]);
7100         fromX = fromY = -1;
7101     }
7102     appData.animate = saveAnimate;
7103     if (appData.animate || appData.animateDragging) {
7104         /* Undo animation damage if needed */
7105         DrawPosition(FALSE, NULL);
7106     }
7107 }
7108
7109 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7110 {   // front-end-free part taken out of PieceMenuPopup
7111     int whichMenu; int xSqr, ySqr;
7112
7113     if(seekGraphUp) { // [HGM] seekgraph
7114         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7115         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7116         return -2;
7117     }
7118
7119     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7120          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7121         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7122         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7123         if(action == Press)   {
7124             originalFlip = flipView;
7125             flipView = !flipView; // temporarily flip board to see game from partners perspective
7126             DrawPosition(TRUE, partnerBoard);
7127             DisplayMessage(partnerStatus, "");
7128             partnerUp = TRUE;
7129         } else if(action == Release) {
7130             flipView = originalFlip;
7131             DrawPosition(TRUE, boards[currentMove]);
7132             partnerUp = FALSE;
7133         }
7134         return -2;
7135     }
7136
7137     xSqr = EventToSquare(x, BOARD_WIDTH);
7138     ySqr = EventToSquare(y, BOARD_HEIGHT);
7139     if (action == Release) {
7140         if(pieceSweep != EmptySquare) {
7141             EditPositionMenuEvent(pieceSweep, toX, toY);
7142             pieceSweep = EmptySquare;
7143         } else UnLoadPV(); // [HGM] pv
7144     }
7145     if (action != Press) return -2; // return code to be ignored
7146     switch (gameMode) {
7147       case IcsExamining:
7148         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7149       case EditPosition:
7150         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7151         if (xSqr < 0 || ySqr < 0) return -1;
7152         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7153         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7154         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7155         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7156         NextPiece(0);
7157         return 2; // grab
7158       case IcsObserving:
7159         if(!appData.icsEngineAnalyze) return -1;
7160       case IcsPlayingWhite:
7161       case IcsPlayingBlack:
7162         if(!appData.zippyPlay) goto noZip;
7163       case AnalyzeMode:
7164       case AnalyzeFile:
7165       case MachinePlaysWhite:
7166       case MachinePlaysBlack:
7167       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7168         if (!appData.dropMenu) {
7169           LoadPV(x, y);
7170           return 2; // flag front-end to grab mouse events
7171         }
7172         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7173            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7174       case EditGame:
7175       noZip:
7176         if (xSqr < 0 || ySqr < 0) return -1;
7177         if (!appData.dropMenu || appData.testLegality &&
7178             gameInfo.variant != VariantBughouse &&
7179             gameInfo.variant != VariantCrazyhouse) return -1;
7180         whichMenu = 1; // drop menu
7181         break;
7182       default:
7183         return -1;
7184     }
7185
7186     if (((*fromX = xSqr) < 0) ||
7187         ((*fromY = ySqr) < 0)) {
7188         *fromX = *fromY = -1;
7189         return -1;
7190     }
7191     if (flipView)
7192       *fromX = BOARD_WIDTH - 1 - *fromX;
7193     else
7194       *fromY = BOARD_HEIGHT - 1 - *fromY;
7195
7196     return whichMenu;
7197 }
7198
7199 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7200 {
7201 //    char * hint = lastHint;
7202     FrontEndProgramStats stats;
7203
7204     stats.which = cps == &first ? 0 : 1;
7205     stats.depth = cpstats->depth;
7206     stats.nodes = cpstats->nodes;
7207     stats.score = cpstats->score;
7208     stats.time = cpstats->time;
7209     stats.pv = cpstats->movelist;
7210     stats.hint = lastHint;
7211     stats.an_move_index = 0;
7212     stats.an_move_count = 0;
7213
7214     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7215         stats.hint = cpstats->move_name;
7216         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7217         stats.an_move_count = cpstats->nr_moves;
7218     }
7219
7220     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7221
7222     SetProgramStats( &stats );
7223 }
7224
7225 void
7226 ClearEngineOutputPane(int which)
7227 {
7228     static FrontEndProgramStats dummyStats;
7229     dummyStats.which = which;
7230     dummyStats.pv = "#";
7231     SetProgramStats( &dummyStats );
7232 }
7233
7234 #define MAXPLAYERS 500
7235
7236 char *
7237 TourneyStandings(int display)
7238 {
7239     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7240     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7241     char result, *p, *names[MAXPLAYERS];
7242
7243     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7244         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7245     names[0] = p = strdup(appData.participants);
7246     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7247
7248     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7249
7250     while(result = appData.results[nr]) {
7251         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7252         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7253         wScore = bScore = 0;
7254         switch(result) {
7255           case '+': wScore = 2; break;
7256           case '-': bScore = 2; break;
7257           case '=': wScore = bScore = 1; break;
7258           case ' ':
7259           case '*': return strdup("busy"); // tourney not finished
7260         }
7261         score[w] += wScore;
7262         score[b] += bScore;
7263         games[w]++;
7264         games[b]++;
7265         nr++;
7266     }
7267     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7268     for(w=0; w<nPlayers; w++) {
7269         bScore = -1;
7270         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7271         ranking[w] = b; points[w] = bScore; score[b] = -2;
7272     }
7273     p = malloc(nPlayers*34+1);
7274     for(w=0; w<nPlayers && w<display; w++)
7275         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7276     free(names[0]);
7277     return p;
7278 }
7279
7280 void
7281 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7282 {       // count all piece types
7283         int p, f, r;
7284         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7285         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7286         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7287                 p = board[r][f];
7288                 pCnt[p]++;
7289                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7290                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7291                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7292                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7293                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7294                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7295         }
7296 }
7297
7298 int
7299 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7300 {
7301         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7302         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7303
7304         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7305         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7306         if(myPawns == 2 && nMine == 3) // KPP
7307             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7308         if(myPawns == 1 && nMine == 2) // KP
7309             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7310         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7311             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7312         if(myPawns) return FALSE;
7313         if(pCnt[WhiteRook+side])
7314             return pCnt[BlackRook-side] ||
7315                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7316                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7317                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7318         if(pCnt[WhiteCannon+side]) {
7319             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7320             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7321         }
7322         if(pCnt[WhiteKnight+side])
7323             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7324         return FALSE;
7325 }
7326
7327 int
7328 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7329 {
7330         VariantClass v = gameInfo.variant;
7331
7332         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7333         if(v == VariantShatranj) return TRUE; // always winnable through baring
7334         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7335         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7336
7337         if(v == VariantXiangqi) {
7338                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7339
7340                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7341                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7342                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7343                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7344                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7345                 if(stale) // we have at least one last-rank P plus perhaps C
7346                     return majors // KPKX
7347                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7348                 else // KCA*E*
7349                     return pCnt[WhiteFerz+side] // KCAK
7350                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7351                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7352                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7353
7354         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7355                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7356
7357                 if(nMine == 1) return FALSE; // bare King
7358                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
7359                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7360                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7361                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7362                 if(pCnt[WhiteKnight+side])
7363                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7364                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7365                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7366                 if(nBishops)
7367                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7368                 if(pCnt[WhiteAlfil+side])
7369                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7370                 if(pCnt[WhiteWazir+side])
7371                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7372         }
7373
7374         return TRUE;
7375 }
7376
7377 int
7378 CompareWithRights(Board b1, Board b2)
7379 {
7380     int rights = 0;
7381     if(!CompareBoards(b1, b2)) return FALSE;
7382     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7383     /* compare castling rights */
7384     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7385            rights++; /* King lost rights, while rook still had them */
7386     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7387         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7388            rights++; /* but at least one rook lost them */
7389     }
7390     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7391            rights++;
7392     if( b1[CASTLING][5] != NoRights ) {
7393         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7394            rights++;
7395     }
7396     return rights == 0;
7397 }
7398
7399 int
7400 Adjudicate(ChessProgramState *cps)
7401 {       // [HGM] some adjudications useful with buggy engines
7402         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7403         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7404         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7405         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7406         int k, count = 0; static int bare = 1;
7407         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7408         Boolean canAdjudicate = !appData.icsActive;
7409
7410         // most tests only when we understand the game, i.e. legality-checking on
7411             if( appData.testLegality )
7412             {   /* [HGM] Some more adjudications for obstinate engines */
7413                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7414                 static int moveCount = 6;
7415                 ChessMove result;
7416                 char *reason = NULL;
7417
7418                 /* Count what is on board. */
7419                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7420
7421                 /* Some material-based adjudications that have to be made before stalemate test */
7422                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7423                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7424                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7425                      if(canAdjudicate && appData.checkMates) {
7426                          if(engineOpponent)
7427                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7428                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7429                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7430                          return 1;
7431                      }
7432                 }
7433
7434                 /* Bare King in Shatranj (loses) or Losers (wins) */
7435                 if( nrW == 1 || nrB == 1) {
7436                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7437                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7438                      if(canAdjudicate && appData.checkMates) {
7439                          if(engineOpponent)
7440                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7441                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7442                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7443                          return 1;
7444                      }
7445                   } else
7446                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7447                   {    /* bare King */
7448                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7449                         if(canAdjudicate && appData.checkMates) {
7450                             /* but only adjudicate if adjudication enabled */
7451                             if(engineOpponent)
7452                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7453                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7454                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7455                             return 1;
7456                         }
7457                   }
7458                 } else bare = 1;
7459
7460
7461             // don't wait for engine to announce game end if we can judge ourselves
7462             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7463               case MT_CHECK:
7464                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7465                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7466                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7467                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7468                             checkCnt++;
7469                         if(checkCnt >= 2) {
7470                             reason = "Xboard adjudication: 3rd check";
7471                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7472                             break;
7473                         }
7474                     }
7475                 }
7476               case MT_NONE:
7477               default:
7478                 break;
7479               case MT_STALEMATE:
7480               case MT_STAINMATE:
7481                 reason = "Xboard adjudication: Stalemate";
7482                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7483                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7484                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7485                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7486                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7487                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7488                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7489                                                                         EP_CHECKMATE : EP_WINS);
7490                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7491                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7492                 }
7493                 break;
7494               case MT_CHECKMATE:
7495                 reason = "Xboard adjudication: Checkmate";
7496                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7497                 break;
7498             }
7499
7500                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7501                     case EP_STALEMATE:
7502                         result = GameIsDrawn; break;
7503                     case EP_CHECKMATE:
7504                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7505                     case EP_WINS:
7506                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7507                     default:
7508                         result = EndOfFile;
7509                 }
7510                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7511                     if(engineOpponent)
7512                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7513                     GameEnds( result, reason, GE_XBOARD );
7514                     return 1;
7515                 }
7516
7517                 /* Next absolutely insufficient mating material. */
7518                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7519                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7520                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7521
7522                      /* always flag draws, for judging claims */
7523                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7524
7525                      if(canAdjudicate && appData.materialDraws) {
7526                          /* but only adjudicate them if adjudication enabled */
7527                          if(engineOpponent) {
7528                            SendToProgram("force\n", engineOpponent); // suppress reply
7529                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7530                          }
7531                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7532                          return 1;
7533                      }
7534                 }
7535
7536                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7537                 if(gameInfo.variant == VariantXiangqi ?
7538                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7539                  : nrW + nrB == 4 &&
7540                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7541                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7542                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7543                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7544                    ) ) {
7545                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7546                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7547                           if(engineOpponent) {
7548                             SendToProgram("force\n", engineOpponent); // suppress reply
7549                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7550                           }
7551                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7552                           return 1;
7553                      }
7554                 } else moveCount = 6;
7555             }
7556         if (appData.debugMode) { int i;
7557             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7558                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7559                     appData.drawRepeats);
7560             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7561               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7562
7563         }
7564
7565         // Repetition draws and 50-move rule can be applied independently of legality testing
7566
7567                 /* Check for rep-draws */
7568                 count = 0;
7569                 for(k = forwardMostMove-2;
7570                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7571                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7572                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7573                     k-=2)
7574                 {   int rights=0;
7575                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7576                         /* compare castling rights */
7577                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7578                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7579                                 rights++; /* King lost rights, while rook still had them */
7580                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7581                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7582                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7583                                    rights++; /* but at least one rook lost them */
7584                         }
7585                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7586                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7587                                 rights++;
7588                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7589                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7590                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7591                                    rights++;
7592                         }
7593                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7594                             && appData.drawRepeats > 1) {
7595                              /* adjudicate after user-specified nr of repeats */
7596                              int result = GameIsDrawn;
7597                              char *details = "XBoard adjudication: repetition draw";
7598                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7599                                 // [HGM] xiangqi: check for forbidden perpetuals
7600                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7601                                 for(m=forwardMostMove; m>k; m-=2) {
7602                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7603                                         ourPerpetual = 0; // the current mover did not always check
7604                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7605                                         hisPerpetual = 0; // the opponent did not always check
7606                                 }
7607                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7608                                                                         ourPerpetual, hisPerpetual);
7609                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7610                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7611                                     details = "Xboard adjudication: perpetual checking";
7612                                 } else
7613                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7614                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7615                                 } else
7616                                 // Now check for perpetual chases
7617                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7618                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7619                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7620                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7621                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7622                                         details = "Xboard adjudication: perpetual chasing";
7623                                     } else
7624                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7625                                         break; // Abort repetition-checking loop.
7626                                 }
7627                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7628                              }
7629                              if(engineOpponent) {
7630                                SendToProgram("force\n", engineOpponent); // suppress reply
7631                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7632                              }
7633                              GameEnds( result, details, GE_XBOARD );
7634                              return 1;
7635                         }
7636                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7637                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7638                     }
7639                 }
7640
7641                 /* Now we test for 50-move draws. Determine ply count */
7642                 count = forwardMostMove;
7643                 /* look for last irreversble move */
7644                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7645                     count--;
7646                 /* if we hit starting position, add initial plies */
7647                 if( count == backwardMostMove )
7648                     count -= initialRulePlies;
7649                 count = forwardMostMove - count;
7650                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7651                         // adjust reversible move counter for checks in Xiangqi
7652                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7653                         if(i < backwardMostMove) i = backwardMostMove;
7654                         while(i <= forwardMostMove) {
7655                                 lastCheck = inCheck; // check evasion does not count
7656                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7657                                 if(inCheck || lastCheck) count--; // check does not count
7658                                 i++;
7659                         }
7660                 }
7661                 if( count >= 100)
7662                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7663                          /* this is used to judge if draw claims are legal */
7664                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7665                          if(engineOpponent) {
7666                            SendToProgram("force\n", engineOpponent); // suppress reply
7667                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7668                          }
7669                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7670                          return 1;
7671                 }
7672
7673                 /* if draw offer is pending, treat it as a draw claim
7674                  * when draw condition present, to allow engines a way to
7675                  * claim draws before making their move to avoid a race
7676                  * condition occurring after their move
7677                  */
7678                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7679                          char *p = NULL;
7680                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7681                              p = "Draw claim: 50-move rule";
7682                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7683                              p = "Draw claim: 3-fold repetition";
7684                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7685                              p = "Draw claim: insufficient mating material";
7686                          if( p != NULL && canAdjudicate) {
7687                              if(engineOpponent) {
7688                                SendToProgram("force\n", engineOpponent); // suppress reply
7689                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7690                              }
7691                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7692                              return 1;
7693                          }
7694                 }
7695
7696                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7697                     if(engineOpponent) {
7698                       SendToProgram("force\n", engineOpponent); // suppress reply
7699                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7700                     }
7701                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7702                     return 1;
7703                 }
7704         return 0;
7705 }
7706
7707 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7708 {   // [HGM] book: this routine intercepts moves to simulate book replies
7709     char *bookHit = NULL;
7710
7711     //first determine if the incoming move brings opponent into his book
7712     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7713         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7714     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7715     if(bookHit != NULL && !cps->bookSuspend) {
7716         // make sure opponent is not going to reply after receiving move to book position
7717         SendToProgram("force\n", cps);
7718         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7719     }
7720     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7721     // now arrange restart after book miss
7722     if(bookHit) {
7723         // after a book hit we never send 'go', and the code after the call to this routine
7724         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7725         char buf[MSG_SIZ], *move = bookHit;
7726         if(cps->useSAN) {
7727             int fromX, fromY, toX, toY;
7728             char promoChar;
7729             ChessMove moveType;
7730             move = buf + 30;
7731             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7732                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7733                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7734                                     PosFlags(forwardMostMove),
7735                                     fromY, fromX, toY, toX, promoChar, move);
7736             } else {
7737                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7738                 bookHit = NULL;
7739             }
7740         }
7741         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7742         SendToProgram(buf, cps);
7743         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7744     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7745         SendToProgram("go\n", cps);
7746         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7747     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7748         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7749             SendToProgram("go\n", cps);
7750         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7751     }
7752     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7753 }
7754
7755 char *savedMessage;
7756 ChessProgramState *savedState;
7757 void DeferredBookMove(void)
7758 {
7759         if(savedState->lastPing != savedState->lastPong)
7760                     ScheduleDelayedEvent(DeferredBookMove, 10);
7761         else
7762         HandleMachineMove(savedMessage, savedState);
7763 }
7764
7765 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7766
7767 void
7768 HandleMachineMove(message, cps)
7769      char *message;
7770      ChessProgramState *cps;
7771 {
7772     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7773     char realname[MSG_SIZ];
7774     int fromX, fromY, toX, toY;
7775     ChessMove moveType;
7776     char promoChar;
7777     char *p, *pv=buf1;
7778     int machineWhite;
7779     char *bookHit;
7780
7781     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7782         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7783         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7784             DisplayError(_("Invalid pairing from pairing engine"), 0);
7785             return;
7786         }
7787         pairingReceived = 1;
7788         NextMatchGame();
7789         return; // Skim the pairing messages here.
7790     }
7791
7792     cps->userError = 0;
7793
7794 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7795     /*
7796      * Kludge to ignore BEL characters
7797      */
7798     while (*message == '\007') message++;
7799
7800     /*
7801      * [HGM] engine debug message: ignore lines starting with '#' character
7802      */
7803     if(cps->debug && *message == '#') return;
7804
7805     /*
7806      * Look for book output
7807      */
7808     if (cps == &first && bookRequested) {
7809         if (message[0] == '\t' || message[0] == ' ') {
7810             /* Part of the book output is here; append it */
7811             strcat(bookOutput, message);
7812             strcat(bookOutput, "  \n");
7813             return;
7814         } else if (bookOutput[0] != NULLCHAR) {
7815             /* All of book output has arrived; display it */
7816             char *p = bookOutput;
7817             while (*p != NULLCHAR) {
7818                 if (*p == '\t') *p = ' ';
7819                 p++;
7820             }
7821             DisplayInformation(bookOutput);
7822             bookRequested = FALSE;
7823             /* Fall through to parse the current output */
7824         }
7825     }
7826
7827     /*
7828      * Look for machine move.
7829      */
7830     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7831         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7832     {
7833         /* This method is only useful on engines that support ping */
7834         if (cps->lastPing != cps->lastPong) {
7835           if (gameMode == BeginningOfGame) {
7836             /* Extra move from before last new; ignore */
7837             if (appData.debugMode) {
7838                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7839             }
7840           } else {
7841             if (appData.debugMode) {
7842                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7843                         cps->which, gameMode);
7844             }
7845
7846             SendToProgram("undo\n", cps);
7847           }
7848           return;
7849         }
7850
7851         switch (gameMode) {
7852           case BeginningOfGame:
7853             /* Extra move from before last reset; ignore */
7854             if (appData.debugMode) {
7855                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7856             }
7857             return;
7858
7859           case EndOfGame:
7860           case IcsIdle:
7861           default:
7862             /* Extra move after we tried to stop.  The mode test is
7863                not a reliable way of detecting this problem, but it's
7864                the best we can do on engines that don't support ping.
7865             */
7866             if (appData.debugMode) {
7867                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7868                         cps->which, gameMode);
7869             }
7870             SendToProgram("undo\n", cps);
7871             return;
7872
7873           case MachinePlaysWhite:
7874           case IcsPlayingWhite:
7875             machineWhite = TRUE;
7876             break;
7877
7878           case MachinePlaysBlack:
7879           case IcsPlayingBlack:
7880             machineWhite = FALSE;
7881             break;
7882
7883           case TwoMachinesPlay:
7884             machineWhite = (cps->twoMachinesColor[0] == 'w');
7885             break;
7886         }
7887         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7888             if (appData.debugMode) {
7889                 fprintf(debugFP,
7890                         "Ignoring move out of turn by %s, gameMode %d"
7891                         ", forwardMost %d\n",
7892                         cps->which, gameMode, forwardMostMove);
7893             }
7894             return;
7895         }
7896
7897     if (appData.debugMode) { int f = forwardMostMove;
7898         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7899                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7900                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7901     }
7902         if(cps->alphaRank) AlphaRank(machineMove, 4);
7903         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7904                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7905             /* Machine move could not be parsed; ignore it. */
7906           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7907                     machineMove, _(cps->which));
7908             DisplayError(buf1, 0);
7909             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7910                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7911             if (gameMode == TwoMachinesPlay) {
7912               GameEnds(machineWhite ? BlackWins : WhiteWins,
7913                        buf1, GE_XBOARD);
7914             }
7915             return;
7916         }
7917
7918         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7919         /* So we have to redo legality test with true e.p. status here,  */
7920         /* to make sure an illegal e.p. capture does not slip through,   */
7921         /* to cause a forfeit on a justified illegal-move complaint      */
7922         /* of the opponent.                                              */
7923         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7924            ChessMove moveType;
7925            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7926                              fromY, fromX, toY, toX, promoChar);
7927             if (appData.debugMode) {
7928                 int i;
7929                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7930                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7931                 fprintf(debugFP, "castling rights\n");
7932             }
7933             if(moveType == IllegalMove) {
7934               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7935                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7936                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7937                            buf1, GE_XBOARD);
7938                 return;
7939            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7940            /* [HGM] Kludge to handle engines that send FRC-style castling
7941               when they shouldn't (like TSCP-Gothic) */
7942            switch(moveType) {
7943              case WhiteASideCastleFR:
7944              case BlackASideCastleFR:
7945                toX+=2;
7946                currentMoveString[2]++;
7947                break;
7948              case WhiteHSideCastleFR:
7949              case BlackHSideCastleFR:
7950                toX--;
7951                currentMoveString[2]--;
7952                break;
7953              default: ; // nothing to do, but suppresses warning of pedantic compilers
7954            }
7955         }
7956         hintRequested = FALSE;
7957         lastHint[0] = NULLCHAR;
7958         bookRequested = FALSE;
7959         /* Program may be pondering now */
7960         cps->maybeThinking = TRUE;
7961         if (cps->sendTime == 2) cps->sendTime = 1;
7962         if (cps->offeredDraw) cps->offeredDraw--;
7963
7964         /* [AS] Save move info*/
7965         pvInfoList[ forwardMostMove ].score = programStats.score;
7966         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7967         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7968
7969         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7970
7971         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7972         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7973             int count = 0;
7974
7975             while( count < adjudicateLossPlies ) {
7976                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7977
7978                 if( count & 1 ) {
7979                     score = -score; /* Flip score for winning side */
7980                 }
7981
7982                 if( score > adjudicateLossThreshold ) {
7983                     break;
7984                 }
7985
7986                 count++;
7987             }
7988
7989             if( count >= adjudicateLossPlies ) {
7990                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7991
7992                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7993                     "Xboard adjudication",
7994                     GE_XBOARD );
7995
7996                 return;
7997             }
7998         }
7999
8000         if(Adjudicate(cps)) {
8001             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8002             return; // [HGM] adjudicate: for all automatic game ends
8003         }
8004
8005 #if ZIPPY
8006         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8007             first.initDone) {
8008           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8009                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8010                 SendToICS("draw ");
8011                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8012           }
8013           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8014           ics_user_moved = 1;
8015           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8016                 char buf[3*MSG_SIZ];
8017
8018                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8019                         programStats.score / 100.,
8020                         programStats.depth,
8021                         programStats.time / 100.,
8022                         (unsigned int)programStats.nodes,
8023                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8024                         programStats.movelist);
8025                 SendToICS(buf);
8026 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8027           }
8028         }
8029 #endif
8030
8031         /* [AS] Clear stats for next move */
8032         ClearProgramStats();
8033         thinkOutput[0] = NULLCHAR;
8034         hiddenThinkOutputState = 0;
8035
8036         bookHit = NULL;
8037         if (gameMode == TwoMachinesPlay) {
8038             /* [HGM] relaying draw offers moved to after reception of move */
8039             /* and interpreting offer as claim if it brings draw condition */
8040             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8041                 SendToProgram("draw\n", cps->other);
8042             }
8043             if (cps->other->sendTime) {
8044                 SendTimeRemaining(cps->other,
8045                                   cps->other->twoMachinesColor[0] == 'w');
8046             }
8047             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8048             if (firstMove && !bookHit) {
8049                 firstMove = FALSE;
8050                 if (cps->other->useColors) {
8051                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8052                 }
8053                 SendToProgram("go\n", cps->other);
8054             }
8055             cps->other->maybeThinking = TRUE;
8056         }
8057
8058         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8059
8060         if (!pausing && appData.ringBellAfterMoves) {
8061             RingBell();
8062         }
8063
8064         /*
8065          * Reenable menu items that were disabled while
8066          * machine was thinking
8067          */
8068         if (gameMode != TwoMachinesPlay)
8069             SetUserThinkingEnables();
8070
8071         // [HGM] book: after book hit opponent has received move and is now in force mode
8072         // force the book reply into it, and then fake that it outputted this move by jumping
8073         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8074         if(bookHit) {
8075                 static char bookMove[MSG_SIZ]; // a bit generous?
8076
8077                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8078                 strcat(bookMove, bookHit);
8079                 message = bookMove;
8080                 cps = cps->other;
8081                 programStats.nodes = programStats.depth = programStats.time =
8082                 programStats.score = programStats.got_only_move = 0;
8083                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8084
8085                 if(cps->lastPing != cps->lastPong) {
8086                     savedMessage = message; // args for deferred call
8087                     savedState = cps;
8088                     ScheduleDelayedEvent(DeferredBookMove, 10);
8089                     return;
8090                 }
8091                 goto FakeBookMove;
8092         }
8093
8094         return;
8095     }
8096
8097     /* Set special modes for chess engines.  Later something general
8098      *  could be added here; for now there is just one kludge feature,
8099      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8100      *  when "xboard" is given as an interactive command.
8101      */
8102     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8103         cps->useSigint = FALSE;
8104         cps->useSigterm = FALSE;
8105     }
8106     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8107       ParseFeatures(message+8, cps);
8108       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8109     }
8110
8111     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8112       int dummy, s=6; char buf[MSG_SIZ];
8113       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8114       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8115       ParseFEN(boards[0], &dummy, message+s);
8116       DrawPosition(TRUE, boards[0]);
8117       startedFromSetupPosition = TRUE;
8118       return;
8119     }
8120     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8121      * want this, I was asked to put it in, and obliged.
8122      */
8123     if (!strncmp(message, "setboard ", 9)) {
8124         Board initial_position;
8125
8126         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8127
8128         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8129             DisplayError(_("Bad FEN received from engine"), 0);
8130             return ;
8131         } else {
8132            Reset(TRUE, FALSE);
8133            CopyBoard(boards[0], initial_position);
8134            initialRulePlies = FENrulePlies;
8135            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8136            else gameMode = MachinePlaysBlack;
8137            DrawPosition(FALSE, boards[currentMove]);
8138         }
8139         return;
8140     }
8141
8142     /*
8143      * Look for communication commands
8144      */
8145     if (!strncmp(message, "telluser ", 9)) {
8146         if(message[9] == '\\' && message[10] == '\\')
8147             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8148         PlayTellSound();
8149         DisplayNote(message + 9);
8150         return;
8151     }
8152     if (!strncmp(message, "tellusererror ", 14)) {
8153         cps->userError = 1;
8154         if(message[14] == '\\' && message[15] == '\\')
8155             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8156         PlayTellSound();
8157         DisplayError(message + 14, 0);
8158         return;
8159     }
8160     if (!strncmp(message, "tellopponent ", 13)) {
8161       if (appData.icsActive) {
8162         if (loggedOn) {
8163           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8164           SendToICS(buf1);
8165         }
8166       } else {
8167         DisplayNote(message + 13);
8168       }
8169       return;
8170     }
8171     if (!strncmp(message, "tellothers ", 11)) {
8172       if (appData.icsActive) {
8173         if (loggedOn) {
8174           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8175           SendToICS(buf1);
8176         }
8177       }
8178       return;
8179     }
8180     if (!strncmp(message, "tellall ", 8)) {
8181       if (appData.icsActive) {
8182         if (loggedOn) {
8183           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8184           SendToICS(buf1);
8185         }
8186       } else {
8187         DisplayNote(message + 8);
8188       }
8189       return;
8190     }
8191     if (strncmp(message, "warning", 7) == 0) {
8192         /* Undocumented feature, use tellusererror in new code */
8193         DisplayError(message, 0);
8194         return;
8195     }
8196     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8197         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8198         strcat(realname, " query");
8199         AskQuestion(realname, buf2, buf1, cps->pr);
8200         return;
8201     }
8202     /* Commands from the engine directly to ICS.  We don't allow these to be
8203      *  sent until we are logged on. Crafty kibitzes have been known to
8204      *  interfere with the login process.
8205      */
8206     if (loggedOn) {
8207         if (!strncmp(message, "tellics ", 8)) {
8208             SendToICS(message + 8);
8209             SendToICS("\n");
8210             return;
8211         }
8212         if (!strncmp(message, "tellicsnoalias ", 15)) {
8213             SendToICS(ics_prefix);
8214             SendToICS(message + 15);
8215             SendToICS("\n");
8216             return;
8217         }
8218         /* The following are for backward compatibility only */
8219         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8220             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8221             SendToICS(ics_prefix);
8222             SendToICS(message);
8223             SendToICS("\n");
8224             return;
8225         }
8226     }
8227     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8228         return;
8229     }
8230     /*
8231      * If the move is illegal, cancel it and redraw the board.
8232      * Also deal with other error cases.  Matching is rather loose
8233      * here to accommodate engines written before the spec.
8234      */
8235     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8236         strncmp(message, "Error", 5) == 0) {
8237         if (StrStr(message, "name") ||
8238             StrStr(message, "rating") || StrStr(message, "?") ||
8239             StrStr(message, "result") || StrStr(message, "board") ||
8240             StrStr(message, "bk") || StrStr(message, "computer") ||
8241             StrStr(message, "variant") || StrStr(message, "hint") ||
8242             StrStr(message, "random") || StrStr(message, "depth") ||
8243             StrStr(message, "accepted")) {
8244             return;
8245         }
8246         if (StrStr(message, "protover")) {
8247           /* Program is responding to input, so it's apparently done
8248              initializing, and this error message indicates it is
8249              protocol version 1.  So we don't need to wait any longer
8250              for it to initialize and send feature commands. */
8251           FeatureDone(cps, 1);
8252           cps->protocolVersion = 1;
8253           return;
8254         }
8255         cps->maybeThinking = FALSE;
8256
8257         if (StrStr(message, "draw")) {
8258             /* Program doesn't have "draw" command */
8259             cps->sendDrawOffers = 0;
8260             return;
8261         }
8262         if (cps->sendTime != 1 &&
8263             (StrStr(message, "time") || StrStr(message, "otim"))) {
8264           /* Program apparently doesn't have "time" or "otim" command */
8265           cps->sendTime = 0;
8266           return;
8267         }
8268         if (StrStr(message, "analyze")) {
8269             cps->analysisSupport = FALSE;
8270             cps->analyzing = FALSE;
8271 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8272             EditGameEvent(); // [HGM] try to preserve loaded game
8273             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8274             DisplayError(buf2, 0);
8275             return;
8276         }
8277         if (StrStr(message, "(no matching move)st")) {
8278           /* Special kludge for GNU Chess 4 only */
8279           cps->stKludge = TRUE;
8280           SendTimeControl(cps, movesPerSession, timeControl,
8281                           timeIncrement, appData.searchDepth,
8282                           searchTime);
8283           return;
8284         }
8285         if (StrStr(message, "(no matching move)sd")) {
8286           /* Special kludge for GNU Chess 4 only */
8287           cps->sdKludge = TRUE;
8288           SendTimeControl(cps, movesPerSession, timeControl,
8289                           timeIncrement, appData.searchDepth,
8290                           searchTime);
8291           return;
8292         }
8293         if (!StrStr(message, "llegal")) {
8294             return;
8295         }
8296         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8297             gameMode == IcsIdle) return;
8298         if (forwardMostMove <= backwardMostMove) return;
8299         if (pausing) PauseEvent();
8300       if(appData.forceIllegal) {
8301             // [HGM] illegal: machine refused move; force position after move into it
8302           SendToProgram("force\n", cps);
8303           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8304                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8305                 // when black is to move, while there might be nothing on a2 or black
8306                 // might already have the move. So send the board as if white has the move.
8307                 // But first we must change the stm of the engine, as it refused the last move
8308                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8309                 if(WhiteOnMove(forwardMostMove)) {
8310                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8311                     SendBoard(cps, forwardMostMove); // kludgeless board
8312                 } else {
8313                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8314                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8315                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8316                 }
8317           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8318             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8319                  gameMode == TwoMachinesPlay)
8320               SendToProgram("go\n", cps);
8321             return;
8322       } else
8323         if (gameMode == PlayFromGameFile) {
8324             /* Stop reading this game file */
8325             gameMode = EditGame;
8326             ModeHighlight();
8327         }
8328         /* [HGM] illegal-move claim should forfeit game when Xboard */
8329         /* only passes fully legal moves                            */
8330         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8331             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8332                                 "False illegal-move claim", GE_XBOARD );
8333             return; // do not take back move we tested as valid
8334         }
8335         currentMove = forwardMostMove-1;
8336         DisplayMove(currentMove-1); /* before DisplayMoveError */
8337         SwitchClocks(forwardMostMove-1); // [HGM] race
8338         DisplayBothClocks();
8339         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8340                 parseList[currentMove], _(cps->which));
8341         DisplayMoveError(buf1);
8342         DrawPosition(FALSE, boards[currentMove]);
8343         return;
8344     }
8345     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8346         /* Program has a broken "time" command that
8347            outputs a string not ending in newline.
8348            Don't use it. */
8349         cps->sendTime = 0;
8350     }
8351
8352     /*
8353      * If chess program startup fails, exit with an error message.
8354      * Attempts to recover here are futile.
8355      */
8356     if ((StrStr(message, "unknown host") != NULL)
8357         || (StrStr(message, "No remote directory") != NULL)
8358         || (StrStr(message, "not found") != NULL)
8359         || (StrStr(message, "No such file") != NULL)
8360         || (StrStr(message, "can't alloc") != NULL)
8361         || (StrStr(message, "Permission denied") != NULL)) {
8362
8363         cps->maybeThinking = FALSE;
8364         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8365                 _(cps->which), cps->program, cps->host, message);
8366         RemoveInputSource(cps->isr);
8367         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8368             if(cps == &first) appData.noChessProgram = TRUE;
8369             DisplayError(buf1, 0);
8370         }
8371         return;
8372     }
8373
8374     /*
8375      * Look for hint output
8376      */
8377     if (sscanf(message, "Hint: %s", buf1) == 1) {
8378         if (cps == &first && hintRequested) {
8379             hintRequested = FALSE;
8380             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8381                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8382                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8383                                     PosFlags(forwardMostMove),
8384                                     fromY, fromX, toY, toX, promoChar, buf1);
8385                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8386                 DisplayInformation(buf2);
8387             } else {
8388                 /* Hint move could not be parsed!? */
8389               snprintf(buf2, sizeof(buf2),
8390                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8391                         buf1, _(cps->which));
8392                 DisplayError(buf2, 0);
8393             }
8394         } else {
8395           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8396         }
8397         return;
8398     }
8399
8400     /*
8401      * Ignore other messages if game is not in progress
8402      */
8403     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8404         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8405
8406     /*
8407      * look for win, lose, draw, or draw offer
8408      */
8409     if (strncmp(message, "1-0", 3) == 0) {
8410         char *p, *q, *r = "";
8411         p = strchr(message, '{');
8412         if (p) {
8413             q = strchr(p, '}');
8414             if (q) {
8415                 *q = NULLCHAR;
8416                 r = p + 1;
8417             }
8418         }
8419         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8420         return;
8421     } else if (strncmp(message, "0-1", 3) == 0) {
8422         char *p, *q, *r = "";
8423         p = strchr(message, '{');
8424         if (p) {
8425             q = strchr(p, '}');
8426             if (q) {
8427                 *q = NULLCHAR;
8428                 r = p + 1;
8429             }
8430         }
8431         /* Kludge for Arasan 4.1 bug */
8432         if (strcmp(r, "Black resigns") == 0) {
8433             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8434             return;
8435         }
8436         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8437         return;
8438     } else if (strncmp(message, "1/2", 3) == 0) {
8439         char *p, *q, *r = "";
8440         p = strchr(message, '{');
8441         if (p) {
8442             q = strchr(p, '}');
8443             if (q) {
8444                 *q = NULLCHAR;
8445                 r = p + 1;
8446             }
8447         }
8448
8449         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8450         return;
8451
8452     } else if (strncmp(message, "White resign", 12) == 0) {
8453         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8454         return;
8455     } else if (strncmp(message, "Black resign", 12) == 0) {
8456         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8457         return;
8458     } else if (strncmp(message, "White matches", 13) == 0 ||
8459                strncmp(message, "Black matches", 13) == 0   ) {
8460         /* [HGM] ignore GNUShogi noises */
8461         return;
8462     } else if (strncmp(message, "White", 5) == 0 &&
8463                message[5] != '(' &&
8464                StrStr(message, "Black") == NULL) {
8465         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8466         return;
8467     } else if (strncmp(message, "Black", 5) == 0 &&
8468                message[5] != '(') {
8469         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8470         return;
8471     } else if (strcmp(message, "resign") == 0 ||
8472                strcmp(message, "computer resigns") == 0) {
8473         switch (gameMode) {
8474           case MachinePlaysBlack:
8475           case IcsPlayingBlack:
8476             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8477             break;
8478           case MachinePlaysWhite:
8479           case IcsPlayingWhite:
8480             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8481             break;
8482           case TwoMachinesPlay:
8483             if (cps->twoMachinesColor[0] == 'w')
8484               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8485             else
8486               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8487             break;
8488           default:
8489             /* can't happen */
8490             break;
8491         }
8492         return;
8493     } else if (strncmp(message, "opponent mates", 14) == 0) {
8494         switch (gameMode) {
8495           case MachinePlaysBlack:
8496           case IcsPlayingBlack:
8497             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8498             break;
8499           case MachinePlaysWhite:
8500           case IcsPlayingWhite:
8501             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8502             break;
8503           case TwoMachinesPlay:
8504             if (cps->twoMachinesColor[0] == 'w')
8505               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8506             else
8507               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8508             break;
8509           default:
8510             /* can't happen */
8511             break;
8512         }
8513         return;
8514     } else if (strncmp(message, "computer mates", 14) == 0) {
8515         switch (gameMode) {
8516           case MachinePlaysBlack:
8517           case IcsPlayingBlack:
8518             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8519             break;
8520           case MachinePlaysWhite:
8521           case IcsPlayingWhite:
8522             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8523             break;
8524           case TwoMachinesPlay:
8525             if (cps->twoMachinesColor[0] == 'w')
8526               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8527             else
8528               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8529             break;
8530           default:
8531             /* can't happen */
8532             break;
8533         }
8534         return;
8535     } else if (strncmp(message, "checkmate", 9) == 0) {
8536         if (WhiteOnMove(forwardMostMove)) {
8537             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8538         } else {
8539             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8540         }
8541         return;
8542     } else if (strstr(message, "Draw") != NULL ||
8543                strstr(message, "game is a draw") != NULL) {
8544         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8545         return;
8546     } else if (strstr(message, "offer") != NULL &&
8547                strstr(message, "draw") != NULL) {
8548 #if ZIPPY
8549         if (appData.zippyPlay && first.initDone) {
8550             /* Relay offer to ICS */
8551             SendToICS(ics_prefix);
8552             SendToICS("draw\n");
8553         }
8554 #endif
8555         cps->offeredDraw = 2; /* valid until this engine moves twice */
8556         if (gameMode == TwoMachinesPlay) {
8557             if (cps->other->offeredDraw) {
8558                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8559             /* [HGM] in two-machine mode we delay relaying draw offer      */
8560             /* until after we also have move, to see if it is really claim */
8561             }
8562         } else if (gameMode == MachinePlaysWhite ||
8563                    gameMode == MachinePlaysBlack) {
8564           if (userOfferedDraw) {
8565             DisplayInformation(_("Machine accepts your draw offer"));
8566             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8567           } else {
8568             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8569           }
8570         }
8571     }
8572
8573
8574     /*
8575      * Look for thinking output
8576      */
8577     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8578           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8579                                 ) {
8580         int plylev, mvleft, mvtot, curscore, time;
8581         char mvname[MOVE_LEN];
8582         u64 nodes; // [DM]
8583         char plyext;
8584         int ignore = FALSE;
8585         int prefixHint = FALSE;
8586         mvname[0] = NULLCHAR;
8587
8588         switch (gameMode) {
8589           case MachinePlaysBlack:
8590           case IcsPlayingBlack:
8591             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8592             break;
8593           case MachinePlaysWhite:
8594           case IcsPlayingWhite:
8595             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8596             break;
8597           case AnalyzeMode:
8598           case AnalyzeFile:
8599             break;
8600           case IcsObserving: /* [DM] icsEngineAnalyze */
8601             if (!appData.icsEngineAnalyze) ignore = TRUE;
8602             break;
8603           case TwoMachinesPlay:
8604             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8605                 ignore = TRUE;
8606             }
8607             break;
8608           default:
8609             ignore = TRUE;
8610             break;
8611         }
8612
8613         if (!ignore) {
8614             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8615             buf1[0] = NULLCHAR;
8616             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8617                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8618
8619                 if (plyext != ' ' && plyext != '\t') {
8620                     time *= 100;
8621                 }
8622
8623                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8624                 if( cps->scoreIsAbsolute &&
8625                     ( gameMode == MachinePlaysBlack ||
8626                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8627                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8628                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8629                      !WhiteOnMove(currentMove)
8630                     ) )
8631                 {
8632                     curscore = -curscore;
8633                 }
8634
8635                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8636
8637                 tempStats.depth = plylev;
8638                 tempStats.nodes = nodes;
8639                 tempStats.time = time;
8640                 tempStats.score = curscore;
8641                 tempStats.got_only_move = 0;
8642
8643                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8644                         int ticklen;
8645
8646                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8647                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8648                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8649                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8650                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8651                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8652                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8653                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8654                 }
8655
8656                 /* Buffer overflow protection */
8657                 if (pv[0] != NULLCHAR) {
8658                     if (strlen(pv) >= sizeof(tempStats.movelist)
8659                         && appData.debugMode) {
8660                         fprintf(debugFP,
8661                                 "PV is too long; using the first %u bytes.\n",
8662                                 (unsigned) sizeof(tempStats.movelist) - 1);
8663                     }
8664
8665                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8666                 } else {
8667                     sprintf(tempStats.movelist, " no PV\n");
8668                 }
8669
8670                 if (tempStats.seen_stat) {
8671                     tempStats.ok_to_send = 1;
8672                 }
8673
8674                 if (strchr(tempStats.movelist, '(') != NULL) {
8675                     tempStats.line_is_book = 1;
8676                     tempStats.nr_moves = 0;
8677                     tempStats.moves_left = 0;
8678                 } else {
8679                     tempStats.line_is_book = 0;
8680                 }
8681
8682                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8683                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8684
8685                 SendProgramStatsToFrontend( cps, &tempStats );
8686
8687                 /*
8688                     [AS] Protect the thinkOutput buffer from overflow... this
8689                     is only useful if buf1 hasn't overflowed first!
8690                 */
8691                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8692                          plylev,
8693                          (gameMode == TwoMachinesPlay ?
8694                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8695                          ((double) curscore) / 100.0,
8696                          prefixHint ? lastHint : "",
8697                          prefixHint ? " " : "" );
8698
8699                 if( buf1[0] != NULLCHAR ) {
8700                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8701
8702                     if( strlen(pv) > max_len ) {
8703                         if( appData.debugMode) {
8704                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8705                         }
8706                         pv[max_len+1] = '\0';
8707                     }
8708
8709                     strcat( thinkOutput, pv);
8710                 }
8711
8712                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8713                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8714                     DisplayMove(currentMove - 1);
8715                 }
8716                 return;
8717
8718             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8719                 /* crafty (9.25+) says "(only move) <move>"
8720                  * if there is only 1 legal move
8721                  */
8722                 sscanf(p, "(only move) %s", buf1);
8723                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8724                 sprintf(programStats.movelist, "%s (only move)", buf1);
8725                 programStats.depth = 1;
8726                 programStats.nr_moves = 1;
8727                 programStats.moves_left = 1;
8728                 programStats.nodes = 1;
8729                 programStats.time = 1;
8730                 programStats.got_only_move = 1;
8731
8732                 /* Not really, but we also use this member to
8733                    mean "line isn't going to change" (Crafty
8734                    isn't searching, so stats won't change) */
8735                 programStats.line_is_book = 1;
8736
8737                 SendProgramStatsToFrontend( cps, &programStats );
8738
8739                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8740                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8741                     DisplayMove(currentMove - 1);
8742                 }
8743                 return;
8744             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8745                               &time, &nodes, &plylev, &mvleft,
8746                               &mvtot, mvname) >= 5) {
8747                 /* The stat01: line is from Crafty (9.29+) in response
8748                    to the "." command */
8749                 programStats.seen_stat = 1;
8750                 cps->maybeThinking = TRUE;
8751
8752                 if (programStats.got_only_move || !appData.periodicUpdates)
8753                   return;
8754
8755                 programStats.depth = plylev;
8756                 programStats.time = time;
8757                 programStats.nodes = nodes;
8758                 programStats.moves_left = mvleft;
8759                 programStats.nr_moves = mvtot;
8760                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8761                 programStats.ok_to_send = 1;
8762                 programStats.movelist[0] = '\0';
8763
8764                 SendProgramStatsToFrontend( cps, &programStats );
8765
8766                 return;
8767
8768             } else if (strncmp(message,"++",2) == 0) {
8769                 /* Crafty 9.29+ outputs this */
8770                 programStats.got_fail = 2;
8771                 return;
8772
8773             } else if (strncmp(message,"--",2) == 0) {
8774                 /* Crafty 9.29+ outputs this */
8775                 programStats.got_fail = 1;
8776                 return;
8777
8778             } else if (thinkOutput[0] != NULLCHAR &&
8779                        strncmp(message, "    ", 4) == 0) {
8780                 unsigned message_len;
8781
8782                 p = message;
8783                 while (*p && *p == ' ') p++;
8784
8785                 message_len = strlen( p );
8786
8787                 /* [AS] Avoid buffer overflow */
8788                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8789                     strcat(thinkOutput, " ");
8790                     strcat(thinkOutput, p);
8791                 }
8792
8793                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8794                     strcat(programStats.movelist, " ");
8795                     strcat(programStats.movelist, p);
8796                 }
8797
8798                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8799                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8800                     DisplayMove(currentMove - 1);
8801                 }
8802                 return;
8803             }
8804         }
8805         else {
8806             buf1[0] = NULLCHAR;
8807
8808             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8809                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8810             {
8811                 ChessProgramStats cpstats;
8812
8813                 if (plyext != ' ' && plyext != '\t') {
8814                     time *= 100;
8815                 }
8816
8817                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8818                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8819                     curscore = -curscore;
8820                 }
8821
8822                 cpstats.depth = plylev;
8823                 cpstats.nodes = nodes;
8824                 cpstats.time = time;
8825                 cpstats.score = curscore;
8826                 cpstats.got_only_move = 0;
8827                 cpstats.movelist[0] = '\0';
8828
8829                 if (buf1[0] != NULLCHAR) {
8830                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8831                 }
8832
8833                 cpstats.ok_to_send = 0;
8834                 cpstats.line_is_book = 0;
8835                 cpstats.nr_moves = 0;
8836                 cpstats.moves_left = 0;
8837
8838                 SendProgramStatsToFrontend( cps, &cpstats );
8839             }
8840         }
8841     }
8842 }
8843
8844
8845 /* Parse a game score from the character string "game", and
8846    record it as the history of the current game.  The game
8847    score is NOT assumed to start from the standard position.
8848    The display is not updated in any way.
8849    */
8850 void
8851 ParseGameHistory(game)
8852      char *game;
8853 {
8854     ChessMove moveType;
8855     int fromX, fromY, toX, toY, boardIndex;
8856     char promoChar;
8857     char *p, *q;
8858     char buf[MSG_SIZ];
8859
8860     if (appData.debugMode)
8861       fprintf(debugFP, "Parsing game history: %s\n", game);
8862
8863     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8864     gameInfo.site = StrSave(appData.icsHost);
8865     gameInfo.date = PGNDate();
8866     gameInfo.round = StrSave("-");
8867
8868     /* Parse out names of players */
8869     while (*game == ' ') game++;
8870     p = buf;
8871     while (*game != ' ') *p++ = *game++;
8872     *p = NULLCHAR;
8873     gameInfo.white = StrSave(buf);
8874     while (*game == ' ') game++;
8875     p = buf;
8876     while (*game != ' ' && *game != '\n') *p++ = *game++;
8877     *p = NULLCHAR;
8878     gameInfo.black = StrSave(buf);
8879
8880     /* Parse moves */
8881     boardIndex = blackPlaysFirst ? 1 : 0;
8882     yynewstr(game);
8883     for (;;) {
8884         yyboardindex = boardIndex;
8885         moveType = (ChessMove) Myylex();
8886         switch (moveType) {
8887           case IllegalMove:             /* maybe suicide chess, etc. */
8888   if (appData.debugMode) {
8889     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8890     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8891     setbuf(debugFP, NULL);
8892   }
8893           case WhitePromotion:
8894           case BlackPromotion:
8895           case WhiteNonPromotion:
8896           case BlackNonPromotion:
8897           case NormalMove:
8898           case WhiteCapturesEnPassant:
8899           case BlackCapturesEnPassant:
8900           case WhiteKingSideCastle:
8901           case WhiteQueenSideCastle:
8902           case BlackKingSideCastle:
8903           case BlackQueenSideCastle:
8904           case WhiteKingSideCastleWild:
8905           case WhiteQueenSideCastleWild:
8906           case BlackKingSideCastleWild:
8907           case BlackQueenSideCastleWild:
8908           /* PUSH Fabien */
8909           case WhiteHSideCastleFR:
8910           case WhiteASideCastleFR:
8911           case BlackHSideCastleFR:
8912           case BlackASideCastleFR:
8913           /* POP Fabien */
8914             fromX = currentMoveString[0] - AAA;
8915             fromY = currentMoveString[1] - ONE;
8916             toX = currentMoveString[2] - AAA;
8917             toY = currentMoveString[3] - ONE;
8918             promoChar = currentMoveString[4];
8919             break;
8920           case WhiteDrop:
8921           case BlackDrop:
8922             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8923             fromX = moveType == WhiteDrop ?
8924               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8925             (int) CharToPiece(ToLower(currentMoveString[0]));
8926             fromY = DROP_RANK;
8927             toX = currentMoveString[2] - AAA;
8928             toY = currentMoveString[3] - ONE;
8929             promoChar = NULLCHAR;
8930             break;
8931           case AmbiguousMove:
8932             /* bug? */
8933             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8934   if (appData.debugMode) {
8935     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8936     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8937     setbuf(debugFP, NULL);
8938   }
8939             DisplayError(buf, 0);
8940             return;
8941           case ImpossibleMove:
8942             /* bug? */
8943             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8944   if (appData.debugMode) {
8945     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8946     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8947     setbuf(debugFP, NULL);
8948   }
8949             DisplayError(buf, 0);
8950             return;
8951           case EndOfFile:
8952             if (boardIndex < backwardMostMove) {
8953                 /* Oops, gap.  How did that happen? */
8954                 DisplayError(_("Gap in move list"), 0);
8955                 return;
8956             }
8957             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8958             if (boardIndex > forwardMostMove) {
8959                 forwardMostMove = boardIndex;
8960             }
8961             return;
8962           case ElapsedTime:
8963             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8964                 strcat(parseList[boardIndex-1], " ");
8965                 strcat(parseList[boardIndex-1], yy_text);
8966             }
8967             continue;
8968           case Comment:
8969           case PGNTag:
8970           case NAG:
8971           default:
8972             /* ignore */
8973             continue;
8974           case WhiteWins:
8975           case BlackWins:
8976           case GameIsDrawn:
8977           case GameUnfinished:
8978             if (gameMode == IcsExamining) {
8979                 if (boardIndex < backwardMostMove) {
8980                     /* Oops, gap.  How did that happen? */
8981                     return;
8982                 }
8983                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8984                 return;
8985             }
8986             gameInfo.result = moveType;
8987             p = strchr(yy_text, '{');
8988             if (p == NULL) p = strchr(yy_text, '(');
8989             if (p == NULL) {
8990                 p = yy_text;
8991                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8992             } else {
8993                 q = strchr(p, *p == '{' ? '}' : ')');
8994                 if (q != NULL) *q = NULLCHAR;
8995                 p++;
8996             }
8997             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8998             gameInfo.resultDetails = StrSave(p);
8999             continue;
9000         }
9001         if (boardIndex >= forwardMostMove &&
9002             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9003             backwardMostMove = blackPlaysFirst ? 1 : 0;
9004             return;
9005         }
9006         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9007                                  fromY, fromX, toY, toX, promoChar,
9008                                  parseList[boardIndex]);
9009         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9010         /* currentMoveString is set as a side-effect of yylex */
9011         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9012         strcat(moveList[boardIndex], "\n");
9013         boardIndex++;
9014         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9015         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9016           case MT_NONE:
9017           case MT_STALEMATE:
9018           default:
9019             break;
9020           case MT_CHECK:
9021             if(gameInfo.variant != VariantShogi)
9022                 strcat(parseList[boardIndex - 1], "+");
9023             break;
9024           case MT_CHECKMATE:
9025           case MT_STAINMATE:
9026             strcat(parseList[boardIndex - 1], "#");
9027             break;
9028         }
9029     }
9030 }
9031
9032
9033 /* Apply a move to the given board  */
9034 void
9035 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9036      int fromX, fromY, toX, toY;
9037      int promoChar;
9038      Board board;
9039 {
9040   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9041   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9042
9043     /* [HGM] compute & store e.p. status and castling rights for new position */
9044     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9045
9046       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9047       oldEP = (signed char)board[EP_STATUS];
9048       board[EP_STATUS] = EP_NONE;
9049
9050   if (fromY == DROP_RANK) {
9051         /* must be first */
9052         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9053             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9054             return;
9055         }
9056         piece = board[toY][toX] = (ChessSquare) fromX;
9057   } else {
9058       int i;
9059
9060       if( board[toY][toX] != EmptySquare )
9061            board[EP_STATUS] = EP_CAPTURE;
9062
9063       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9064            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9065                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9066       } else
9067       if( board[fromY][fromX] == WhitePawn ) {
9068            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9069                board[EP_STATUS] = EP_PAWN_MOVE;
9070            if( toY-fromY==2) {
9071                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9072                         gameInfo.variant != VariantBerolina || toX < fromX)
9073                       board[EP_STATUS] = toX | berolina;
9074                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9075                         gameInfo.variant != VariantBerolina || toX > fromX)
9076                       board[EP_STATUS] = toX;
9077            }
9078       } else
9079       if( board[fromY][fromX] == BlackPawn ) {
9080            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9081                board[EP_STATUS] = EP_PAWN_MOVE;
9082            if( toY-fromY== -2) {
9083                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9084                         gameInfo.variant != VariantBerolina || toX < fromX)
9085                       board[EP_STATUS] = toX | berolina;
9086                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9087                         gameInfo.variant != VariantBerolina || toX > fromX)
9088                       board[EP_STATUS] = toX;
9089            }
9090        }
9091
9092        for(i=0; i<nrCastlingRights; i++) {
9093            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9094               board[CASTLING][i] == toX   && castlingRank[i] == toY
9095              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9096        }
9097
9098      if (fromX == toX && fromY == toY) return;
9099
9100      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9101      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9102      if(gameInfo.variant == VariantKnightmate)
9103          king += (int) WhiteUnicorn - (int) WhiteKing;
9104
9105     /* Code added by Tord: */
9106     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9107     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9108         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9109       board[fromY][fromX] = EmptySquare;
9110       board[toY][toX] = EmptySquare;
9111       if((toX > fromX) != (piece == WhiteRook)) {
9112         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9113       } else {
9114         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9115       }
9116     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9117                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9118       board[fromY][fromX] = EmptySquare;
9119       board[toY][toX] = EmptySquare;
9120       if((toX > fromX) != (piece == BlackRook)) {
9121         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9122       } else {
9123         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9124       }
9125     /* End of code added by Tord */
9126
9127     } else if (board[fromY][fromX] == king
9128         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9129         && toY == fromY && toX > fromX+1) {
9130         board[fromY][fromX] = EmptySquare;
9131         board[toY][toX] = king;
9132         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9133         board[fromY][BOARD_RGHT-1] = EmptySquare;
9134     } else if (board[fromY][fromX] == king
9135         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9136                && toY == fromY && toX < fromX-1) {
9137         board[fromY][fromX] = EmptySquare;
9138         board[toY][toX] = king;
9139         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9140         board[fromY][BOARD_LEFT] = EmptySquare;
9141     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9142                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9143                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9144                ) {
9145         /* white pawn promotion */
9146         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9147         if(gameInfo.variant==VariantBughouse ||
9148            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9149             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9150         board[fromY][fromX] = EmptySquare;
9151     } else if ((fromY >= BOARD_HEIGHT>>1)
9152                && (toX != fromX)
9153                && gameInfo.variant != VariantXiangqi
9154                && gameInfo.variant != VariantBerolina
9155                && (board[fromY][fromX] == WhitePawn)
9156                && (board[toY][toX] == EmptySquare)) {
9157         board[fromY][fromX] = EmptySquare;
9158         board[toY][toX] = WhitePawn;
9159         captured = board[toY - 1][toX];
9160         board[toY - 1][toX] = EmptySquare;
9161     } else if ((fromY == BOARD_HEIGHT-4)
9162                && (toX == fromX)
9163                && gameInfo.variant == VariantBerolina
9164                && (board[fromY][fromX] == WhitePawn)
9165                && (board[toY][toX] == EmptySquare)) {
9166         board[fromY][fromX] = EmptySquare;
9167         board[toY][toX] = WhitePawn;
9168         if(oldEP & EP_BEROLIN_A) {
9169                 captured = board[fromY][fromX-1];
9170                 board[fromY][fromX-1] = EmptySquare;
9171         }else{  captured = board[fromY][fromX+1];
9172                 board[fromY][fromX+1] = EmptySquare;
9173         }
9174     } else if (board[fromY][fromX] == king
9175         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9176                && toY == fromY && toX > fromX+1) {
9177         board[fromY][fromX] = EmptySquare;
9178         board[toY][toX] = king;
9179         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9180         board[fromY][BOARD_RGHT-1] = EmptySquare;
9181     } else if (board[fromY][fromX] == king
9182         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9183                && toY == fromY && toX < fromX-1) {
9184         board[fromY][fromX] = EmptySquare;
9185         board[toY][toX] = king;
9186         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9187         board[fromY][BOARD_LEFT] = EmptySquare;
9188     } else if (fromY == 7 && fromX == 3
9189                && board[fromY][fromX] == BlackKing
9190                && toY == 7 && toX == 5) {
9191         board[fromY][fromX] = EmptySquare;
9192         board[toY][toX] = BlackKing;
9193         board[fromY][7] = EmptySquare;
9194         board[toY][4] = BlackRook;
9195     } else if (fromY == 7 && fromX == 3
9196                && board[fromY][fromX] == BlackKing
9197                && toY == 7 && toX == 1) {
9198         board[fromY][fromX] = EmptySquare;
9199         board[toY][toX] = BlackKing;
9200         board[fromY][0] = EmptySquare;
9201         board[toY][2] = BlackRook;
9202     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9203                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9204                && toY < promoRank && promoChar
9205                ) {
9206         /* black pawn promotion */
9207         board[toY][toX] = CharToPiece(ToLower(promoChar));
9208         if(gameInfo.variant==VariantBughouse ||
9209            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9210             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9211         board[fromY][fromX] = EmptySquare;
9212     } else if ((fromY < BOARD_HEIGHT>>1)
9213                && (toX != fromX)
9214                && gameInfo.variant != VariantXiangqi
9215                && gameInfo.variant != VariantBerolina
9216                && (board[fromY][fromX] == BlackPawn)
9217                && (board[toY][toX] == EmptySquare)) {
9218         board[fromY][fromX] = EmptySquare;
9219         board[toY][toX] = BlackPawn;
9220         captured = board[toY + 1][toX];
9221         board[toY + 1][toX] = EmptySquare;
9222     } else if ((fromY == 3)
9223                && (toX == fromX)
9224                && gameInfo.variant == VariantBerolina
9225                && (board[fromY][fromX] == BlackPawn)
9226                && (board[toY][toX] == EmptySquare)) {
9227         board[fromY][fromX] = EmptySquare;
9228         board[toY][toX] = BlackPawn;
9229         if(oldEP & EP_BEROLIN_A) {
9230                 captured = board[fromY][fromX-1];
9231                 board[fromY][fromX-1] = EmptySquare;
9232         }else{  captured = board[fromY][fromX+1];
9233                 board[fromY][fromX+1] = EmptySquare;
9234         }
9235     } else {
9236         board[toY][toX] = board[fromY][fromX];
9237         board[fromY][fromX] = EmptySquare;
9238     }
9239   }
9240
9241     if (gameInfo.holdingsWidth != 0) {
9242
9243       /* !!A lot more code needs to be written to support holdings  */
9244       /* [HGM] OK, so I have written it. Holdings are stored in the */
9245       /* penultimate board files, so they are automaticlly stored   */
9246       /* in the game history.                                       */
9247       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9248                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9249         /* Delete from holdings, by decreasing count */
9250         /* and erasing image if necessary            */
9251         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9252         if(p < (int) BlackPawn) { /* white drop */
9253              p -= (int)WhitePawn;
9254                  p = PieceToNumber((ChessSquare)p);
9255              if(p >= gameInfo.holdingsSize) p = 0;
9256              if(--board[p][BOARD_WIDTH-2] <= 0)
9257                   board[p][BOARD_WIDTH-1] = EmptySquare;
9258              if((int)board[p][BOARD_WIDTH-2] < 0)
9259                         board[p][BOARD_WIDTH-2] = 0;
9260         } else {                  /* black drop */
9261              p -= (int)BlackPawn;
9262                  p = PieceToNumber((ChessSquare)p);
9263              if(p >= gameInfo.holdingsSize) p = 0;
9264              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9265                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9266              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9267                         board[BOARD_HEIGHT-1-p][1] = 0;
9268         }
9269       }
9270       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9271           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9272         /* [HGM] holdings: Add to holdings, if holdings exist */
9273         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9274                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9275                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9276         }
9277         p = (int) captured;
9278         if (p >= (int) BlackPawn) {
9279           p -= (int)BlackPawn;
9280           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9281                   /* in Shogi restore piece to its original  first */
9282                   captured = (ChessSquare) (DEMOTED captured);
9283                   p = DEMOTED p;
9284           }
9285           p = PieceToNumber((ChessSquare)p);
9286           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9287           board[p][BOARD_WIDTH-2]++;
9288           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9289         } else {
9290           p -= (int)WhitePawn;
9291           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9292                   captured = (ChessSquare) (DEMOTED captured);
9293                   p = DEMOTED p;
9294           }
9295           p = PieceToNumber((ChessSquare)p);
9296           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9297           board[BOARD_HEIGHT-1-p][1]++;
9298           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9299         }
9300       }
9301     } else if (gameInfo.variant == VariantAtomic) {
9302       if (captured != EmptySquare) {
9303         int y, x;
9304         for (y = toY-1; y <= toY+1; y++) {
9305           for (x = toX-1; x <= toX+1; x++) {
9306             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9307                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9308               board[y][x] = EmptySquare;
9309             }
9310           }
9311         }
9312         board[toY][toX] = EmptySquare;
9313       }
9314     }
9315     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9316         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9317     } else
9318     if(promoChar == '+') {
9319         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9320         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9321     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9322         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9323     }
9324     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9325                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9326         // [HGM] superchess: take promotion piece out of holdings
9327         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9328         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9329             if(!--board[k][BOARD_WIDTH-2])
9330                 board[k][BOARD_WIDTH-1] = EmptySquare;
9331         } else {
9332             if(!--board[BOARD_HEIGHT-1-k][1])
9333                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9334         }
9335     }
9336
9337 }
9338
9339 /* Updates forwardMostMove */
9340 void
9341 MakeMove(fromX, fromY, toX, toY, promoChar)
9342      int fromX, fromY, toX, toY;
9343      int promoChar;
9344 {
9345 //    forwardMostMove++; // [HGM] bare: moved downstream
9346
9347     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9348         int timeLeft; static int lastLoadFlag=0; int king, piece;
9349         piece = boards[forwardMostMove][fromY][fromX];
9350         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9351         if(gameInfo.variant == VariantKnightmate)
9352             king += (int) WhiteUnicorn - (int) WhiteKing;
9353         if(forwardMostMove == 0) {
9354             if(blackPlaysFirst)
9355                 fprintf(serverMoves, "%s;", second.tidy);
9356             fprintf(serverMoves, "%s;", first.tidy);
9357             if(!blackPlaysFirst)
9358                 fprintf(serverMoves, "%s;", second.tidy);
9359         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9360         lastLoadFlag = loadFlag;
9361         // print base move
9362         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9363         // print castling suffix
9364         if( toY == fromY && piece == king ) {
9365             if(toX-fromX > 1)
9366                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9367             if(fromX-toX >1)
9368                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9369         }
9370         // e.p. suffix
9371         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9372              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9373              boards[forwardMostMove][toY][toX] == EmptySquare
9374              && fromX != toX && fromY != toY)
9375                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9376         // promotion suffix
9377         if(promoChar != NULLCHAR)
9378                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9379         if(!loadFlag) {
9380             fprintf(serverMoves, "/%d/%d",
9381                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9382             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9383             else                      timeLeft = blackTimeRemaining/1000;
9384             fprintf(serverMoves, "/%d", timeLeft);
9385         }
9386         fflush(serverMoves);
9387     }
9388
9389     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9390       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9391                         0, 1);
9392       return;
9393     }
9394     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9395     if (commentList[forwardMostMove+1] != NULL) {
9396         free(commentList[forwardMostMove+1]);
9397         commentList[forwardMostMove+1] = NULL;
9398     }
9399     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9400     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9401     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9402     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9403     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9404     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9405     gameInfo.result = GameUnfinished;
9406     if (gameInfo.resultDetails != NULL) {
9407         free(gameInfo.resultDetails);
9408         gameInfo.resultDetails = NULL;
9409     }
9410     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9411                               moveList[forwardMostMove - 1]);
9412     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9413                              PosFlags(forwardMostMove - 1),
9414                              fromY, fromX, toY, toX, promoChar,
9415                              parseList[forwardMostMove - 1]);
9416     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9417       case MT_NONE:
9418       case MT_STALEMATE:
9419       default:
9420         break;
9421       case MT_CHECK:
9422         if(gameInfo.variant != VariantShogi)
9423             strcat(parseList[forwardMostMove - 1], "+");
9424         break;
9425       case MT_CHECKMATE:
9426       case MT_STAINMATE:
9427         strcat(parseList[forwardMostMove - 1], "#");
9428         break;
9429     }
9430     if (appData.debugMode) {
9431         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9432     }
9433
9434 }
9435
9436 /* Updates currentMove if not pausing */
9437 void
9438 ShowMove(fromX, fromY, toX, toY)
9439 {
9440     int instant = (gameMode == PlayFromGameFile) ?
9441         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9442     if(appData.noGUI) return;
9443     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9444         if (!instant) {
9445             if (forwardMostMove == currentMove + 1) {
9446                 AnimateMove(boards[forwardMostMove - 1],
9447                             fromX, fromY, toX, toY);
9448             }
9449             if (appData.highlightLastMove) {
9450                 SetHighlights(fromX, fromY, toX, toY);
9451             }
9452         }
9453         currentMove = forwardMostMove;
9454     }
9455
9456     if (instant) return;
9457
9458     DisplayMove(currentMove - 1);
9459     DrawPosition(FALSE, boards[currentMove]);
9460     DisplayBothClocks();
9461     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9462     DisplayBook(currentMove);
9463 }
9464
9465 void SendEgtPath(ChessProgramState *cps)
9466 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9467         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9468
9469         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9470
9471         while(*p) {
9472             char c, *q = name+1, *r, *s;
9473
9474             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9475             while(*p && *p != ',') *q++ = *p++;
9476             *q++ = ':'; *q = 0;
9477             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9478                 strcmp(name, ",nalimov:") == 0 ) {
9479                 // take nalimov path from the menu-changeable option first, if it is defined
9480               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9481                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9482             } else
9483             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9484                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9485                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9486                 s = r = StrStr(s, ":") + 1; // beginning of path info
9487                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9488                 c = *r; *r = 0;             // temporarily null-terminate path info
9489                     *--q = 0;               // strip of trailig ':' from name
9490                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9491                 *r = c;
9492                 SendToProgram(buf,cps);     // send egtbpath command for this format
9493             }
9494             if(*p == ',') p++; // read away comma to position for next format name
9495         }
9496 }
9497
9498 void
9499 InitChessProgram(cps, setup)
9500      ChessProgramState *cps;
9501      int setup; /* [HGM] needed to setup FRC opening position */
9502 {
9503     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9504     if (appData.noChessProgram) return;
9505     hintRequested = FALSE;
9506     bookRequested = FALSE;
9507
9508     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9509     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9510     if(cps->memSize) { /* [HGM] memory */
9511       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9512         SendToProgram(buf, cps);
9513     }
9514     SendEgtPath(cps); /* [HGM] EGT */
9515     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9516       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9517         SendToProgram(buf, cps);
9518     }
9519
9520     SendToProgram(cps->initString, cps);
9521     if (gameInfo.variant != VariantNormal &&
9522         gameInfo.variant != VariantLoadable
9523         /* [HGM] also send variant if board size non-standard */
9524         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9525                                             ) {
9526       char *v = VariantName(gameInfo.variant);
9527       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9528         /* [HGM] in protocol 1 we have to assume all variants valid */
9529         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9530         DisplayFatalError(buf, 0, 1);
9531         return;
9532       }
9533
9534       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9535       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9536       if( gameInfo.variant == VariantXiangqi )
9537            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9538       if( gameInfo.variant == VariantShogi )
9539            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9540       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9541            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9542       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9543           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9544            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9545       if( gameInfo.variant == VariantCourier )
9546            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9547       if( gameInfo.variant == VariantSuper )
9548            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9549       if( gameInfo.variant == VariantGreat )
9550            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9551       if( gameInfo.variant == VariantSChess )
9552            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9553       if( gameInfo.variant == VariantGrand )
9554            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9555
9556       if(overruled) {
9557         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9558                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9559            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9560            if(StrStr(cps->variants, b) == NULL) {
9561                // specific sized variant not known, check if general sizing allowed
9562                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9563                    if(StrStr(cps->variants, "boardsize") == NULL) {
9564                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9565                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9566                        DisplayFatalError(buf, 0, 1);
9567                        return;
9568                    }
9569                    /* [HGM] here we really should compare with the maximum supported board size */
9570                }
9571            }
9572       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9573       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9574       SendToProgram(buf, cps);
9575     }
9576     currentlyInitializedVariant = gameInfo.variant;
9577
9578     /* [HGM] send opening position in FRC to first engine */
9579     if(setup) {
9580           SendToProgram("force\n", cps);
9581           SendBoard(cps, 0);
9582           /* engine is now in force mode! Set flag to wake it up after first move. */
9583           setboardSpoiledMachineBlack = 1;
9584     }
9585
9586     if (cps->sendICS) {
9587       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9588       SendToProgram(buf, cps);
9589     }
9590     cps->maybeThinking = FALSE;
9591     cps->offeredDraw = 0;
9592     if (!appData.icsActive) {
9593         SendTimeControl(cps, movesPerSession, timeControl,
9594                         timeIncrement, appData.searchDepth,
9595                         searchTime);
9596     }
9597     if (appData.showThinking
9598         // [HGM] thinking: four options require thinking output to be sent
9599         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9600                                 ) {
9601         SendToProgram("post\n", cps);
9602     }
9603     SendToProgram("hard\n", cps);
9604     if (!appData.ponderNextMove) {
9605         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9606            it without being sure what state we are in first.  "hard"
9607            is not a toggle, so that one is OK.
9608          */
9609         SendToProgram("easy\n", cps);
9610     }
9611     if (cps->usePing) {
9612       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9613       SendToProgram(buf, cps);
9614     }
9615     cps->initDone = TRUE;
9616     ClearEngineOutputPane(cps == &second);
9617 }
9618
9619
9620 void
9621 StartChessProgram(cps)
9622      ChessProgramState *cps;
9623 {
9624     char buf[MSG_SIZ];
9625     int err;
9626
9627     if (appData.noChessProgram) return;
9628     cps->initDone = FALSE;
9629
9630     if (strcmp(cps->host, "localhost") == 0) {
9631         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9632     } else if (*appData.remoteShell == NULLCHAR) {
9633         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9634     } else {
9635         if (*appData.remoteUser == NULLCHAR) {
9636           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9637                     cps->program);
9638         } else {
9639           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9640                     cps->host, appData.remoteUser, cps->program);
9641         }
9642         err = StartChildProcess(buf, "", &cps->pr);
9643     }
9644
9645     if (err != 0) {
9646       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9647         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9648         if(cps != &first) return;
9649         appData.noChessProgram = TRUE;
9650         ThawUI();
9651         SetNCPMode();
9652 //      DisplayFatalError(buf, err, 1);
9653 //      cps->pr = NoProc;
9654 //      cps->isr = NULL;
9655         return;
9656     }
9657
9658     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9659     if (cps->protocolVersion > 1) {
9660       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9661       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9662       cps->comboCnt = 0;  //                and values of combo boxes
9663       SendToProgram(buf, cps);
9664     } else {
9665       SendToProgram("xboard\n", cps);
9666     }
9667 }
9668
9669 void
9670 TwoMachinesEventIfReady P((void))
9671 {
9672   static int curMess = 0;
9673   if (first.lastPing != first.lastPong) {
9674     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9675     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9676     return;
9677   }
9678   if (second.lastPing != second.lastPong) {
9679     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9680     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9681     return;
9682   }
9683   DisplayMessage("", ""); curMess = 0;
9684   ThawUI();
9685   TwoMachinesEvent();
9686 }
9687
9688 char *
9689 MakeName(char *template)
9690 {
9691     time_t clock;
9692     struct tm *tm;
9693     static char buf[MSG_SIZ];
9694     char *p = buf;
9695     int i;
9696
9697     clock = time((time_t *)NULL);
9698     tm = localtime(&clock);
9699
9700     while(*p++ = *template++) if(p[-1] == '%') {
9701         switch(*template++) {
9702           case 0:   *p = 0; return buf;
9703           case 'Y': i = tm->tm_year+1900; break;
9704           case 'y': i = tm->tm_year-100; break;
9705           case 'M': i = tm->tm_mon+1; break;
9706           case 'd': i = tm->tm_mday; break;
9707           case 'h': i = tm->tm_hour; break;
9708           case 'm': i = tm->tm_min; break;
9709           case 's': i = tm->tm_sec; break;
9710           default:  i = 0;
9711         }
9712         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9713     }
9714     return buf;
9715 }
9716
9717 int
9718 CountPlayers(char *p)
9719 {
9720     int n = 0;
9721     while(p = strchr(p, '\n')) p++, n++; // count participants
9722     return n;
9723 }
9724
9725 FILE *
9726 WriteTourneyFile(char *results, FILE *f)
9727 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9728     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9729     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9730         // create a file with tournament description
9731         fprintf(f, "-participants {%s}\n", appData.participants);
9732         fprintf(f, "-seedBase %d\n", appData.seedBase);
9733         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9734         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9735         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9736         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9737         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9738         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9739         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9740         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9741         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9742         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9743         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9744         if(searchTime > 0)
9745                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9746         else {
9747                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9748                 fprintf(f, "-tc %s\n", appData.timeControl);
9749                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9750         }
9751         fprintf(f, "-results \"%s\"\n", results);
9752     }
9753     return f;
9754 }
9755
9756 #define MAXENGINES 1000
9757 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9758
9759 void Substitute(char *participants, int expunge)
9760 {
9761     int i, changed, changes=0, nPlayers=0;
9762     char *p, *q, *r, buf[MSG_SIZ];
9763     if(participants == NULL) return;
9764     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9765     r = p = participants; q = appData.participants;
9766     while(*p && *p == *q) {
9767         if(*p == '\n') r = p+1, nPlayers++;
9768         p++; q++;
9769     }
9770     if(*p) { // difference
9771         while(*p && *p++ != '\n');
9772         while(*q && *q++ != '\n');
9773       changed = nPlayers;
9774         changes = 1 + (strcmp(p, q) != 0);
9775     }
9776     if(changes == 1) { // a single engine mnemonic was changed
9777         q = r; while(*q) nPlayers += (*q++ == '\n');
9778         p = buf; while(*r && (*p = *r++) != '\n') p++;
9779         *p = NULLCHAR;
9780         NamesToList(firstChessProgramNames, command, mnemonic);
9781         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9782         if(mnemonic[i]) { // The substitute is valid
9783             FILE *f;
9784             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9785                 flock(fileno(f), LOCK_EX);
9786                 ParseArgsFromFile(f);
9787                 fseek(f, 0, SEEK_SET);
9788                 FREE(appData.participants); appData.participants = participants;
9789                 if(expunge) { // erase results of replaced engine
9790                     int len = strlen(appData.results), w, b, dummy;
9791                     for(i=0; i<len; i++) {
9792                         Pairing(i, nPlayers, &w, &b, &dummy);
9793                         if((w == changed || b == changed) && appData.results[i] == '*') {
9794                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9795                             fclose(f);
9796                             return;
9797                         }
9798                     }
9799                     for(i=0; i<len; i++) {
9800                         Pairing(i, nPlayers, &w, &b, &dummy);
9801                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9802                     }
9803                 }
9804                 WriteTourneyFile(appData.results, f);
9805                 fclose(f); // release lock
9806                 return;
9807             }
9808         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9809     }
9810     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9811     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9812     free(participants);
9813     return;
9814 }
9815
9816 int
9817 CreateTourney(char *name)
9818 {
9819         FILE *f;
9820         if(matchMode && strcmp(name, appData.tourneyFile)) {
9821              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9822         }
9823         if(name[0] == NULLCHAR) {
9824             if(appData.participants[0])
9825                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9826             return 0;
9827         }
9828         f = fopen(name, "r");
9829         if(f) { // file exists
9830             ASSIGN(appData.tourneyFile, name);
9831             ParseArgsFromFile(f); // parse it
9832         } else {
9833             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9834             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9835                 DisplayError(_("Not enough participants"), 0);
9836                 return 0;
9837             }
9838             ASSIGN(appData.tourneyFile, name);
9839             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9840             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9841         }
9842         fclose(f);
9843         appData.noChessProgram = FALSE;
9844         appData.clockMode = TRUE;
9845         SetGNUMode();
9846         return 1;
9847 }
9848
9849 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9850 {
9851     char buf[MSG_SIZ], *p, *q;
9852     int i=1;
9853     while(*names) {
9854         p = names; q = buf;
9855         while(*p && *p != '\n') *q++ = *p++;
9856         *q = 0;
9857         if(engineList[i]) free(engineList[i]);
9858         engineList[i] = strdup(buf);
9859         if(*p == '\n') p++;
9860         TidyProgramName(engineList[i], "localhost", buf);
9861         if(engineMnemonic[i]) free(engineMnemonic[i]);
9862         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9863             strcat(buf, " (");
9864             sscanf(q + 8, "%s", buf + strlen(buf));
9865             strcat(buf, ")");
9866         }
9867         engineMnemonic[i] = strdup(buf);
9868         names = p; i++;
9869       if(i > MAXENGINES - 2) break;
9870     }
9871     engineList[i] = engineMnemonic[i] = NULL;
9872 }
9873
9874 // following implemented as macro to avoid type limitations
9875 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9876
9877 void SwapEngines(int n)
9878 {   // swap settings for first engine and other engine (so far only some selected options)
9879     int h;
9880     char *p;
9881     if(n == 0) return;
9882     SWAP(directory, p)
9883     SWAP(chessProgram, p)
9884     SWAP(isUCI, h)
9885     SWAP(hasOwnBookUCI, h)
9886     SWAP(protocolVersion, h)
9887     SWAP(reuse, h)
9888     SWAP(scoreIsAbsolute, h)
9889     SWAP(timeOdds, h)
9890     SWAP(logo, p)
9891     SWAP(pgnName, p)
9892     SWAP(pvSAN, h)
9893 }
9894
9895 void
9896 SetPlayer(int player)
9897 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9898     int i;
9899     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9900     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9901     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9902     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9903     if(mnemonic[i]) {
9904         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9905         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9906         ParseArgsFromString(buf);
9907     }
9908     free(engineName);
9909 }
9910
9911 int
9912 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9913 {   // determine players from game number
9914     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9915
9916     if(appData.tourneyType == 0) {
9917         roundsPerCycle = (nPlayers - 1) | 1;
9918         pairingsPerRound = nPlayers / 2;
9919     } else if(appData.tourneyType > 0) {
9920         roundsPerCycle = nPlayers - appData.tourneyType;
9921         pairingsPerRound = appData.tourneyType;
9922     }
9923     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9924     gamesPerCycle = gamesPerRound * roundsPerCycle;
9925     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9926     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9927     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9928     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9929     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9930     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9931
9932     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9933     if(appData.roundSync) *syncInterval = gamesPerRound;
9934
9935     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9936
9937     if(appData.tourneyType == 0) {
9938         if(curPairing == (nPlayers-1)/2 ) {
9939             *whitePlayer = curRound;
9940             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9941         } else {
9942             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9943             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9944             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9945             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9946         }
9947     } else if(appData.tourneyType > 0) {
9948         *whitePlayer = curPairing;
9949         *blackPlayer = curRound + appData.tourneyType;
9950     }
9951
9952     // take care of white/black alternation per round. 
9953     // For cycles and games this is already taken care of by default, derived from matchGame!
9954     return curRound & 1;
9955 }
9956
9957 int
9958 NextTourneyGame(int nr, int *swapColors)
9959 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9960     char *p, *q;
9961     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9962     FILE *tf;
9963     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9964     tf = fopen(appData.tourneyFile, "r");
9965     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9966     ParseArgsFromFile(tf); fclose(tf);
9967     InitTimeControls(); // TC might be altered from tourney file
9968
9969     nPlayers = CountPlayers(appData.participants); // count participants
9970     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9971     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9972
9973     if(syncInterval) {
9974         p = q = appData.results;
9975         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9976         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9977             DisplayMessage(_("Waiting for other game(s)"),"");
9978             waitingForGame = TRUE;
9979             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9980             return 0;
9981         }
9982         waitingForGame = FALSE;
9983     }
9984
9985     if(appData.tourneyType < 0) {
9986         if(nr>=0 && !pairingReceived) {
9987             char buf[1<<16];
9988             if(pairing.pr == NoProc) {
9989                 if(!appData.pairingEngine[0]) {
9990                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9991                     return 0;
9992                 }
9993                 StartChessProgram(&pairing); // starts the pairing engine
9994             }
9995             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9996             SendToProgram(buf, &pairing);
9997             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9998             SendToProgram(buf, &pairing);
9999             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10000         }
10001         pairingReceived = 0;                              // ... so we continue here 
10002         *swapColors = 0;
10003         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10004         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10005         matchGame = 1; roundNr = nr / syncInterval + 1;
10006     }
10007
10008     if(first.pr != NoProc) return 1; // engines already loaded
10009
10010     // redefine engines, engine dir, etc.
10011     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10012     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10013     SwapEngines(1);
10014     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10015     SwapEngines(1);         // and make that valid for second engine by swapping
10016     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10017     InitEngine(&second, 1);
10018     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10019     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10020     return 1;
10021 }
10022
10023 void
10024 NextMatchGame()
10025 {   // performs game initialization that does not invoke engines, and then tries to start the game
10026     int firstWhite, swapColors = 0;
10027     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10028     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10029     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10030     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10031     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10032     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10033     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10034     Reset(FALSE, first.pr != NoProc);
10035     appData.noChessProgram = FALSE;
10036     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
10037     TwoMachinesEvent();
10038 }
10039
10040 void UserAdjudicationEvent( int result )
10041 {
10042     ChessMove gameResult = GameIsDrawn;
10043
10044     if( result > 0 ) {
10045         gameResult = WhiteWins;
10046     }
10047     else if( result < 0 ) {
10048         gameResult = BlackWins;
10049     }
10050
10051     if( gameMode == TwoMachinesPlay ) {
10052         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10053     }
10054 }
10055
10056
10057 // [HGM] save: calculate checksum of game to make games easily identifiable
10058 int StringCheckSum(char *s)
10059 {
10060         int i = 0;
10061         if(s==NULL) return 0;
10062         while(*s) i = i*259 + *s++;
10063         return i;
10064 }
10065
10066 int GameCheckSum()
10067 {
10068         int i, sum=0;
10069         for(i=backwardMostMove; i<forwardMostMove; i++) {
10070                 sum += pvInfoList[i].depth;
10071                 sum += StringCheckSum(parseList[i]);
10072                 sum += StringCheckSum(commentList[i]);
10073                 sum *= 261;
10074         }
10075         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10076         return sum + StringCheckSum(commentList[i]);
10077 } // end of save patch
10078
10079 void
10080 GameEnds(result, resultDetails, whosays)
10081      ChessMove result;
10082      char *resultDetails;
10083      int whosays;
10084 {
10085     GameMode nextGameMode;
10086     int isIcsGame;
10087     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10088
10089     if(endingGame) return; /* [HGM] crash: forbid recursion */
10090     endingGame = 1;
10091     if(twoBoards) { // [HGM] dual: switch back to one board
10092         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10093         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10094     }
10095     if (appData.debugMode) {
10096       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10097               result, resultDetails ? resultDetails : "(null)", whosays);
10098     }
10099
10100     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10101
10102     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10103         /* If we are playing on ICS, the server decides when the
10104            game is over, but the engine can offer to draw, claim
10105            a draw, or resign.
10106          */
10107 #if ZIPPY
10108         if (appData.zippyPlay && first.initDone) {
10109             if (result == GameIsDrawn) {
10110                 /* In case draw still needs to be claimed */
10111                 SendToICS(ics_prefix);
10112                 SendToICS("draw\n");
10113             } else if (StrCaseStr(resultDetails, "resign")) {
10114                 SendToICS(ics_prefix);
10115                 SendToICS("resign\n");
10116             }
10117         }
10118 #endif
10119         endingGame = 0; /* [HGM] crash */
10120         return;
10121     }
10122
10123     /* If we're loading the game from a file, stop */
10124     if (whosays == GE_FILE) {
10125       (void) StopLoadGameTimer();
10126       gameFileFP = NULL;
10127     }
10128
10129     /* Cancel draw offers */
10130     first.offeredDraw = second.offeredDraw = 0;
10131
10132     /* If this is an ICS game, only ICS can really say it's done;
10133        if not, anyone can. */
10134     isIcsGame = (gameMode == IcsPlayingWhite ||
10135                  gameMode == IcsPlayingBlack ||
10136                  gameMode == IcsObserving    ||
10137                  gameMode == IcsExamining);
10138
10139     if (!isIcsGame || whosays == GE_ICS) {
10140         /* OK -- not an ICS game, or ICS said it was done */
10141         StopClocks();
10142         if (!isIcsGame && !appData.noChessProgram)
10143           SetUserThinkingEnables();
10144
10145         /* [HGM] if a machine claims the game end we verify this claim */
10146         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10147             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10148                 char claimer;
10149                 ChessMove trueResult = (ChessMove) -1;
10150
10151                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10152                                             first.twoMachinesColor[0] :
10153                                             second.twoMachinesColor[0] ;
10154
10155                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10156                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10157                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10158                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10159                 } else
10160                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10161                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10162                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10163                 } else
10164                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10165                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10166                 }
10167
10168                 // now verify win claims, but not in drop games, as we don't understand those yet
10169                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10170                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10171                     (result == WhiteWins && claimer == 'w' ||
10172                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10173                       if (appData.debugMode) {
10174                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10175                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10176                       }
10177                       if(result != trueResult) {
10178                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10179                               result = claimer == 'w' ? BlackWins : WhiteWins;
10180                               resultDetails = buf;
10181                       }
10182                 } else
10183                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10184                     && (forwardMostMove <= backwardMostMove ||
10185                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10186                         (claimer=='b')==(forwardMostMove&1))
10187                                                                                   ) {
10188                       /* [HGM] verify: draws that were not flagged are false claims */
10189                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10190                       result = claimer == 'w' ? BlackWins : WhiteWins;
10191                       resultDetails = buf;
10192                 }
10193                 /* (Claiming a loss is accepted no questions asked!) */
10194             }
10195             /* [HGM] bare: don't allow bare King to win */
10196             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10197                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10198                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10199                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10200                && result != GameIsDrawn)
10201             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10202                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10203                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10204                         if(p >= 0 && p <= (int)WhiteKing) k++;
10205                 }
10206                 if (appData.debugMode) {
10207                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10208                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10209                 }
10210                 if(k <= 1) {
10211                         result = GameIsDrawn;
10212                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10213                         resultDetails = buf;
10214                 }
10215             }
10216         }
10217
10218
10219         if(serverMoves != NULL && !loadFlag) { char c = '=';
10220             if(result==WhiteWins) c = '+';
10221             if(result==BlackWins) c = '-';
10222             if(resultDetails != NULL)
10223                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10224         }
10225         if (resultDetails != NULL) {
10226             gameInfo.result = result;
10227             gameInfo.resultDetails = StrSave(resultDetails);
10228
10229             /* display last move only if game was not loaded from file */
10230             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10231                 DisplayMove(currentMove - 1);
10232
10233             if (forwardMostMove != 0) {
10234                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10235                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10236                                                                 ) {
10237                     if (*appData.saveGameFile != NULLCHAR) {
10238                         SaveGameToFile(appData.saveGameFile, TRUE);
10239                     } else if (appData.autoSaveGames) {
10240                         AutoSaveGame();
10241                     }
10242                     if (*appData.savePositionFile != NULLCHAR) {
10243                         SavePositionToFile(appData.savePositionFile);
10244                     }
10245                 }
10246             }
10247
10248             /* Tell program how game ended in case it is learning */
10249             /* [HGM] Moved this to after saving the PGN, just in case */
10250             /* engine died and we got here through time loss. In that */
10251             /* case we will get a fatal error writing the pipe, which */
10252             /* would otherwise lose us the PGN.                       */
10253             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10254             /* output during GameEnds should never be fatal anymore   */
10255             if (gameMode == MachinePlaysWhite ||
10256                 gameMode == MachinePlaysBlack ||
10257                 gameMode == TwoMachinesPlay ||
10258                 gameMode == IcsPlayingWhite ||
10259                 gameMode == IcsPlayingBlack ||
10260                 gameMode == BeginningOfGame) {
10261                 char buf[MSG_SIZ];
10262                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10263                         resultDetails);
10264                 if (first.pr != NoProc) {
10265                     SendToProgram(buf, &first);
10266                 }
10267                 if (second.pr != NoProc &&
10268                     gameMode == TwoMachinesPlay) {
10269                     SendToProgram(buf, &second);
10270                 }
10271             }
10272         }
10273
10274         if (appData.icsActive) {
10275             if (appData.quietPlay &&
10276                 (gameMode == IcsPlayingWhite ||
10277                  gameMode == IcsPlayingBlack)) {
10278                 SendToICS(ics_prefix);
10279                 SendToICS("set shout 1\n");
10280             }
10281             nextGameMode = IcsIdle;
10282             ics_user_moved = FALSE;
10283             /* clean up premove.  It's ugly when the game has ended and the
10284              * premove highlights are still on the board.
10285              */
10286             if (gotPremove) {
10287               gotPremove = FALSE;
10288               ClearPremoveHighlights();
10289               DrawPosition(FALSE, boards[currentMove]);
10290             }
10291             if (whosays == GE_ICS) {
10292                 switch (result) {
10293                 case WhiteWins:
10294                     if (gameMode == IcsPlayingWhite)
10295                         PlayIcsWinSound();
10296                     else if(gameMode == IcsPlayingBlack)
10297                         PlayIcsLossSound();
10298                     break;
10299                 case BlackWins:
10300                     if (gameMode == IcsPlayingBlack)
10301                         PlayIcsWinSound();
10302                     else if(gameMode == IcsPlayingWhite)
10303                         PlayIcsLossSound();
10304                     break;
10305                 case GameIsDrawn:
10306                     PlayIcsDrawSound();
10307                     break;
10308                 default:
10309                     PlayIcsUnfinishedSound();
10310                 }
10311             }
10312         } else if (gameMode == EditGame ||
10313                    gameMode == PlayFromGameFile ||
10314                    gameMode == AnalyzeMode ||
10315                    gameMode == AnalyzeFile) {
10316             nextGameMode = gameMode;
10317         } else {
10318             nextGameMode = EndOfGame;
10319         }
10320         pausing = FALSE;
10321         ModeHighlight();
10322     } else {
10323         nextGameMode = gameMode;
10324     }
10325
10326     if (appData.noChessProgram) {
10327         gameMode = nextGameMode;
10328         ModeHighlight();
10329         endingGame = 0; /* [HGM] crash */
10330         return;
10331     }
10332
10333     if (first.reuse) {
10334         /* Put first chess program into idle state */
10335         if (first.pr != NoProc &&
10336             (gameMode == MachinePlaysWhite ||
10337              gameMode == MachinePlaysBlack ||
10338              gameMode == TwoMachinesPlay ||
10339              gameMode == IcsPlayingWhite ||
10340              gameMode == IcsPlayingBlack ||
10341              gameMode == BeginningOfGame)) {
10342             SendToProgram("force\n", &first);
10343             if (first.usePing) {
10344               char buf[MSG_SIZ];
10345               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10346               SendToProgram(buf, &first);
10347             }
10348         }
10349     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10350         /* Kill off first chess program */
10351         if (first.isr != NULL)
10352           RemoveInputSource(first.isr);
10353         first.isr = NULL;
10354
10355         if (first.pr != NoProc) {
10356             ExitAnalyzeMode();
10357             DoSleep( appData.delayBeforeQuit );
10358             SendToProgram("quit\n", &first);
10359             DoSleep( appData.delayAfterQuit );
10360             DestroyChildProcess(first.pr, first.useSigterm);
10361         }
10362         first.pr = NoProc;
10363     }
10364     if (second.reuse) {
10365         /* Put second chess program into idle state */
10366         if (second.pr != NoProc &&
10367             gameMode == TwoMachinesPlay) {
10368             SendToProgram("force\n", &second);
10369             if (second.usePing) {
10370               char buf[MSG_SIZ];
10371               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10372               SendToProgram(buf, &second);
10373             }
10374         }
10375     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10376         /* Kill off second chess program */
10377         if (second.isr != NULL)
10378           RemoveInputSource(second.isr);
10379         second.isr = NULL;
10380
10381         if (second.pr != NoProc) {
10382             DoSleep( appData.delayBeforeQuit );
10383             SendToProgram("quit\n", &second);
10384             DoSleep( appData.delayAfterQuit );
10385             DestroyChildProcess(second.pr, second.useSigterm);
10386         }
10387         second.pr = NoProc;
10388     }
10389
10390     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10391         char resChar = '=';
10392         switch (result) {
10393         case WhiteWins:
10394           resChar = '+';
10395           if (first.twoMachinesColor[0] == 'w') {
10396             first.matchWins++;
10397           } else {
10398             second.matchWins++;
10399           }
10400           break;
10401         case BlackWins:
10402           resChar = '-';
10403           if (first.twoMachinesColor[0] == 'b') {
10404             first.matchWins++;
10405           } else {
10406             second.matchWins++;
10407           }
10408           break;
10409         case GameUnfinished:
10410           resChar = ' ';
10411         default:
10412           break;
10413         }
10414
10415         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10416         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10417             ReserveGame(nextGame, resChar); // sets nextGame
10418             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10419             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10420         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10421
10422         if (nextGame <= appData.matchGames && !abortMatch) {
10423             gameMode = nextGameMode;
10424             matchGame = nextGame; // this will be overruled in tourney mode!
10425             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10426             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10427             endingGame = 0; /* [HGM] crash */
10428             return;
10429         } else {
10430             gameMode = nextGameMode;
10431             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10432                      first.tidy, second.tidy,
10433                      first.matchWins, second.matchWins,
10434                      appData.matchGames - (first.matchWins + second.matchWins));
10435             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10436             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10437             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10438                 first.twoMachinesColor = "black\n";
10439                 second.twoMachinesColor = "white\n";
10440             } else {
10441                 first.twoMachinesColor = "white\n";
10442                 second.twoMachinesColor = "black\n";
10443             }
10444         }
10445     }
10446     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10447         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10448       ExitAnalyzeMode();
10449     gameMode = nextGameMode;
10450     ModeHighlight();
10451     endingGame = 0;  /* [HGM] crash */
10452     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10453         if(matchMode == TRUE) { // match through command line: exit with or without popup
10454             if(ranking) {
10455                 ToNrEvent(forwardMostMove);
10456                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10457                 else ExitEvent(0);
10458             } else DisplayFatalError(buf, 0, 0);
10459         } else { // match through menu; just stop, with or without popup
10460             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10461             ModeHighlight();
10462             if(ranking){
10463                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10464             } else DisplayNote(buf);
10465       }
10466       if(ranking) free(ranking);
10467     }
10468 }
10469
10470 /* Assumes program was just initialized (initString sent).
10471    Leaves program in force mode. */
10472 void
10473 FeedMovesToProgram(cps, upto)
10474      ChessProgramState *cps;
10475      int upto;
10476 {
10477     int i;
10478
10479     if (appData.debugMode)
10480       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10481               startedFromSetupPosition ? "position and " : "",
10482               backwardMostMove, upto, cps->which);
10483     if(currentlyInitializedVariant != gameInfo.variant) {
10484       char buf[MSG_SIZ];
10485         // [HGM] variantswitch: make engine aware of new variant
10486         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10487                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10488         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10489         SendToProgram(buf, cps);
10490         currentlyInitializedVariant = gameInfo.variant;
10491     }
10492     SendToProgram("force\n", cps);
10493     if (startedFromSetupPosition) {
10494         SendBoard(cps, backwardMostMove);
10495     if (appData.debugMode) {
10496         fprintf(debugFP, "feedMoves\n");
10497     }
10498     }
10499     for (i = backwardMostMove; i < upto; i++) {
10500         SendMoveToProgram(i, cps);
10501     }
10502 }
10503
10504
10505 int
10506 ResurrectChessProgram()
10507 {
10508      /* The chess program may have exited.
10509         If so, restart it and feed it all the moves made so far. */
10510     static int doInit = 0;
10511
10512     if (appData.noChessProgram) return 1;
10513
10514     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10515         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10516         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10517         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10518     } else {
10519         if (first.pr != NoProc) return 1;
10520         StartChessProgram(&first);
10521     }
10522     InitChessProgram(&first, FALSE);
10523     FeedMovesToProgram(&first, currentMove);
10524
10525     if (!first.sendTime) {
10526         /* can't tell gnuchess what its clock should read,
10527            so we bow to its notion. */
10528         ResetClocks();
10529         timeRemaining[0][currentMove] = whiteTimeRemaining;
10530         timeRemaining[1][currentMove] = blackTimeRemaining;
10531     }
10532
10533     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10534                 appData.icsEngineAnalyze) && first.analysisSupport) {
10535       SendToProgram("analyze\n", &first);
10536       first.analyzing = TRUE;
10537     }
10538     return 1;
10539 }
10540
10541 /*
10542  * Button procedures
10543  */
10544 void
10545 Reset(redraw, init)
10546      int redraw, init;
10547 {
10548     int i;
10549
10550     if (appData.debugMode) {
10551         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10552                 redraw, init, gameMode);
10553     }
10554     CleanupTail(); // [HGM] vari: delete any stored variations
10555     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10556     pausing = pauseExamInvalid = FALSE;
10557     startedFromSetupPosition = blackPlaysFirst = FALSE;
10558     firstMove = TRUE;
10559     whiteFlag = blackFlag = FALSE;
10560     userOfferedDraw = FALSE;
10561     hintRequested = bookRequested = FALSE;
10562     first.maybeThinking = FALSE;
10563     second.maybeThinking = FALSE;
10564     first.bookSuspend = FALSE; // [HGM] book
10565     second.bookSuspend = FALSE;
10566     thinkOutput[0] = NULLCHAR;
10567     lastHint[0] = NULLCHAR;
10568     ClearGameInfo(&gameInfo);
10569     gameInfo.variant = StringToVariant(appData.variant);
10570     ics_user_moved = ics_clock_paused = FALSE;
10571     ics_getting_history = H_FALSE;
10572     ics_gamenum = -1;
10573     white_holding[0] = black_holding[0] = NULLCHAR;
10574     ClearProgramStats();
10575     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10576
10577     ResetFrontEnd();
10578     ClearHighlights();
10579     flipView = appData.flipView;
10580     ClearPremoveHighlights();
10581     gotPremove = FALSE;
10582     alarmSounded = FALSE;
10583
10584     GameEnds(EndOfFile, NULL, GE_PLAYER);
10585     if(appData.serverMovesName != NULL) {
10586         /* [HGM] prepare to make moves file for broadcasting */
10587         clock_t t = clock();
10588         if(serverMoves != NULL) fclose(serverMoves);
10589         serverMoves = fopen(appData.serverMovesName, "r");
10590         if(serverMoves != NULL) {
10591             fclose(serverMoves);
10592             /* delay 15 sec before overwriting, so all clients can see end */
10593             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10594         }
10595         serverMoves = fopen(appData.serverMovesName, "w");
10596     }
10597
10598     ExitAnalyzeMode();
10599     gameMode = BeginningOfGame;
10600     ModeHighlight();
10601     if(appData.icsActive) gameInfo.variant = VariantNormal;
10602     currentMove = forwardMostMove = backwardMostMove = 0;
10603     InitPosition(redraw);
10604     for (i = 0; i < MAX_MOVES; i++) {
10605         if (commentList[i] != NULL) {
10606             free(commentList[i]);
10607             commentList[i] = NULL;
10608         }
10609     }
10610     ResetClocks();
10611     timeRemaining[0][0] = whiteTimeRemaining;
10612     timeRemaining[1][0] = blackTimeRemaining;
10613
10614     if (first.pr == NULL) {
10615         StartChessProgram(&first);
10616     }
10617     if (init) {
10618             InitChessProgram(&first, startedFromSetupPosition);
10619     }
10620     DisplayTitle("");
10621     DisplayMessage("", "");
10622     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10623     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10624 }
10625
10626 void
10627 AutoPlayGameLoop()
10628 {
10629     for (;;) {
10630         if (!AutoPlayOneMove())
10631           return;
10632         if (matchMode || appData.timeDelay == 0)
10633           continue;
10634         if (appData.timeDelay < 0)
10635           return;
10636         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10637         break;
10638     }
10639 }
10640
10641
10642 int
10643 AutoPlayOneMove()
10644 {
10645     int fromX, fromY, toX, toY;
10646
10647     if (appData.debugMode) {
10648       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10649     }
10650
10651     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10652       return FALSE;
10653
10654     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10655       pvInfoList[currentMove].depth = programStats.depth;
10656       pvInfoList[currentMove].score = programStats.score;
10657       pvInfoList[currentMove].time  = 0;
10658       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10659     }
10660
10661     if (currentMove >= forwardMostMove) {
10662       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10663 //      gameMode = EndOfGame;
10664 //      ModeHighlight();
10665
10666       /* [AS] Clear current move marker at the end of a game */
10667       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10668
10669       return FALSE;
10670     }
10671
10672     toX = moveList[currentMove][2] - AAA;
10673     toY = moveList[currentMove][3] - ONE;
10674
10675     if (moveList[currentMove][1] == '@') {
10676         if (appData.highlightLastMove) {
10677             SetHighlights(-1, -1, toX, toY);
10678         }
10679     } else {
10680         fromX = moveList[currentMove][0] - AAA;
10681         fromY = moveList[currentMove][1] - ONE;
10682
10683         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10684
10685         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10686
10687         if (appData.highlightLastMove) {
10688             SetHighlights(fromX, fromY, toX, toY);
10689         }
10690     }
10691     DisplayMove(currentMove);
10692     SendMoveToProgram(currentMove++, &first);
10693     DisplayBothClocks();
10694     DrawPosition(FALSE, boards[currentMove]);
10695     // [HGM] PV info: always display, routine tests if empty
10696     DisplayComment(currentMove - 1, commentList[currentMove]);
10697     return TRUE;
10698 }
10699
10700
10701 int
10702 LoadGameOneMove(readAhead)
10703      ChessMove readAhead;
10704 {
10705     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10706     char promoChar = NULLCHAR;
10707     ChessMove moveType;
10708     char move[MSG_SIZ];
10709     char *p, *q;
10710
10711     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10712         gameMode != AnalyzeMode && gameMode != Training) {
10713         gameFileFP = NULL;
10714         return FALSE;
10715     }
10716
10717     yyboardindex = forwardMostMove;
10718     if (readAhead != EndOfFile) {
10719       moveType = readAhead;
10720     } else {
10721       if (gameFileFP == NULL)
10722           return FALSE;
10723       moveType = (ChessMove) Myylex();
10724     }
10725
10726     done = FALSE;
10727     switch (moveType) {
10728       case Comment:
10729         if (appData.debugMode)
10730           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10731         p = yy_text;
10732
10733         /* append the comment but don't display it */
10734         AppendComment(currentMove, p, FALSE);
10735         return TRUE;
10736
10737       case WhiteCapturesEnPassant:
10738       case BlackCapturesEnPassant:
10739       case WhitePromotion:
10740       case BlackPromotion:
10741       case WhiteNonPromotion:
10742       case BlackNonPromotion:
10743       case NormalMove:
10744       case WhiteKingSideCastle:
10745       case WhiteQueenSideCastle:
10746       case BlackKingSideCastle:
10747       case BlackQueenSideCastle:
10748       case WhiteKingSideCastleWild:
10749       case WhiteQueenSideCastleWild:
10750       case BlackKingSideCastleWild:
10751       case BlackQueenSideCastleWild:
10752       /* PUSH Fabien */
10753       case WhiteHSideCastleFR:
10754       case WhiteASideCastleFR:
10755       case BlackHSideCastleFR:
10756       case BlackASideCastleFR:
10757       /* POP Fabien */
10758         if (appData.debugMode)
10759           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10760         fromX = currentMoveString[0] - AAA;
10761         fromY = currentMoveString[1] - ONE;
10762         toX = currentMoveString[2] - AAA;
10763         toY = currentMoveString[3] - ONE;
10764         promoChar = currentMoveString[4];
10765         break;
10766
10767       case WhiteDrop:
10768       case BlackDrop:
10769         if (appData.debugMode)
10770           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10771         fromX = moveType == WhiteDrop ?
10772           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10773         (int) CharToPiece(ToLower(currentMoveString[0]));
10774         fromY = DROP_RANK;
10775         toX = currentMoveString[2] - AAA;
10776         toY = currentMoveString[3] - ONE;
10777         break;
10778
10779       case WhiteWins:
10780       case BlackWins:
10781       case GameIsDrawn:
10782       case GameUnfinished:
10783         if (appData.debugMode)
10784           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10785         p = strchr(yy_text, '{');
10786         if (p == NULL) p = strchr(yy_text, '(');
10787         if (p == NULL) {
10788             p = yy_text;
10789             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10790         } else {
10791             q = strchr(p, *p == '{' ? '}' : ')');
10792             if (q != NULL) *q = NULLCHAR;
10793             p++;
10794         }
10795         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10796         GameEnds(moveType, p, GE_FILE);
10797         done = TRUE;
10798         if (cmailMsgLoaded) {
10799             ClearHighlights();
10800             flipView = WhiteOnMove(currentMove);
10801             if (moveType == GameUnfinished) flipView = !flipView;
10802             if (appData.debugMode)
10803               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10804         }
10805         break;
10806
10807       case EndOfFile:
10808         if (appData.debugMode)
10809           fprintf(debugFP, "Parser hit end of file\n");
10810         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10811           case MT_NONE:
10812           case MT_CHECK:
10813             break;
10814           case MT_CHECKMATE:
10815           case MT_STAINMATE:
10816             if (WhiteOnMove(currentMove)) {
10817                 GameEnds(BlackWins, "Black mates", GE_FILE);
10818             } else {
10819                 GameEnds(WhiteWins, "White mates", GE_FILE);
10820             }
10821             break;
10822           case MT_STALEMATE:
10823             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10824             break;
10825         }
10826         done = TRUE;
10827         break;
10828
10829       case MoveNumberOne:
10830         if (lastLoadGameStart == GNUChessGame) {
10831             /* GNUChessGames have numbers, but they aren't move numbers */
10832             if (appData.debugMode)
10833               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10834                       yy_text, (int) moveType);
10835             return LoadGameOneMove(EndOfFile); /* tail recursion */
10836         }
10837         /* else fall thru */
10838
10839       case XBoardGame:
10840       case GNUChessGame:
10841       case PGNTag:
10842         /* Reached start of next game in file */
10843         if (appData.debugMode)
10844           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10845         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10846           case MT_NONE:
10847           case MT_CHECK:
10848             break;
10849           case MT_CHECKMATE:
10850           case MT_STAINMATE:
10851             if (WhiteOnMove(currentMove)) {
10852                 GameEnds(BlackWins, "Black mates", GE_FILE);
10853             } else {
10854                 GameEnds(WhiteWins, "White mates", GE_FILE);
10855             }
10856             break;
10857           case MT_STALEMATE:
10858             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10859             break;
10860         }
10861         done = TRUE;
10862         break;
10863
10864       case PositionDiagram:     /* should not happen; ignore */
10865       case ElapsedTime:         /* ignore */
10866       case NAG:                 /* ignore */
10867         if (appData.debugMode)
10868           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10869                   yy_text, (int) moveType);
10870         return LoadGameOneMove(EndOfFile); /* tail recursion */
10871
10872       case IllegalMove:
10873         if (appData.testLegality) {
10874             if (appData.debugMode)
10875               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10876             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10877                     (forwardMostMove / 2) + 1,
10878                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10879             DisplayError(move, 0);
10880             done = TRUE;
10881         } else {
10882             if (appData.debugMode)
10883               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10884                       yy_text, currentMoveString);
10885             fromX = currentMoveString[0] - AAA;
10886             fromY = currentMoveString[1] - ONE;
10887             toX = currentMoveString[2] - AAA;
10888             toY = currentMoveString[3] - ONE;
10889             promoChar = currentMoveString[4];
10890         }
10891         break;
10892
10893       case AmbiguousMove:
10894         if (appData.debugMode)
10895           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10896         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10897                 (forwardMostMove / 2) + 1,
10898                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10899         DisplayError(move, 0);
10900         done = TRUE;
10901         break;
10902
10903       default:
10904       case ImpossibleMove:
10905         if (appData.debugMode)
10906           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10907         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10908                 (forwardMostMove / 2) + 1,
10909                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10910         DisplayError(move, 0);
10911         done = TRUE;
10912         break;
10913     }
10914
10915     if (done) {
10916         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10917             DrawPosition(FALSE, boards[currentMove]);
10918             DisplayBothClocks();
10919             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10920               DisplayComment(currentMove - 1, commentList[currentMove]);
10921         }
10922         (void) StopLoadGameTimer();
10923         gameFileFP = NULL;
10924         cmailOldMove = forwardMostMove;
10925         return FALSE;
10926     } else {
10927         /* currentMoveString is set as a side-effect of yylex */
10928
10929         thinkOutput[0] = NULLCHAR;
10930         MakeMove(fromX, fromY, toX, toY, promoChar);
10931         currentMove = forwardMostMove;
10932         return TRUE;
10933     }
10934 }
10935
10936 /* Load the nth game from the given file */
10937 int
10938 LoadGameFromFile(filename, n, title, useList)
10939      char *filename;
10940      int n;
10941      char *title;
10942      /*Boolean*/ int useList;
10943 {
10944     FILE *f;
10945     char buf[MSG_SIZ];
10946
10947     if (strcmp(filename, "-") == 0) {
10948         f = stdin;
10949         title = "stdin";
10950     } else {
10951         f = fopen(filename, "rb");
10952         if (f == NULL) {
10953           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10954             DisplayError(buf, errno);
10955             return FALSE;
10956         }
10957     }
10958     if (fseek(f, 0, 0) == -1) {
10959         /* f is not seekable; probably a pipe */
10960         useList = FALSE;
10961     }
10962     if (useList && n == 0) {
10963         int error = GameListBuild(f);
10964         if (error) {
10965             DisplayError(_("Cannot build game list"), error);
10966         } else if (!ListEmpty(&gameList) &&
10967                    ((ListGame *) gameList.tailPred)->number > 1) {
10968             GameListPopUp(f, title);
10969             return TRUE;
10970         }
10971         GameListDestroy();
10972         n = 1;
10973     }
10974     if (n == 0) n = 1;
10975     return LoadGame(f, n, title, FALSE);
10976 }
10977
10978
10979 void
10980 MakeRegisteredMove()
10981 {
10982     int fromX, fromY, toX, toY;
10983     char promoChar;
10984     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10985         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10986           case CMAIL_MOVE:
10987           case CMAIL_DRAW:
10988             if (appData.debugMode)
10989               fprintf(debugFP, "Restoring %s for game %d\n",
10990                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10991
10992             thinkOutput[0] = NULLCHAR;
10993             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10994             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10995             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10996             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10997             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10998             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10999             MakeMove(fromX, fromY, toX, toY, promoChar);
11000             ShowMove(fromX, fromY, toX, toY);
11001
11002             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11003               case MT_NONE:
11004               case MT_CHECK:
11005                 break;
11006
11007               case MT_CHECKMATE:
11008               case MT_STAINMATE:
11009                 if (WhiteOnMove(currentMove)) {
11010                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11011                 } else {
11012                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11013                 }
11014                 break;
11015
11016               case MT_STALEMATE:
11017                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11018                 break;
11019             }
11020
11021             break;
11022
11023           case CMAIL_RESIGN:
11024             if (WhiteOnMove(currentMove)) {
11025                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11026             } else {
11027                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11028             }
11029             break;
11030
11031           case CMAIL_ACCEPT:
11032             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11033             break;
11034
11035           default:
11036             break;
11037         }
11038     }
11039
11040     return;
11041 }
11042
11043 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11044 int
11045 CmailLoadGame(f, gameNumber, title, useList)
11046      FILE *f;
11047      int gameNumber;
11048      char *title;
11049      int useList;
11050 {
11051     int retVal;
11052
11053     if (gameNumber > nCmailGames) {
11054         DisplayError(_("No more games in this message"), 0);
11055         return FALSE;
11056     }
11057     if (f == lastLoadGameFP) {
11058         int offset = gameNumber - lastLoadGameNumber;
11059         if (offset == 0) {
11060             cmailMsg[0] = NULLCHAR;
11061             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11062                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11063                 nCmailMovesRegistered--;
11064             }
11065             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11066             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11067                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11068             }
11069         } else {
11070             if (! RegisterMove()) return FALSE;
11071         }
11072     }
11073
11074     retVal = LoadGame(f, gameNumber, title, useList);
11075
11076     /* Make move registered during previous look at this game, if any */
11077     MakeRegisteredMove();
11078
11079     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11080         commentList[currentMove]
11081           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11082         DisplayComment(currentMove - 1, commentList[currentMove]);
11083     }
11084
11085     return retVal;
11086 }
11087
11088 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11089 int
11090 ReloadGame(offset)
11091      int offset;
11092 {
11093     int gameNumber = lastLoadGameNumber + offset;
11094     if (lastLoadGameFP == NULL) {
11095         DisplayError(_("No game has been loaded yet"), 0);
11096         return FALSE;
11097     }
11098     if (gameNumber <= 0) {
11099         DisplayError(_("Can't back up any further"), 0);
11100         return FALSE;
11101     }
11102     if (cmailMsgLoaded) {
11103         return CmailLoadGame(lastLoadGameFP, gameNumber,
11104                              lastLoadGameTitle, lastLoadGameUseList);
11105     } else {
11106         return LoadGame(lastLoadGameFP, gameNumber,
11107                         lastLoadGameTitle, lastLoadGameUseList);
11108     }
11109 }
11110
11111 int keys[EmptySquare+1];
11112
11113 int
11114 PositionMatches(Board b1, Board b2)
11115 {
11116     int r, f, sum=0;
11117     switch(appData.searchMode) {
11118         case 1: return CompareWithRights(b1, b2);
11119         case 2:
11120             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11121                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11122             }
11123             return TRUE;
11124         case 3:
11125             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11126               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11127                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11128             }
11129             return sum==0;
11130         case 4:
11131             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11132                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11133             }
11134             return sum==0;
11135     }
11136     return TRUE;
11137 }
11138
11139 GameInfo dummyInfo;
11140
11141 int GameContainsPosition(FILE *f, ListGame *lg)
11142 {
11143     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11144     int fromX, fromY, toX, toY;
11145     char promoChar;
11146     static int initDone=FALSE;
11147
11148     if(!initDone) {
11149         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11150         initDone = TRUE;
11151     }
11152     dummyInfo.variant = VariantNormal;
11153     FREE(dummyInfo.fen); dummyInfo.fen = NULL;
11154     dummyInfo.whiteRating = 0;
11155     dummyInfo.blackRating = 0;
11156     FREE(dummyInfo.date); dummyInfo.date = NULL;
11157     fseek(f, lg->offset, 0);
11158     yynewfile(f);
11159     CopyBoard(boards[scratch], initialPosition); // default start position
11160     while(1) {
11161         yyboardindex = scratch + (plyNr&1);
11162       quickFlag = 1;
11163         next = Myylex();
11164       quickFlag = 0;
11165         switch(next) {
11166             case PGNTag:
11167                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11168 #if 0
11169                 ParsePGNTag(yy_text, &dummyInfo); // this has a bad memory leak...
11170                 if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL;
11171 #else
11172                 // do it ourselves avoiding malloc
11173                 { char *p = yy_text+1, *q;
11174                   while(!isdigit(*p) && !isalpha(*p)) p++;
11175                   q  = p; while(*p != ' ' && *p != '\t' && *p != '\n') p++;
11176                   *p = NULLCHAR;
11177                   if(!StrCaseCmp(q, "Date") && (p = strchr(p+1, '"'))) { if(atoi(p+1) < appData.dateThreshold) return -1; } else
11178                   if(!StrCaseCmp(q, "Variant")  &&  (p = strchr(p+1, '"'))) dummyInfo.variant = StringToVariant(p+1); else
11179                   if(!StrCaseCmp(q, "WhiteElo")  && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11180                   if(!StrCaseCmp(q, "BlackElo")  && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11181                   if(!StrCaseCmp(q, "WhiteUSCF") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11182                   if(!StrCaseCmp(q, "BlackUSCF") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11183                   if(!StrCaseCmp(q, "FEN")  && (p = strchr(p+1, '"'))) ParseFEN(boards[scratch], &btm, p+1);
11184                 }
11185 #endif
11186             default:
11187                 continue;
11188
11189             case XBoardGame:
11190             case GNUChessGame:
11191                 if(plyNr) return -1; // after we have seen moves, this is for new game
11192               continue;
11193
11194             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11195             case ImpossibleMove:
11196             case WhiteWins: // game ends here with these four
11197             case BlackWins:
11198             case GameIsDrawn:
11199             case GameUnfinished:
11200                 return -1;
11201
11202             case IllegalMove:
11203                 if(appData.testLegality) return -1;
11204             case WhiteCapturesEnPassant:
11205             case BlackCapturesEnPassant:
11206             case WhitePromotion:
11207             case BlackPromotion:
11208             case WhiteNonPromotion:
11209             case BlackNonPromotion:
11210             case NormalMove:
11211             case WhiteKingSideCastle:
11212             case WhiteQueenSideCastle:
11213             case BlackKingSideCastle:
11214             case BlackQueenSideCastle:
11215             case WhiteKingSideCastleWild:
11216             case WhiteQueenSideCastleWild:
11217             case BlackKingSideCastleWild:
11218             case BlackQueenSideCastleWild:
11219             case WhiteHSideCastleFR:
11220             case WhiteASideCastleFR:
11221             case BlackHSideCastleFR:
11222             case BlackASideCastleFR:
11223                 fromX = currentMoveString[0] - AAA;
11224                 fromY = currentMoveString[1] - ONE;
11225                 toX = currentMoveString[2] - AAA;
11226                 toY = currentMoveString[3] - ONE;
11227                 promoChar = currentMoveString[4];
11228                 break;
11229             case WhiteDrop:
11230             case BlackDrop:
11231                 fromX = next == WhiteDrop ?
11232                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11233                   (int) CharToPiece(ToLower(currentMoveString[0]));
11234                 fromY = DROP_RANK;
11235                 toX = currentMoveString[2] - AAA;
11236                 toY = currentMoveString[3] - ONE;
11237                 break;
11238         }
11239         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11240         if(plyNr == 0) { // but first figure out variant and initial position
11241             if(dummyInfo.variant != gameInfo.variant) return -1; // wrong variant
11242             if(appData.eloThreshold1 && (dummyInfo.whiteRating < appData.eloThreshold1 && dummyInfo.blackRating < appData.eloThreshold1)) return -1;
11243             if(appData.eloThreshold2 && (dummyInfo.whiteRating < appData.eloThreshold2 || dummyInfo.blackRating < appData.eloThreshold2)) return -1;
11244             if(appData.dateThreshold && (!dummyInfo.date || atoi(dummyInfo.date) < appData.dateThreshold)) return -1;
11245             if(btm) CopyBoard(boards[scratch+1], boards[scratch]), plyNr++;
11246             if(PositionMatches(boards[scratch + plyNr], boards[currentMove])) return plyNr;
11247         }
11248         CopyBoard(boards[scratch + (plyNr+1&1)], boards[scratch + (plyNr&1)]);
11249         plyNr++;
11250         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch + (plyNr&1)]);
11251         if(PositionMatches(boards[scratch + (plyNr&1)], boards[currentMove])) return plyNr;
11252     }
11253 }
11254
11255 /* Load the nth game from open file f */
11256 int
11257 LoadGame(f, gameNumber, title, useList)
11258      FILE *f;
11259      int gameNumber;
11260      char *title;
11261      int useList;
11262 {
11263     ChessMove cm;
11264     char buf[MSG_SIZ];
11265     int gn = gameNumber;
11266     ListGame *lg = NULL;
11267     int numPGNTags = 0;
11268     int err, pos = -1;
11269     GameMode oldGameMode;
11270     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11271
11272     if (appData.debugMode)
11273         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11274
11275     if (gameMode == Training )
11276         SetTrainingModeOff();
11277
11278     oldGameMode = gameMode;
11279     if (gameMode != BeginningOfGame) {
11280       Reset(FALSE, TRUE);
11281     }
11282
11283     gameFileFP = f;
11284     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11285         fclose(lastLoadGameFP);
11286     }
11287
11288     if (useList) {
11289         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11290
11291         if (lg) {
11292             fseek(f, lg->offset, 0);
11293             GameListHighlight(gameNumber);
11294             pos = lg->position;
11295             gn = 1;
11296         }
11297         else {
11298             DisplayError(_("Game number out of range"), 0);
11299             return FALSE;
11300         }
11301     } else {
11302         GameListDestroy();
11303         if (fseek(f, 0, 0) == -1) {
11304             if (f == lastLoadGameFP ?
11305                 gameNumber == lastLoadGameNumber + 1 :
11306                 gameNumber == 1) {
11307                 gn = 1;
11308             } else {
11309                 DisplayError(_("Can't seek on game file"), 0);
11310                 return FALSE;
11311             }
11312         }
11313     }
11314     lastLoadGameFP = f;
11315     lastLoadGameNumber = gameNumber;
11316     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11317     lastLoadGameUseList = useList;
11318
11319     yynewfile(f);
11320
11321     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11322       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11323                 lg->gameInfo.black);
11324             DisplayTitle(buf);
11325     } else if (*title != NULLCHAR) {
11326         if (gameNumber > 1) {
11327           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11328             DisplayTitle(buf);
11329         } else {
11330             DisplayTitle(title);
11331         }
11332     }
11333
11334     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11335         gameMode = PlayFromGameFile;
11336         ModeHighlight();
11337     }
11338
11339     currentMove = forwardMostMove = backwardMostMove = 0;
11340     CopyBoard(boards[0], initialPosition);
11341     StopClocks();
11342
11343     /*
11344      * Skip the first gn-1 games in the file.
11345      * Also skip over anything that precedes an identifiable
11346      * start of game marker, to avoid being confused by
11347      * garbage at the start of the file.  Currently
11348      * recognized start of game markers are the move number "1",
11349      * the pattern "gnuchess .* game", the pattern
11350      * "^[#;%] [^ ]* game file", and a PGN tag block.
11351      * A game that starts with one of the latter two patterns
11352      * will also have a move number 1, possibly
11353      * following a position diagram.
11354      * 5-4-02: Let's try being more lenient and allowing a game to
11355      * start with an unnumbered move.  Does that break anything?
11356      */
11357     cm = lastLoadGameStart = EndOfFile;
11358     while (gn > 0) {
11359         yyboardindex = forwardMostMove;
11360         cm = (ChessMove) Myylex();
11361         switch (cm) {
11362           case EndOfFile:
11363             if (cmailMsgLoaded) {
11364                 nCmailGames = CMAIL_MAX_GAMES - gn;
11365             } else {
11366                 Reset(TRUE, TRUE);
11367                 DisplayError(_("Game not found in file"), 0);
11368             }
11369             return FALSE;
11370
11371           case GNUChessGame:
11372           case XBoardGame:
11373             gn--;
11374             lastLoadGameStart = cm;
11375             break;
11376
11377           case MoveNumberOne:
11378             switch (lastLoadGameStart) {
11379               case GNUChessGame:
11380               case XBoardGame:
11381               case PGNTag:
11382                 break;
11383               case MoveNumberOne:
11384               case EndOfFile:
11385                 gn--;           /* count this game */
11386                 lastLoadGameStart = cm;
11387                 break;
11388               default:
11389                 /* impossible */
11390                 break;
11391             }
11392             break;
11393
11394           case PGNTag:
11395             switch (lastLoadGameStart) {
11396               case GNUChessGame:
11397               case PGNTag:
11398               case MoveNumberOne:
11399               case EndOfFile:
11400                 gn--;           /* count this game */
11401                 lastLoadGameStart = cm;
11402                 break;
11403               case XBoardGame:
11404                 lastLoadGameStart = cm; /* game counted already */
11405                 break;
11406               default:
11407                 /* impossible */
11408                 break;
11409             }
11410             if (gn > 0) {
11411                 do {
11412                     yyboardindex = forwardMostMove;
11413                     cm = (ChessMove) Myylex();
11414                 } while (cm == PGNTag || cm == Comment);
11415             }
11416             break;
11417
11418           case WhiteWins:
11419           case BlackWins:
11420           case GameIsDrawn:
11421             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11422                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11423                     != CMAIL_OLD_RESULT) {
11424                     nCmailResults ++ ;
11425                     cmailResult[  CMAIL_MAX_GAMES
11426                                 - gn - 1] = CMAIL_OLD_RESULT;
11427                 }
11428             }
11429             break;
11430
11431           case NormalMove:
11432             /* Only a NormalMove can be at the start of a game
11433              * without a position diagram. */
11434             if (lastLoadGameStart == EndOfFile ) {
11435               gn--;
11436               lastLoadGameStart = MoveNumberOne;
11437             }
11438             break;
11439
11440           default:
11441             break;
11442         }
11443     }
11444
11445     if (appData.debugMode)
11446       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11447
11448     if (cm == XBoardGame) {
11449         /* Skip any header junk before position diagram and/or move 1 */
11450         for (;;) {
11451             yyboardindex = forwardMostMove;
11452             cm = (ChessMove) Myylex();
11453
11454             if (cm == EndOfFile ||
11455                 cm == GNUChessGame || cm == XBoardGame) {
11456                 /* Empty game; pretend end-of-file and handle later */
11457                 cm = EndOfFile;
11458                 break;
11459             }
11460
11461             if (cm == MoveNumberOne || cm == PositionDiagram ||
11462                 cm == PGNTag || cm == Comment)
11463               break;
11464         }
11465     } else if (cm == GNUChessGame) {
11466         if (gameInfo.event != NULL) {
11467             free(gameInfo.event);
11468         }
11469         gameInfo.event = StrSave(yy_text);
11470     }
11471
11472     startedFromSetupPosition = FALSE;
11473     while (cm == PGNTag) {
11474         if (appData.debugMode)
11475           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11476         err = ParsePGNTag(yy_text, &gameInfo);
11477         if (!err) numPGNTags++;
11478
11479         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11480         if(gameInfo.variant != oldVariant) {
11481             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11482             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11483             InitPosition(TRUE);
11484             oldVariant = gameInfo.variant;
11485             if (appData.debugMode)
11486               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11487         }
11488
11489
11490         if (gameInfo.fen != NULL) {
11491           Board initial_position;
11492           startedFromSetupPosition = TRUE;
11493           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11494             Reset(TRUE, TRUE);
11495             DisplayError(_("Bad FEN position in file"), 0);
11496             return FALSE;
11497           }
11498           CopyBoard(boards[0], initial_position);
11499           if (blackPlaysFirst) {
11500             currentMove = forwardMostMove = backwardMostMove = 1;
11501             CopyBoard(boards[1], initial_position);
11502             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11503             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11504             timeRemaining[0][1] = whiteTimeRemaining;
11505             timeRemaining[1][1] = blackTimeRemaining;
11506             if (commentList[0] != NULL) {
11507               commentList[1] = commentList[0];
11508               commentList[0] = NULL;
11509             }
11510           } else {
11511             currentMove = forwardMostMove = backwardMostMove = 0;
11512           }
11513           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11514           {   int i;
11515               initialRulePlies = FENrulePlies;
11516               for( i=0; i< nrCastlingRights; i++ )
11517                   initialRights[i] = initial_position[CASTLING][i];
11518           }
11519           yyboardindex = forwardMostMove;
11520           free(gameInfo.fen);
11521           gameInfo.fen = NULL;
11522         }
11523
11524         yyboardindex = forwardMostMove;
11525         cm = (ChessMove) Myylex();
11526
11527         /* Handle comments interspersed among the tags */
11528         while (cm == Comment) {
11529             char *p;
11530             if (appData.debugMode)
11531               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11532             p = yy_text;
11533             AppendComment(currentMove, p, FALSE);
11534             yyboardindex = forwardMostMove;
11535             cm = (ChessMove) Myylex();
11536         }
11537     }
11538
11539     /* don't rely on existence of Event tag since if game was
11540      * pasted from clipboard the Event tag may not exist
11541      */
11542     if (numPGNTags > 0){
11543         char *tags;
11544         if (gameInfo.variant == VariantNormal) {
11545           VariantClass v = StringToVariant(gameInfo.event);
11546           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11547           if(v < VariantShogi) gameInfo.variant = v;
11548         }
11549         if (!matchMode) {
11550           if( appData.autoDisplayTags ) {
11551             tags = PGNTags(&gameInfo);
11552             TagsPopUp(tags, CmailMsg());
11553             free(tags);
11554           }
11555         }
11556     } else {
11557         /* Make something up, but don't display it now */
11558         SetGameInfo();
11559         TagsPopDown();
11560     }
11561
11562     if (cm == PositionDiagram) {
11563         int i, j;
11564         char *p;
11565         Board initial_position;
11566
11567         if (appData.debugMode)
11568           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11569
11570         if (!startedFromSetupPosition) {
11571             p = yy_text;
11572             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11573               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11574                 switch (*p) {
11575                   case '{':
11576                   case '[':
11577                   case '-':
11578                   case ' ':
11579                   case '\t':
11580                   case '\n':
11581                   case '\r':
11582                     break;
11583                   default:
11584                     initial_position[i][j++] = CharToPiece(*p);
11585                     break;
11586                 }
11587             while (*p == ' ' || *p == '\t' ||
11588                    *p == '\n' || *p == '\r') p++;
11589
11590             if (strncmp(p, "black", strlen("black"))==0)
11591               blackPlaysFirst = TRUE;
11592             else
11593               blackPlaysFirst = FALSE;
11594             startedFromSetupPosition = TRUE;
11595
11596             CopyBoard(boards[0], initial_position);
11597             if (blackPlaysFirst) {
11598                 currentMove = forwardMostMove = backwardMostMove = 1;
11599                 CopyBoard(boards[1], initial_position);
11600                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11601                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11602                 timeRemaining[0][1] = whiteTimeRemaining;
11603                 timeRemaining[1][1] = blackTimeRemaining;
11604                 if (commentList[0] != NULL) {
11605                     commentList[1] = commentList[0];
11606                     commentList[0] = NULL;
11607                 }
11608             } else {
11609                 currentMove = forwardMostMove = backwardMostMove = 0;
11610             }
11611         }
11612         yyboardindex = forwardMostMove;
11613         cm = (ChessMove) Myylex();
11614     }
11615
11616     if (first.pr == NoProc) {
11617         StartChessProgram(&first);
11618     }
11619     InitChessProgram(&first, FALSE);
11620     SendToProgram("force\n", &first);
11621     if (startedFromSetupPosition) {
11622         SendBoard(&first, forwardMostMove);
11623     if (appData.debugMode) {
11624         fprintf(debugFP, "Load Game\n");
11625     }
11626         DisplayBothClocks();
11627     }
11628
11629     /* [HGM] server: flag to write setup moves in broadcast file as one */
11630     loadFlag = appData.suppressLoadMoves;
11631
11632     while (cm == Comment) {
11633         char *p;
11634         if (appData.debugMode)
11635           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11636         p = yy_text;
11637         AppendComment(currentMove, p, FALSE);
11638         yyboardindex = forwardMostMove;
11639         cm = (ChessMove) Myylex();
11640     }
11641
11642     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11643         cm == WhiteWins || cm == BlackWins ||
11644         cm == GameIsDrawn || cm == GameUnfinished) {
11645         DisplayMessage("", _("No moves in game"));
11646         if (cmailMsgLoaded) {
11647             if (appData.debugMode)
11648               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11649             ClearHighlights();
11650             flipView = FALSE;
11651         }
11652         DrawPosition(FALSE, boards[currentMove]);
11653         DisplayBothClocks();
11654         gameMode = EditGame;
11655         ModeHighlight();
11656         gameFileFP = NULL;
11657         cmailOldMove = 0;
11658         return TRUE;
11659     }
11660
11661     // [HGM] PV info: routine tests if comment empty
11662     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11663         DisplayComment(currentMove - 1, commentList[currentMove]);
11664     }
11665     if (!matchMode && appData.timeDelay != 0)
11666       DrawPosition(FALSE, boards[currentMove]);
11667
11668     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11669       programStats.ok_to_send = 1;
11670     }
11671
11672     /* if the first token after the PGN tags is a move
11673      * and not move number 1, retrieve it from the parser
11674      */
11675     if (cm != MoveNumberOne)
11676         LoadGameOneMove(cm);
11677
11678     /* load the remaining moves from the file */
11679     while (LoadGameOneMove(EndOfFile)) {
11680       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11681       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11682     }
11683
11684     /* rewind to the start of the game */
11685     currentMove = backwardMostMove;
11686
11687     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11688
11689     if (oldGameMode == AnalyzeFile ||
11690         oldGameMode == AnalyzeMode) {
11691       AnalyzeFileEvent();
11692     }
11693
11694     if (!matchMode && pos >= 0) {
11695         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11696     } else
11697     if (matchMode || appData.timeDelay == 0) {
11698       ToEndEvent();
11699     } else if (appData.timeDelay > 0) {
11700       AutoPlayGameLoop();
11701     }
11702
11703     if (appData.debugMode)
11704         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11705
11706     loadFlag = 0; /* [HGM] true game starts */
11707     return TRUE;
11708 }
11709
11710 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11711 int
11712 ReloadPosition(offset)
11713      int offset;
11714 {
11715     int positionNumber = lastLoadPositionNumber + offset;
11716     if (lastLoadPositionFP == NULL) {
11717         DisplayError(_("No position has been loaded yet"), 0);
11718         return FALSE;
11719     }
11720     if (positionNumber <= 0) {
11721         DisplayError(_("Can't back up any further"), 0);
11722         return FALSE;
11723     }
11724     return LoadPosition(lastLoadPositionFP, positionNumber,
11725                         lastLoadPositionTitle);
11726 }
11727
11728 /* Load the nth position from the given file */
11729 int
11730 LoadPositionFromFile(filename, n, title)
11731      char *filename;
11732      int n;
11733      char *title;
11734 {
11735     FILE *f;
11736     char buf[MSG_SIZ];
11737
11738     if (strcmp(filename, "-") == 0) {
11739         return LoadPosition(stdin, n, "stdin");
11740     } else {
11741         f = fopen(filename, "rb");
11742         if (f == NULL) {
11743             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11744             DisplayError(buf, errno);
11745             return FALSE;
11746         } else {
11747             return LoadPosition(f, n, title);
11748         }
11749     }
11750 }
11751
11752 /* Load the nth position from the given open file, and close it */
11753 int
11754 LoadPosition(f, positionNumber, title)
11755      FILE *f;
11756      int positionNumber;
11757      char *title;
11758 {
11759     char *p, line[MSG_SIZ];
11760     Board initial_position;
11761     int i, j, fenMode, pn;
11762
11763     if (gameMode == Training )
11764         SetTrainingModeOff();
11765
11766     if (gameMode != BeginningOfGame) {
11767         Reset(FALSE, TRUE);
11768     }
11769     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11770         fclose(lastLoadPositionFP);
11771     }
11772     if (positionNumber == 0) positionNumber = 1;
11773     lastLoadPositionFP = f;
11774     lastLoadPositionNumber = positionNumber;
11775     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11776     if (first.pr == NoProc) {
11777       StartChessProgram(&first);
11778       InitChessProgram(&first, FALSE);
11779     }
11780     pn = positionNumber;
11781     if (positionNumber < 0) {
11782         /* Negative position number means to seek to that byte offset */
11783         if (fseek(f, -positionNumber, 0) == -1) {
11784             DisplayError(_("Can't seek on position file"), 0);
11785             return FALSE;
11786         };
11787         pn = 1;
11788     } else {
11789         if (fseek(f, 0, 0) == -1) {
11790             if (f == lastLoadPositionFP ?
11791                 positionNumber == lastLoadPositionNumber + 1 :
11792                 positionNumber == 1) {
11793                 pn = 1;
11794             } else {
11795                 DisplayError(_("Can't seek on position file"), 0);
11796                 return FALSE;
11797             }
11798         }
11799     }
11800     /* See if this file is FEN or old-style xboard */
11801     if (fgets(line, MSG_SIZ, f) == NULL) {
11802         DisplayError(_("Position not found in file"), 0);
11803         return FALSE;
11804     }
11805     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11806     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11807
11808     if (pn >= 2) {
11809         if (fenMode || line[0] == '#') pn--;
11810         while (pn > 0) {
11811             /* skip positions before number pn */
11812             if (fgets(line, MSG_SIZ, f) == NULL) {
11813                 Reset(TRUE, TRUE);
11814                 DisplayError(_("Position not found in file"), 0);
11815                 return FALSE;
11816             }
11817             if (fenMode || line[0] == '#') pn--;
11818         }
11819     }
11820
11821     if (fenMode) {
11822         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11823             DisplayError(_("Bad FEN position in file"), 0);
11824             return FALSE;
11825         }
11826     } else {
11827         (void) fgets(line, MSG_SIZ, f);
11828         (void) fgets(line, MSG_SIZ, f);
11829
11830         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11831             (void) fgets(line, MSG_SIZ, f);
11832             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11833                 if (*p == ' ')
11834                   continue;
11835                 initial_position[i][j++] = CharToPiece(*p);
11836             }
11837         }
11838
11839         blackPlaysFirst = FALSE;
11840         if (!feof(f)) {
11841             (void) fgets(line, MSG_SIZ, f);
11842             if (strncmp(line, "black", strlen("black"))==0)
11843               blackPlaysFirst = TRUE;
11844         }
11845     }
11846     startedFromSetupPosition = TRUE;
11847
11848     SendToProgram("force\n", &first);
11849     CopyBoard(boards[0], initial_position);
11850     if (blackPlaysFirst) {
11851         currentMove = forwardMostMove = backwardMostMove = 1;
11852         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11853         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11854         CopyBoard(boards[1], initial_position);
11855         DisplayMessage("", _("Black to play"));
11856     } else {
11857         currentMove = forwardMostMove = backwardMostMove = 0;
11858         DisplayMessage("", _("White to play"));
11859     }
11860     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11861     SendBoard(&first, forwardMostMove);
11862     if (appData.debugMode) {
11863 int i, j;
11864   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11865   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11866         fprintf(debugFP, "Load Position\n");
11867     }
11868
11869     if (positionNumber > 1) {
11870       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11871         DisplayTitle(line);
11872     } else {
11873         DisplayTitle(title);
11874     }
11875     gameMode = EditGame;
11876     ModeHighlight();
11877     ResetClocks();
11878     timeRemaining[0][1] = whiteTimeRemaining;
11879     timeRemaining[1][1] = blackTimeRemaining;
11880     DrawPosition(FALSE, boards[currentMove]);
11881
11882     return TRUE;
11883 }
11884
11885
11886 void
11887 CopyPlayerNameIntoFileName(dest, src)
11888      char **dest, *src;
11889 {
11890     while (*src != NULLCHAR && *src != ',') {
11891         if (*src == ' ') {
11892             *(*dest)++ = '_';
11893             src++;
11894         } else {
11895             *(*dest)++ = *src++;
11896         }
11897     }
11898 }
11899
11900 char *DefaultFileName(ext)
11901      char *ext;
11902 {
11903     static char def[MSG_SIZ];
11904     char *p;
11905
11906     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11907         p = def;
11908         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11909         *p++ = '-';
11910         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11911         *p++ = '.';
11912         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11913     } else {
11914         def[0] = NULLCHAR;
11915     }
11916     return def;
11917 }
11918
11919 /* Save the current game to the given file */
11920 int
11921 SaveGameToFile(filename, append)
11922      char *filename;
11923      int append;
11924 {
11925     FILE *f;
11926     char buf[MSG_SIZ];
11927     int result;
11928
11929     if (strcmp(filename, "-") == 0) {
11930         return SaveGame(stdout, 0, NULL);
11931     } else {
11932         f = fopen(filename, append ? "a" : "w");
11933         if (f == NULL) {
11934             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11935             DisplayError(buf, errno);
11936             return FALSE;
11937         } else {
11938             safeStrCpy(buf, lastMsg, MSG_SIZ);
11939             DisplayMessage(_("Waiting for access to save file"), "");
11940             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11941             DisplayMessage(_("Saving game"), "");
11942             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11943             result = SaveGame(f, 0, NULL);
11944             DisplayMessage(buf, "");
11945             return result;
11946         }
11947     }
11948 }
11949
11950 char *
11951 SavePart(str)
11952      char *str;
11953 {
11954     static char buf[MSG_SIZ];
11955     char *p;
11956
11957     p = strchr(str, ' ');
11958     if (p == NULL) return str;
11959     strncpy(buf, str, p - str);
11960     buf[p - str] = NULLCHAR;
11961     return buf;
11962 }
11963
11964 #define PGN_MAX_LINE 75
11965
11966 #define PGN_SIDE_WHITE  0
11967 #define PGN_SIDE_BLACK  1
11968
11969 /* [AS] */
11970 static int FindFirstMoveOutOfBook( int side )
11971 {
11972     int result = -1;
11973
11974     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11975         int index = backwardMostMove;
11976         int has_book_hit = 0;
11977
11978         if( (index % 2) != side ) {
11979             index++;
11980         }
11981
11982         while( index < forwardMostMove ) {
11983             /* Check to see if engine is in book */
11984             int depth = pvInfoList[index].depth;
11985             int score = pvInfoList[index].score;
11986             int in_book = 0;
11987
11988             if( depth <= 2 ) {
11989                 in_book = 1;
11990             }
11991             else if( score == 0 && depth == 63 ) {
11992                 in_book = 1; /* Zappa */
11993             }
11994             else if( score == 2 && depth == 99 ) {
11995                 in_book = 1; /* Abrok */
11996             }
11997
11998             has_book_hit += in_book;
11999
12000             if( ! in_book ) {
12001                 result = index;
12002
12003                 break;
12004             }
12005
12006             index += 2;
12007         }
12008     }
12009
12010     return result;
12011 }
12012
12013 /* [AS] */
12014 void GetOutOfBookInfo( char * buf )
12015 {
12016     int oob[2];
12017     int i;
12018     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12019
12020     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12021     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12022
12023     *buf = '\0';
12024
12025     if( oob[0] >= 0 || oob[1] >= 0 ) {
12026         for( i=0; i<2; i++ ) {
12027             int idx = oob[i];
12028
12029             if( idx >= 0 ) {
12030                 if( i > 0 && oob[0] >= 0 ) {
12031                     strcat( buf, "   " );
12032                 }
12033
12034                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12035                 sprintf( buf+strlen(buf), "%s%.2f",
12036                     pvInfoList[idx].score >= 0 ? "+" : "",
12037                     pvInfoList[idx].score / 100.0 );
12038             }
12039         }
12040     }
12041 }
12042
12043 /* Save game in PGN style and close the file */
12044 int
12045 SaveGamePGN(f)
12046      FILE *f;
12047 {
12048     int i, offset, linelen, newblock;
12049     time_t tm;
12050 //    char *movetext;
12051     char numtext[32];
12052     int movelen, numlen, blank;
12053     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12054
12055     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12056
12057     tm = time((time_t *) NULL);
12058
12059     PrintPGNTags(f, &gameInfo);
12060
12061     if (backwardMostMove > 0 || startedFromSetupPosition) {
12062         char *fen = PositionToFEN(backwardMostMove, NULL);
12063         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12064         fprintf(f, "\n{--------------\n");
12065         PrintPosition(f, backwardMostMove);
12066         fprintf(f, "--------------}\n");
12067         free(fen);
12068     }
12069     else {
12070         /* [AS] Out of book annotation */
12071         if( appData.saveOutOfBookInfo ) {
12072             char buf[64];
12073
12074             GetOutOfBookInfo( buf );
12075
12076             if( buf[0] != '\0' ) {
12077                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12078             }
12079         }
12080
12081         fprintf(f, "\n");
12082     }
12083
12084     i = backwardMostMove;
12085     linelen = 0;
12086     newblock = TRUE;
12087
12088     while (i < forwardMostMove) {
12089         /* Print comments preceding this move */
12090         if (commentList[i] != NULL) {
12091             if (linelen > 0) fprintf(f, "\n");
12092             fprintf(f, "%s", commentList[i]);
12093             linelen = 0;
12094             newblock = TRUE;
12095         }
12096
12097         /* Format move number */
12098         if ((i % 2) == 0)
12099           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12100         else
12101           if (newblock)
12102             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12103           else
12104             numtext[0] = NULLCHAR;
12105
12106         numlen = strlen(numtext);
12107         newblock = FALSE;
12108
12109         /* Print move number */
12110         blank = linelen > 0 && numlen > 0;
12111         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12112             fprintf(f, "\n");
12113             linelen = 0;
12114             blank = 0;
12115         }
12116         if (blank) {
12117             fprintf(f, " ");
12118             linelen++;
12119         }
12120         fprintf(f, "%s", numtext);
12121         linelen += numlen;
12122
12123         /* Get move */
12124         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12125         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12126
12127         /* Print move */
12128         blank = linelen > 0 && movelen > 0;
12129         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12130             fprintf(f, "\n");
12131             linelen = 0;
12132             blank = 0;
12133         }
12134         if (blank) {
12135             fprintf(f, " ");
12136             linelen++;
12137         }
12138         fprintf(f, "%s", move_buffer);
12139         linelen += movelen;
12140
12141         /* [AS] Add PV info if present */
12142         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12143             /* [HGM] add time */
12144             char buf[MSG_SIZ]; int seconds;
12145
12146             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12147
12148             if( seconds <= 0)
12149               buf[0] = 0;
12150             else
12151               if( seconds < 30 )
12152                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12153               else
12154                 {
12155                   seconds = (seconds + 4)/10; // round to full seconds
12156                   if( seconds < 60 )
12157                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12158                   else
12159                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12160                 }
12161
12162             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12163                       pvInfoList[i].score >= 0 ? "+" : "",
12164                       pvInfoList[i].score / 100.0,
12165                       pvInfoList[i].depth,
12166                       buf );
12167
12168             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12169
12170             /* Print score/depth */
12171             blank = linelen > 0 && movelen > 0;
12172             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12173                 fprintf(f, "\n");
12174                 linelen = 0;
12175                 blank = 0;
12176             }
12177             if (blank) {
12178                 fprintf(f, " ");
12179                 linelen++;
12180             }
12181             fprintf(f, "%s", move_buffer);
12182             linelen += movelen;
12183         }
12184
12185         i++;
12186     }
12187
12188     /* Start a new line */
12189     if (linelen > 0) fprintf(f, "\n");
12190
12191     /* Print comments after last move */
12192     if (commentList[i] != NULL) {
12193         fprintf(f, "%s\n", commentList[i]);
12194     }
12195
12196     /* Print result */
12197     if (gameInfo.resultDetails != NULL &&
12198         gameInfo.resultDetails[0] != NULLCHAR) {
12199         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12200                 PGNResult(gameInfo.result));
12201     } else {
12202         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12203     }
12204
12205     fclose(f);
12206     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12207     return TRUE;
12208 }
12209
12210 /* Save game in old style and close the file */
12211 int
12212 SaveGameOldStyle(f)
12213      FILE *f;
12214 {
12215     int i, offset;
12216     time_t tm;
12217
12218     tm = time((time_t *) NULL);
12219
12220     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12221     PrintOpponents(f);
12222
12223     if (backwardMostMove > 0 || startedFromSetupPosition) {
12224         fprintf(f, "\n[--------------\n");
12225         PrintPosition(f, backwardMostMove);
12226         fprintf(f, "--------------]\n");
12227     } else {
12228         fprintf(f, "\n");
12229     }
12230
12231     i = backwardMostMove;
12232     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12233
12234     while (i < forwardMostMove) {
12235         if (commentList[i] != NULL) {
12236             fprintf(f, "[%s]\n", commentList[i]);
12237         }
12238
12239         if ((i % 2) == 1) {
12240             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12241             i++;
12242         } else {
12243             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12244             i++;
12245             if (commentList[i] != NULL) {
12246                 fprintf(f, "\n");
12247                 continue;
12248             }
12249             if (i >= forwardMostMove) {
12250                 fprintf(f, "\n");
12251                 break;
12252             }
12253             fprintf(f, "%s\n", parseList[i]);
12254             i++;
12255         }
12256     }
12257
12258     if (commentList[i] != NULL) {
12259         fprintf(f, "[%s]\n", commentList[i]);
12260     }
12261
12262     /* This isn't really the old style, but it's close enough */
12263     if (gameInfo.resultDetails != NULL &&
12264         gameInfo.resultDetails[0] != NULLCHAR) {
12265         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12266                 gameInfo.resultDetails);
12267     } else {
12268         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12269     }
12270
12271     fclose(f);
12272     return TRUE;
12273 }
12274
12275 /* Save the current game to open file f and close the file */
12276 int
12277 SaveGame(f, dummy, dummy2)
12278      FILE *f;
12279      int dummy;
12280      char *dummy2;
12281 {
12282     if (gameMode == EditPosition) EditPositionDone(TRUE);
12283     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12284     if (appData.oldSaveStyle)
12285       return SaveGameOldStyle(f);
12286     else
12287       return SaveGamePGN(f);
12288 }
12289
12290 /* Save the current position to the given file */
12291 int
12292 SavePositionToFile(filename)
12293      char *filename;
12294 {
12295     FILE *f;
12296     char buf[MSG_SIZ];
12297
12298     if (strcmp(filename, "-") == 0) {
12299         return SavePosition(stdout, 0, NULL);
12300     } else {
12301         f = fopen(filename, "a");
12302         if (f == NULL) {
12303             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12304             DisplayError(buf, errno);
12305             return FALSE;
12306         } else {
12307             safeStrCpy(buf, lastMsg, MSG_SIZ);
12308             DisplayMessage(_("Waiting for access to save file"), "");
12309             flock(fileno(f), LOCK_EX); // [HGM] lock
12310             DisplayMessage(_("Saving position"), "");
12311             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12312             SavePosition(f, 0, NULL);
12313             DisplayMessage(buf, "");
12314             return TRUE;
12315         }
12316     }
12317 }
12318
12319 /* Save the current position to the given open file and close the file */
12320 int
12321 SavePosition(f, dummy, dummy2)
12322      FILE *f;
12323      int dummy;
12324      char *dummy2;
12325 {
12326     time_t tm;
12327     char *fen;
12328
12329     if (gameMode == EditPosition) EditPositionDone(TRUE);
12330     if (appData.oldSaveStyle) {
12331         tm = time((time_t *) NULL);
12332
12333         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12334         PrintOpponents(f);
12335         fprintf(f, "[--------------\n");
12336         PrintPosition(f, currentMove);
12337         fprintf(f, "--------------]\n");
12338     } else {
12339         fen = PositionToFEN(currentMove, NULL);
12340         fprintf(f, "%s\n", fen);
12341         free(fen);
12342     }
12343     fclose(f);
12344     return TRUE;
12345 }
12346
12347 void
12348 ReloadCmailMsgEvent(unregister)
12349      int unregister;
12350 {
12351 #if !WIN32
12352     static char *inFilename = NULL;
12353     static char *outFilename;
12354     int i;
12355     struct stat inbuf, outbuf;
12356     int status;
12357
12358     /* Any registered moves are unregistered if unregister is set, */
12359     /* i.e. invoked by the signal handler */
12360     if (unregister) {
12361         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12362             cmailMoveRegistered[i] = FALSE;
12363             if (cmailCommentList[i] != NULL) {
12364                 free(cmailCommentList[i]);
12365                 cmailCommentList[i] = NULL;
12366             }
12367         }
12368         nCmailMovesRegistered = 0;
12369     }
12370
12371     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12372         cmailResult[i] = CMAIL_NOT_RESULT;
12373     }
12374     nCmailResults = 0;
12375
12376     if (inFilename == NULL) {
12377         /* Because the filenames are static they only get malloced once  */
12378         /* and they never get freed                                      */
12379         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12380         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12381
12382         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12383         sprintf(outFilename, "%s.out", appData.cmailGameName);
12384     }
12385
12386     status = stat(outFilename, &outbuf);
12387     if (status < 0) {
12388         cmailMailedMove = FALSE;
12389     } else {
12390         status = stat(inFilename, &inbuf);
12391         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12392     }
12393
12394     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12395        counts the games, notes how each one terminated, etc.
12396
12397        It would be nice to remove this kludge and instead gather all
12398        the information while building the game list.  (And to keep it
12399        in the game list nodes instead of having a bunch of fixed-size
12400        parallel arrays.)  Note this will require getting each game's
12401        termination from the PGN tags, as the game list builder does
12402        not process the game moves.  --mann
12403        */
12404     cmailMsgLoaded = TRUE;
12405     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12406
12407     /* Load first game in the file or popup game menu */
12408     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12409
12410 #endif /* !WIN32 */
12411     return;
12412 }
12413
12414 int
12415 RegisterMove()
12416 {
12417     FILE *f;
12418     char string[MSG_SIZ];
12419
12420     if (   cmailMailedMove
12421         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12422         return TRUE;            /* Allow free viewing  */
12423     }
12424
12425     /* Unregister move to ensure that we don't leave RegisterMove        */
12426     /* with the move registered when the conditions for registering no   */
12427     /* longer hold                                                       */
12428     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12429         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12430         nCmailMovesRegistered --;
12431
12432         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12433           {
12434               free(cmailCommentList[lastLoadGameNumber - 1]);
12435               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12436           }
12437     }
12438
12439     if (cmailOldMove == -1) {
12440         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12441         return FALSE;
12442     }
12443
12444     if (currentMove > cmailOldMove + 1) {
12445         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12446         return FALSE;
12447     }
12448
12449     if (currentMove < cmailOldMove) {
12450         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12451         return FALSE;
12452     }
12453
12454     if (forwardMostMove > currentMove) {
12455         /* Silently truncate extra moves */
12456         TruncateGame();
12457     }
12458
12459     if (   (currentMove == cmailOldMove + 1)
12460         || (   (currentMove == cmailOldMove)
12461             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12462                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12463         if (gameInfo.result != GameUnfinished) {
12464             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12465         }
12466
12467         if (commentList[currentMove] != NULL) {
12468             cmailCommentList[lastLoadGameNumber - 1]
12469               = StrSave(commentList[currentMove]);
12470         }
12471         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12472
12473         if (appData.debugMode)
12474           fprintf(debugFP, "Saving %s for game %d\n",
12475                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12476
12477         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12478
12479         f = fopen(string, "w");
12480         if (appData.oldSaveStyle) {
12481             SaveGameOldStyle(f); /* also closes the file */
12482
12483             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12484             f = fopen(string, "w");
12485             SavePosition(f, 0, NULL); /* also closes the file */
12486         } else {
12487             fprintf(f, "{--------------\n");
12488             PrintPosition(f, currentMove);
12489             fprintf(f, "--------------}\n\n");
12490
12491             SaveGame(f, 0, NULL); /* also closes the file*/
12492         }
12493
12494         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12495         nCmailMovesRegistered ++;
12496     } else if (nCmailGames == 1) {
12497         DisplayError(_("You have not made a move yet"), 0);
12498         return FALSE;
12499     }
12500
12501     return TRUE;
12502 }
12503
12504 void
12505 MailMoveEvent()
12506 {
12507 #if !WIN32
12508     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12509     FILE *commandOutput;
12510     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12511     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12512     int nBuffers;
12513     int i;
12514     int archived;
12515     char *arcDir;
12516
12517     if (! cmailMsgLoaded) {
12518         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12519         return;
12520     }
12521
12522     if (nCmailGames == nCmailResults) {
12523         DisplayError(_("No unfinished games"), 0);
12524         return;
12525     }
12526
12527 #if CMAIL_PROHIBIT_REMAIL
12528     if (cmailMailedMove) {
12529       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);
12530         DisplayError(msg, 0);
12531         return;
12532     }
12533 #endif
12534
12535     if (! (cmailMailedMove || RegisterMove())) return;
12536
12537     if (   cmailMailedMove
12538         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12539       snprintf(string, MSG_SIZ, partCommandString,
12540                appData.debugMode ? " -v" : "", appData.cmailGameName);
12541         commandOutput = popen(string, "r");
12542
12543         if (commandOutput == NULL) {
12544             DisplayError(_("Failed to invoke cmail"), 0);
12545         } else {
12546             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12547                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12548             }
12549             if (nBuffers > 1) {
12550                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12551                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12552                 nBytes = MSG_SIZ - 1;
12553             } else {
12554                 (void) memcpy(msg, buffer, nBytes);
12555             }
12556             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12557
12558             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12559                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12560
12561                 archived = TRUE;
12562                 for (i = 0; i < nCmailGames; i ++) {
12563                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12564                         archived = FALSE;
12565                     }
12566                 }
12567                 if (   archived
12568                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12569                         != NULL)) {
12570                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12571                            arcDir,
12572                            appData.cmailGameName,
12573                            gameInfo.date);
12574                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12575                     cmailMsgLoaded = FALSE;
12576                 }
12577             }
12578
12579             DisplayInformation(msg);
12580             pclose(commandOutput);
12581         }
12582     } else {
12583         if ((*cmailMsg) != '\0') {
12584             DisplayInformation(cmailMsg);
12585         }
12586     }
12587
12588     return;
12589 #endif /* !WIN32 */
12590 }
12591
12592 char *
12593 CmailMsg()
12594 {
12595 #if WIN32
12596     return NULL;
12597 #else
12598     int  prependComma = 0;
12599     char number[5];
12600     char string[MSG_SIZ];       /* Space for game-list */
12601     int  i;
12602
12603     if (!cmailMsgLoaded) return "";
12604
12605     if (cmailMailedMove) {
12606       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12607     } else {
12608         /* Create a list of games left */
12609       snprintf(string, MSG_SIZ, "[");
12610         for (i = 0; i < nCmailGames; i ++) {
12611             if (! (   cmailMoveRegistered[i]
12612                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12613                 if (prependComma) {
12614                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12615                 } else {
12616                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12617                     prependComma = 1;
12618                 }
12619
12620                 strcat(string, number);
12621             }
12622         }
12623         strcat(string, "]");
12624
12625         if (nCmailMovesRegistered + nCmailResults == 0) {
12626             switch (nCmailGames) {
12627               case 1:
12628                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12629                 break;
12630
12631               case 2:
12632                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12633                 break;
12634
12635               default:
12636                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12637                          nCmailGames);
12638                 break;
12639             }
12640         } else {
12641             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12642               case 1:
12643                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12644                          string);
12645                 break;
12646
12647               case 0:
12648                 if (nCmailResults == nCmailGames) {
12649                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12650                 } else {
12651                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12652                 }
12653                 break;
12654
12655               default:
12656                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12657                          string);
12658             }
12659         }
12660     }
12661     return cmailMsg;
12662 #endif /* WIN32 */
12663 }
12664
12665 void
12666 ResetGameEvent()
12667 {
12668     if (gameMode == Training)
12669       SetTrainingModeOff();
12670
12671     Reset(TRUE, TRUE);
12672     cmailMsgLoaded = FALSE;
12673     if (appData.icsActive) {
12674       SendToICS(ics_prefix);
12675       SendToICS("refresh\n");
12676     }
12677 }
12678
12679 void
12680 ExitEvent(status)
12681      int status;
12682 {
12683     exiting++;
12684     if (exiting > 2) {
12685       /* Give up on clean exit */
12686       exit(status);
12687     }
12688     if (exiting > 1) {
12689       /* Keep trying for clean exit */
12690       return;
12691     }
12692
12693     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12694
12695     if (telnetISR != NULL) {
12696       RemoveInputSource(telnetISR);
12697     }
12698     if (icsPR != NoProc) {
12699       DestroyChildProcess(icsPR, TRUE);
12700     }
12701
12702     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12703     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12704
12705     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12706     /* make sure this other one finishes before killing it!                  */
12707     if(endingGame) { int count = 0;
12708         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12709         while(endingGame && count++ < 10) DoSleep(1);
12710         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12711     }
12712
12713     /* Kill off chess programs */
12714     if (first.pr != NoProc) {
12715         ExitAnalyzeMode();
12716
12717         DoSleep( appData.delayBeforeQuit );
12718         SendToProgram("quit\n", &first);
12719         DoSleep( appData.delayAfterQuit );
12720         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12721     }
12722     if (second.pr != NoProc) {
12723         DoSleep( appData.delayBeforeQuit );
12724         SendToProgram("quit\n", &second);
12725         DoSleep( appData.delayAfterQuit );
12726         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12727     }
12728     if (first.isr != NULL) {
12729         RemoveInputSource(first.isr);
12730     }
12731     if (second.isr != NULL) {
12732         RemoveInputSource(second.isr);
12733     }
12734
12735     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12736     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12737
12738     ShutDownFrontEnd();
12739     exit(status);
12740 }
12741
12742 void
12743 PauseEvent()
12744 {
12745     if (appData.debugMode)
12746         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12747     if (pausing) {
12748         pausing = FALSE;
12749         ModeHighlight();
12750         if (gameMode == MachinePlaysWhite ||
12751             gameMode == MachinePlaysBlack) {
12752             StartClocks();
12753         } else {
12754             DisplayBothClocks();
12755         }
12756         if (gameMode == PlayFromGameFile) {
12757             if (appData.timeDelay >= 0)
12758                 AutoPlayGameLoop();
12759         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12760             Reset(FALSE, TRUE);
12761             SendToICS(ics_prefix);
12762             SendToICS("refresh\n");
12763         } else if (currentMove < forwardMostMove) {
12764             ForwardInner(forwardMostMove);
12765         }
12766         pauseExamInvalid = FALSE;
12767     } else {
12768         switch (gameMode) {
12769           default:
12770             return;
12771           case IcsExamining:
12772             pauseExamForwardMostMove = forwardMostMove;
12773             pauseExamInvalid = FALSE;
12774             /* fall through */
12775           case IcsObserving:
12776           case IcsPlayingWhite:
12777           case IcsPlayingBlack:
12778             pausing = TRUE;
12779             ModeHighlight();
12780             return;
12781           case PlayFromGameFile:
12782             (void) StopLoadGameTimer();
12783             pausing = TRUE;
12784             ModeHighlight();
12785             break;
12786           case BeginningOfGame:
12787             if (appData.icsActive) return;
12788             /* else fall through */
12789           case MachinePlaysWhite:
12790           case MachinePlaysBlack:
12791           case TwoMachinesPlay:
12792             if (forwardMostMove == 0)
12793               return;           /* don't pause if no one has moved */
12794             if ((gameMode == MachinePlaysWhite &&
12795                  !WhiteOnMove(forwardMostMove)) ||
12796                 (gameMode == MachinePlaysBlack &&
12797                  WhiteOnMove(forwardMostMove))) {
12798                 StopClocks();
12799             }
12800             pausing = TRUE;
12801             ModeHighlight();
12802             break;
12803         }
12804     }
12805 }
12806
12807 void
12808 EditCommentEvent()
12809 {
12810     char title[MSG_SIZ];
12811
12812     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12813       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12814     } else {
12815       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12816                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12817                parseList[currentMove - 1]);
12818     }
12819
12820     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12821 }
12822
12823
12824 void
12825 EditTagsEvent()
12826 {
12827     char *tags = PGNTags(&gameInfo);
12828     bookUp = FALSE;
12829     EditTagsPopUp(tags, NULL);
12830     free(tags);
12831 }
12832
12833 void
12834 AnalyzeModeEvent()
12835 {
12836     if (appData.noChessProgram || gameMode == AnalyzeMode)
12837       return;
12838
12839     if (gameMode != AnalyzeFile) {
12840         if (!appData.icsEngineAnalyze) {
12841                EditGameEvent();
12842                if (gameMode != EditGame) return;
12843         }
12844         ResurrectChessProgram();
12845         SendToProgram("analyze\n", &first);
12846         first.analyzing = TRUE;
12847         /*first.maybeThinking = TRUE;*/
12848         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12849         EngineOutputPopUp();
12850     }
12851     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12852     pausing = FALSE;
12853     ModeHighlight();
12854     SetGameInfo();
12855
12856     StartAnalysisClock();
12857     GetTimeMark(&lastNodeCountTime);
12858     lastNodeCount = 0;
12859 }
12860
12861 void
12862 AnalyzeFileEvent()
12863 {
12864     if (appData.noChessProgram || gameMode == AnalyzeFile)
12865       return;
12866
12867     if (gameMode != AnalyzeMode) {
12868         EditGameEvent();
12869         if (gameMode != EditGame) return;
12870         ResurrectChessProgram();
12871         SendToProgram("analyze\n", &first);
12872         first.analyzing = TRUE;
12873         /*first.maybeThinking = TRUE;*/
12874         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12875         EngineOutputPopUp();
12876     }
12877     gameMode = AnalyzeFile;
12878     pausing = FALSE;
12879     ModeHighlight();
12880     SetGameInfo();
12881
12882     StartAnalysisClock();
12883     GetTimeMark(&lastNodeCountTime);
12884     lastNodeCount = 0;
12885     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
12886 }
12887
12888 void
12889 MachineWhiteEvent()
12890 {
12891     char buf[MSG_SIZ];
12892     char *bookHit = NULL;
12893
12894     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12895       return;
12896
12897
12898     if (gameMode == PlayFromGameFile ||
12899         gameMode == TwoMachinesPlay  ||
12900         gameMode == Training         ||
12901         gameMode == AnalyzeMode      ||
12902         gameMode == EndOfGame)
12903         EditGameEvent();
12904
12905     if (gameMode == EditPosition)
12906         EditPositionDone(TRUE);
12907
12908     if (!WhiteOnMove(currentMove)) {
12909         DisplayError(_("It is not White's turn"), 0);
12910         return;
12911     }
12912
12913     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12914       ExitAnalyzeMode();
12915
12916     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12917         gameMode == AnalyzeFile)
12918         TruncateGame();
12919
12920     ResurrectChessProgram();    /* in case it isn't running */
12921     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12922         gameMode = MachinePlaysWhite;
12923         ResetClocks();
12924     } else
12925     gameMode = MachinePlaysWhite;
12926     pausing = FALSE;
12927     ModeHighlight();
12928     SetGameInfo();
12929     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12930     DisplayTitle(buf);
12931     if (first.sendName) {
12932       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12933       SendToProgram(buf, &first);
12934     }
12935     if (first.sendTime) {
12936       if (first.useColors) {
12937         SendToProgram("black\n", &first); /*gnu kludge*/
12938       }
12939       SendTimeRemaining(&first, TRUE);
12940     }
12941     if (first.useColors) {
12942       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12943     }
12944     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12945     SetMachineThinkingEnables();
12946     first.maybeThinking = TRUE;
12947     StartClocks();
12948     firstMove = FALSE;
12949
12950     if (appData.autoFlipView && !flipView) {
12951       flipView = !flipView;
12952       DrawPosition(FALSE, NULL);
12953       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12954     }
12955
12956     if(bookHit) { // [HGM] book: simulate book reply
12957         static char bookMove[MSG_SIZ]; // a bit generous?
12958
12959         programStats.nodes = programStats.depth = programStats.time =
12960         programStats.score = programStats.got_only_move = 0;
12961         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12962
12963         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12964         strcat(bookMove, bookHit);
12965         HandleMachineMove(bookMove, &first);
12966     }
12967 }
12968
12969 void
12970 MachineBlackEvent()
12971 {
12972   char buf[MSG_SIZ];
12973   char *bookHit = NULL;
12974
12975     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12976         return;
12977
12978
12979     if (gameMode == PlayFromGameFile ||
12980         gameMode == TwoMachinesPlay  ||
12981         gameMode == Training         ||
12982         gameMode == AnalyzeMode      ||
12983         gameMode == EndOfGame)
12984         EditGameEvent();
12985
12986     if (gameMode == EditPosition)
12987         EditPositionDone(TRUE);
12988
12989     if (WhiteOnMove(currentMove)) {
12990         DisplayError(_("It is not Black's turn"), 0);
12991         return;
12992     }
12993
12994     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12995       ExitAnalyzeMode();
12996
12997     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12998         gameMode == AnalyzeFile)
12999         TruncateGame();
13000
13001     ResurrectChessProgram();    /* in case it isn't running */
13002     gameMode = MachinePlaysBlack;
13003     pausing = FALSE;
13004     ModeHighlight();
13005     SetGameInfo();
13006     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13007     DisplayTitle(buf);
13008     if (first.sendName) {
13009       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13010       SendToProgram(buf, &first);
13011     }
13012     if (first.sendTime) {
13013       if (first.useColors) {
13014         SendToProgram("white\n", &first); /*gnu kludge*/
13015       }
13016       SendTimeRemaining(&first, FALSE);
13017     }
13018     if (first.useColors) {
13019       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13020     }
13021     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13022     SetMachineThinkingEnables();
13023     first.maybeThinking = TRUE;
13024     StartClocks();
13025
13026     if (appData.autoFlipView && flipView) {
13027       flipView = !flipView;
13028       DrawPosition(FALSE, NULL);
13029       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13030     }
13031     if(bookHit) { // [HGM] book: simulate book reply
13032         static char bookMove[MSG_SIZ]; // a bit generous?
13033
13034         programStats.nodes = programStats.depth = programStats.time =
13035         programStats.score = programStats.got_only_move = 0;
13036         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13037
13038         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13039         strcat(bookMove, bookHit);
13040         HandleMachineMove(bookMove, &first);
13041     }
13042 }
13043
13044
13045 void
13046 DisplayTwoMachinesTitle()
13047 {
13048     char buf[MSG_SIZ];
13049     if (appData.matchGames > 0) {
13050         if(appData.tourneyFile[0]) {
13051           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13052                    gameInfo.white, gameInfo.black,
13053                    nextGame+1, appData.matchGames+1,
13054                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13055         } else 
13056         if (first.twoMachinesColor[0] == 'w') {
13057           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13058                    gameInfo.white, gameInfo.black,
13059                    first.matchWins, second.matchWins,
13060                    matchGame - 1 - (first.matchWins + second.matchWins));
13061         } else {
13062           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13063                    gameInfo.white, gameInfo.black,
13064                    second.matchWins, first.matchWins,
13065                    matchGame - 1 - (first.matchWins + second.matchWins));
13066         }
13067     } else {
13068       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13069     }
13070     DisplayTitle(buf);
13071 }
13072
13073 void
13074 SettingsMenuIfReady()
13075 {
13076   if (second.lastPing != second.lastPong) {
13077     DisplayMessage("", _("Waiting for second chess program"));
13078     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13079     return;
13080   }
13081   ThawUI();
13082   DisplayMessage("", "");
13083   SettingsPopUp(&second);
13084 }
13085
13086 int
13087 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13088 {
13089     char buf[MSG_SIZ];
13090     if (cps->pr == NULL) {
13091         StartChessProgram(cps);
13092         if (cps->protocolVersion == 1) {
13093           retry();
13094         } else {
13095           /* kludge: allow timeout for initial "feature" command */
13096           FreezeUI();
13097           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13098           DisplayMessage("", buf);
13099           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13100         }
13101         return 1;
13102     }
13103     return 0;
13104 }
13105
13106 void
13107 TwoMachinesEvent P((void))
13108 {
13109     int i;
13110     char buf[MSG_SIZ];
13111     ChessProgramState *onmove;
13112     char *bookHit = NULL;
13113     static int stalling = 0;
13114     TimeMark now;
13115     long wait;
13116
13117     if (appData.noChessProgram) return;
13118
13119     switch (gameMode) {
13120       case TwoMachinesPlay:
13121         return;
13122       case MachinePlaysWhite:
13123       case MachinePlaysBlack:
13124         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13125             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13126             return;
13127         }
13128         /* fall through */
13129       case BeginningOfGame:
13130       case PlayFromGameFile:
13131       case EndOfGame:
13132         EditGameEvent();
13133         if (gameMode != EditGame) return;
13134         break;
13135       case EditPosition:
13136         EditPositionDone(TRUE);
13137         break;
13138       case AnalyzeMode:
13139       case AnalyzeFile:
13140         ExitAnalyzeMode();
13141         break;
13142       case EditGame:
13143       default:
13144         break;
13145     }
13146
13147 //    forwardMostMove = currentMove;
13148     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13149
13150     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13151
13152     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13153     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13154       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13155       return;
13156     }
13157     if(!stalling) {
13158       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13159       SendToProgram("force\n", &second);
13160       stalling = 1;
13161       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13162       return;
13163     }
13164     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13165     if(appData.matchPause>10000 || appData.matchPause<10)
13166                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13167     wait = SubtractTimeMarks(&now, &pauseStart);
13168     if(wait < appData.matchPause) {
13169         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13170         return;
13171     }
13172     stalling = 0;
13173     DisplayMessage("", "");
13174     if (startedFromSetupPosition) {
13175         SendBoard(&second, backwardMostMove);
13176     if (appData.debugMode) {
13177         fprintf(debugFP, "Two Machines\n");
13178     }
13179     }
13180     for (i = backwardMostMove; i < forwardMostMove; i++) {
13181         SendMoveToProgram(i, &second);
13182     }
13183
13184     gameMode = TwoMachinesPlay;
13185     pausing = FALSE;
13186     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13187     SetGameInfo();
13188     DisplayTwoMachinesTitle();
13189     firstMove = TRUE;
13190     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13191         onmove = &first;
13192     } else {
13193         onmove = &second;
13194     }
13195     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13196     SendToProgram(first.computerString, &first);
13197     if (first.sendName) {
13198       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13199       SendToProgram(buf, &first);
13200     }
13201     SendToProgram(second.computerString, &second);
13202     if (second.sendName) {
13203       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13204       SendToProgram(buf, &second);
13205     }
13206
13207     ResetClocks();
13208     if (!first.sendTime || !second.sendTime) {
13209         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13210         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13211     }
13212     if (onmove->sendTime) {
13213       if (onmove->useColors) {
13214         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13215       }
13216       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13217     }
13218     if (onmove->useColors) {
13219       SendToProgram(onmove->twoMachinesColor, onmove);
13220     }
13221     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13222 //    SendToProgram("go\n", onmove);
13223     onmove->maybeThinking = TRUE;
13224     SetMachineThinkingEnables();
13225
13226     StartClocks();
13227
13228     if(bookHit) { // [HGM] book: simulate book reply
13229         static char bookMove[MSG_SIZ]; // a bit generous?
13230
13231         programStats.nodes = programStats.depth = programStats.time =
13232         programStats.score = programStats.got_only_move = 0;
13233         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13234
13235         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13236         strcat(bookMove, bookHit);
13237         savedMessage = bookMove; // args for deferred call
13238         savedState = onmove;
13239         ScheduleDelayedEvent(DeferredBookMove, 1);
13240     }
13241 }
13242
13243 void
13244 TrainingEvent()
13245 {
13246     if (gameMode == Training) {
13247       SetTrainingModeOff();
13248       gameMode = PlayFromGameFile;
13249       DisplayMessage("", _("Training mode off"));
13250     } else {
13251       gameMode = Training;
13252       animateTraining = appData.animate;
13253
13254       /* make sure we are not already at the end of the game */
13255       if (currentMove < forwardMostMove) {
13256         SetTrainingModeOn();
13257         DisplayMessage("", _("Training mode on"));
13258       } else {
13259         gameMode = PlayFromGameFile;
13260         DisplayError(_("Already at end of game"), 0);
13261       }
13262     }
13263     ModeHighlight();
13264 }
13265
13266 void
13267 IcsClientEvent()
13268 {
13269     if (!appData.icsActive) return;
13270     switch (gameMode) {
13271       case IcsPlayingWhite:
13272       case IcsPlayingBlack:
13273       case IcsObserving:
13274       case IcsIdle:
13275       case BeginningOfGame:
13276       case IcsExamining:
13277         return;
13278
13279       case EditGame:
13280         break;
13281
13282       case EditPosition:
13283         EditPositionDone(TRUE);
13284         break;
13285
13286       case AnalyzeMode:
13287       case AnalyzeFile:
13288         ExitAnalyzeMode();
13289         break;
13290
13291       default:
13292         EditGameEvent();
13293         break;
13294     }
13295
13296     gameMode = IcsIdle;
13297     ModeHighlight();
13298     return;
13299 }
13300
13301
13302 void
13303 EditGameEvent()
13304 {
13305     int i;
13306
13307     switch (gameMode) {
13308       case Training:
13309         SetTrainingModeOff();
13310         break;
13311       case MachinePlaysWhite:
13312       case MachinePlaysBlack:
13313       case BeginningOfGame:
13314         SendToProgram("force\n", &first);
13315         SetUserThinkingEnables();
13316         break;
13317       case PlayFromGameFile:
13318         (void) StopLoadGameTimer();
13319         if (gameFileFP != NULL) {
13320             gameFileFP = NULL;
13321         }
13322         break;
13323       case EditPosition:
13324         EditPositionDone(TRUE);
13325         break;
13326       case AnalyzeMode:
13327       case AnalyzeFile:
13328         ExitAnalyzeMode();
13329         SendToProgram("force\n", &first);
13330         break;
13331       case TwoMachinesPlay:
13332         GameEnds(EndOfFile, NULL, GE_PLAYER);
13333         ResurrectChessProgram();
13334         SetUserThinkingEnables();
13335         break;
13336       case EndOfGame:
13337         ResurrectChessProgram();
13338         break;
13339       case IcsPlayingBlack:
13340       case IcsPlayingWhite:
13341         DisplayError(_("Warning: You are still playing a game"), 0);
13342         break;
13343       case IcsObserving:
13344         DisplayError(_("Warning: You are still observing a game"), 0);
13345         break;
13346       case IcsExamining:
13347         DisplayError(_("Warning: You are still examining a game"), 0);
13348         break;
13349       case IcsIdle:
13350         break;
13351       case EditGame:
13352       default:
13353         return;
13354     }
13355
13356     pausing = FALSE;
13357     StopClocks();
13358     first.offeredDraw = second.offeredDraw = 0;
13359
13360     if (gameMode == PlayFromGameFile) {
13361         whiteTimeRemaining = timeRemaining[0][currentMove];
13362         blackTimeRemaining = timeRemaining[1][currentMove];
13363         DisplayTitle("");
13364     }
13365
13366     if (gameMode == MachinePlaysWhite ||
13367         gameMode == MachinePlaysBlack ||
13368         gameMode == TwoMachinesPlay ||
13369         gameMode == EndOfGame) {
13370         i = forwardMostMove;
13371         while (i > currentMove) {
13372             SendToProgram("undo\n", &first);
13373             i--;
13374         }
13375         whiteTimeRemaining = timeRemaining[0][currentMove];
13376         blackTimeRemaining = timeRemaining[1][currentMove];
13377         DisplayBothClocks();
13378         if (whiteFlag || blackFlag) {
13379             whiteFlag = blackFlag = 0;
13380         }
13381         DisplayTitle("");
13382     }
13383
13384     gameMode = EditGame;
13385     ModeHighlight();
13386     SetGameInfo();
13387 }
13388
13389
13390 void
13391 EditPositionEvent()
13392 {
13393     if (gameMode == EditPosition) {
13394         EditGameEvent();
13395         return;
13396     }
13397
13398     EditGameEvent();
13399     if (gameMode != EditGame) return;
13400
13401     gameMode = EditPosition;
13402     ModeHighlight();
13403     SetGameInfo();
13404     if (currentMove > 0)
13405       CopyBoard(boards[0], boards[currentMove]);
13406
13407     blackPlaysFirst = !WhiteOnMove(currentMove);
13408     ResetClocks();
13409     currentMove = forwardMostMove = backwardMostMove = 0;
13410     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13411     DisplayMove(-1);
13412 }
13413
13414 void
13415 ExitAnalyzeMode()
13416 {
13417     /* [DM] icsEngineAnalyze - possible call from other functions */
13418     if (appData.icsEngineAnalyze) {
13419         appData.icsEngineAnalyze = FALSE;
13420
13421         DisplayMessage("",_("Close ICS engine analyze..."));
13422     }
13423     if (first.analysisSupport && first.analyzing) {
13424       SendToProgram("exit\n", &first);
13425       first.analyzing = FALSE;
13426     }
13427     thinkOutput[0] = NULLCHAR;
13428 }
13429
13430 void
13431 EditPositionDone(Boolean fakeRights)
13432 {
13433     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13434
13435     startedFromSetupPosition = TRUE;
13436     InitChessProgram(&first, FALSE);
13437     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13438       boards[0][EP_STATUS] = EP_NONE;
13439       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13440     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13441         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13442         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13443       } else boards[0][CASTLING][2] = NoRights;
13444     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13445         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13446         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13447       } else boards[0][CASTLING][5] = NoRights;
13448     }
13449     SendToProgram("force\n", &first);
13450     if (blackPlaysFirst) {
13451         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13452         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13453         currentMove = forwardMostMove = backwardMostMove = 1;
13454         CopyBoard(boards[1], boards[0]);
13455     } else {
13456         currentMove = forwardMostMove = backwardMostMove = 0;
13457     }
13458     SendBoard(&first, forwardMostMove);
13459     if (appData.debugMode) {
13460         fprintf(debugFP, "EditPosDone\n");
13461     }
13462     DisplayTitle("");
13463     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13464     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13465     gameMode = EditGame;
13466     ModeHighlight();
13467     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13468     ClearHighlights(); /* [AS] */
13469 }
13470
13471 /* Pause for `ms' milliseconds */
13472 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13473 void
13474 TimeDelay(ms)
13475      long ms;
13476 {
13477     TimeMark m1, m2;
13478
13479     GetTimeMark(&m1);
13480     do {
13481         GetTimeMark(&m2);
13482     } while (SubtractTimeMarks(&m2, &m1) < ms);
13483 }
13484
13485 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13486 void
13487 SendMultiLineToICS(buf)
13488      char *buf;
13489 {
13490     char temp[MSG_SIZ+1], *p;
13491     int len;
13492
13493     len = strlen(buf);
13494     if (len > MSG_SIZ)
13495       len = MSG_SIZ;
13496
13497     strncpy(temp, buf, len);
13498     temp[len] = 0;
13499
13500     p = temp;
13501     while (*p) {
13502         if (*p == '\n' || *p == '\r')
13503           *p = ' ';
13504         ++p;
13505     }
13506
13507     strcat(temp, "\n");
13508     SendToICS(temp);
13509     SendToPlayer(temp, strlen(temp));
13510 }
13511
13512 void
13513 SetWhiteToPlayEvent()
13514 {
13515     if (gameMode == EditPosition) {
13516         blackPlaysFirst = FALSE;
13517         DisplayBothClocks();    /* works because currentMove is 0 */
13518     } else if (gameMode == IcsExamining) {
13519         SendToICS(ics_prefix);
13520         SendToICS("tomove white\n");
13521     }
13522 }
13523
13524 void
13525 SetBlackToPlayEvent()
13526 {
13527     if (gameMode == EditPosition) {
13528         blackPlaysFirst = TRUE;
13529         currentMove = 1;        /* kludge */
13530         DisplayBothClocks();
13531         currentMove = 0;
13532     } else if (gameMode == IcsExamining) {
13533         SendToICS(ics_prefix);
13534         SendToICS("tomove black\n");
13535     }
13536 }
13537
13538 void
13539 EditPositionMenuEvent(selection, x, y)
13540      ChessSquare selection;
13541      int x, y;
13542 {
13543     char buf[MSG_SIZ];
13544     ChessSquare piece = boards[0][y][x];
13545
13546     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13547
13548     switch (selection) {
13549       case ClearBoard:
13550         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13551             SendToICS(ics_prefix);
13552             SendToICS("bsetup clear\n");
13553         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13554             SendToICS(ics_prefix);
13555             SendToICS("clearboard\n");
13556         } else {
13557             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13558                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13559                 for (y = 0; y < BOARD_HEIGHT; y++) {
13560                     if (gameMode == IcsExamining) {
13561                         if (boards[currentMove][y][x] != EmptySquare) {
13562                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13563                                     AAA + x, ONE + y);
13564                             SendToICS(buf);
13565                         }
13566                     } else {
13567                         boards[0][y][x] = p;
13568                     }
13569                 }
13570             }
13571         }
13572         if (gameMode == EditPosition) {
13573             DrawPosition(FALSE, boards[0]);
13574         }
13575         break;
13576
13577       case WhitePlay:
13578         SetWhiteToPlayEvent();
13579         break;
13580
13581       case BlackPlay:
13582         SetBlackToPlayEvent();
13583         break;
13584
13585       case EmptySquare:
13586         if (gameMode == IcsExamining) {
13587             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13588             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13589             SendToICS(buf);
13590         } else {
13591             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13592                 if(x == BOARD_LEFT-2) {
13593                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13594                     boards[0][y][1] = 0;
13595                 } else
13596                 if(x == BOARD_RGHT+1) {
13597                     if(y >= gameInfo.holdingsSize) break;
13598                     boards[0][y][BOARD_WIDTH-2] = 0;
13599                 } else break;
13600             }
13601             boards[0][y][x] = EmptySquare;
13602             DrawPosition(FALSE, boards[0]);
13603         }
13604         break;
13605
13606       case PromotePiece:
13607         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13608            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13609             selection = (ChessSquare) (PROMOTED piece);
13610         } else if(piece == EmptySquare) selection = WhiteSilver;
13611         else selection = (ChessSquare)((int)piece - 1);
13612         goto defaultlabel;
13613
13614       case DemotePiece:
13615         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13616            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13617             selection = (ChessSquare) (DEMOTED piece);
13618         } else if(piece == EmptySquare) selection = BlackSilver;
13619         else selection = (ChessSquare)((int)piece + 1);
13620         goto defaultlabel;
13621
13622       case WhiteQueen:
13623       case BlackQueen:
13624         if(gameInfo.variant == VariantShatranj ||
13625            gameInfo.variant == VariantXiangqi  ||
13626            gameInfo.variant == VariantCourier  ||
13627            gameInfo.variant == VariantMakruk     )
13628             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13629         goto defaultlabel;
13630
13631       case WhiteKing:
13632       case BlackKing:
13633         if(gameInfo.variant == VariantXiangqi)
13634             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13635         if(gameInfo.variant == VariantKnightmate)
13636             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13637       default:
13638         defaultlabel:
13639         if (gameMode == IcsExamining) {
13640             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13641             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13642                      PieceToChar(selection), AAA + x, ONE + y);
13643             SendToICS(buf);
13644         } else {
13645             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13646                 int n;
13647                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13648                     n = PieceToNumber(selection - BlackPawn);
13649                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13650                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13651                     boards[0][BOARD_HEIGHT-1-n][1]++;
13652                 } else
13653                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13654                     n = PieceToNumber(selection);
13655                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13656                     boards[0][n][BOARD_WIDTH-1] = selection;
13657                     boards[0][n][BOARD_WIDTH-2]++;
13658                 }
13659             } else
13660             boards[0][y][x] = selection;
13661             DrawPosition(TRUE, boards[0]);
13662         }
13663         break;
13664     }
13665 }
13666
13667
13668 void
13669 DropMenuEvent(selection, x, y)
13670      ChessSquare selection;
13671      int x, y;
13672 {
13673     ChessMove moveType;
13674
13675     switch (gameMode) {
13676       case IcsPlayingWhite:
13677       case MachinePlaysBlack:
13678         if (!WhiteOnMove(currentMove)) {
13679             DisplayMoveError(_("It is Black's turn"));
13680             return;
13681         }
13682         moveType = WhiteDrop;
13683         break;
13684       case IcsPlayingBlack:
13685       case MachinePlaysWhite:
13686         if (WhiteOnMove(currentMove)) {
13687             DisplayMoveError(_("It is White's turn"));
13688             return;
13689         }
13690         moveType = BlackDrop;
13691         break;
13692       case EditGame:
13693         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13694         break;
13695       default:
13696         return;
13697     }
13698
13699     if (moveType == BlackDrop && selection < BlackPawn) {
13700       selection = (ChessSquare) ((int) selection
13701                                  + (int) BlackPawn - (int) WhitePawn);
13702     }
13703     if (boards[currentMove][y][x] != EmptySquare) {
13704         DisplayMoveError(_("That square is occupied"));
13705         return;
13706     }
13707
13708     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13709 }
13710
13711 void
13712 AcceptEvent()
13713 {
13714     /* Accept a pending offer of any kind from opponent */
13715
13716     if (appData.icsActive) {
13717         SendToICS(ics_prefix);
13718         SendToICS("accept\n");
13719     } else if (cmailMsgLoaded) {
13720         if (currentMove == cmailOldMove &&
13721             commentList[cmailOldMove] != NULL &&
13722             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13723                    "Black offers a draw" : "White offers a draw")) {
13724             TruncateGame();
13725             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13726             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13727         } else {
13728             DisplayError(_("There is no pending offer on this move"), 0);
13729             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13730         }
13731     } else {
13732         /* Not used for offers from chess program */
13733     }
13734 }
13735
13736 void
13737 DeclineEvent()
13738 {
13739     /* Decline a pending offer of any kind from opponent */
13740
13741     if (appData.icsActive) {
13742         SendToICS(ics_prefix);
13743         SendToICS("decline\n");
13744     } else if (cmailMsgLoaded) {
13745         if (currentMove == cmailOldMove &&
13746             commentList[cmailOldMove] != NULL &&
13747             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13748                    "Black offers a draw" : "White offers a draw")) {
13749 #ifdef NOTDEF
13750             AppendComment(cmailOldMove, "Draw declined", TRUE);
13751             DisplayComment(cmailOldMove - 1, "Draw declined");
13752 #endif /*NOTDEF*/
13753         } else {
13754             DisplayError(_("There is no pending offer on this move"), 0);
13755         }
13756     } else {
13757         /* Not used for offers from chess program */
13758     }
13759 }
13760
13761 void
13762 RematchEvent()
13763 {
13764     /* Issue ICS rematch command */
13765     if (appData.icsActive) {
13766         SendToICS(ics_prefix);
13767         SendToICS("rematch\n");
13768     }
13769 }
13770
13771 void
13772 CallFlagEvent()
13773 {
13774     /* Call your opponent's flag (claim a win on time) */
13775     if (appData.icsActive) {
13776         SendToICS(ics_prefix);
13777         SendToICS("flag\n");
13778     } else {
13779         switch (gameMode) {
13780           default:
13781             return;
13782           case MachinePlaysWhite:
13783             if (whiteFlag) {
13784                 if (blackFlag)
13785                   GameEnds(GameIsDrawn, "Both players ran out of time",
13786                            GE_PLAYER);
13787                 else
13788                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13789             } else {
13790                 DisplayError(_("Your opponent is not out of time"), 0);
13791             }
13792             break;
13793           case MachinePlaysBlack:
13794             if (blackFlag) {
13795                 if (whiteFlag)
13796                   GameEnds(GameIsDrawn, "Both players ran out of time",
13797                            GE_PLAYER);
13798                 else
13799                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13800             } else {
13801                 DisplayError(_("Your opponent is not out of time"), 0);
13802             }
13803             break;
13804         }
13805     }
13806 }
13807
13808 void
13809 ClockClick(int which)
13810 {       // [HGM] code moved to back-end from winboard.c
13811         if(which) { // black clock
13812           if (gameMode == EditPosition || gameMode == IcsExamining) {
13813             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13814             SetBlackToPlayEvent();
13815           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13816           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13817           } else if (shiftKey) {
13818             AdjustClock(which, -1);
13819           } else if (gameMode == IcsPlayingWhite ||
13820                      gameMode == MachinePlaysBlack) {
13821             CallFlagEvent();
13822           }
13823         } else { // white clock
13824           if (gameMode == EditPosition || gameMode == IcsExamining) {
13825             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13826             SetWhiteToPlayEvent();
13827           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13828           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13829           } else if (shiftKey) {
13830             AdjustClock(which, -1);
13831           } else if (gameMode == IcsPlayingBlack ||
13832                    gameMode == MachinePlaysWhite) {
13833             CallFlagEvent();
13834           }
13835         }
13836 }
13837
13838 void
13839 DrawEvent()
13840 {
13841     /* Offer draw or accept pending draw offer from opponent */
13842
13843     if (appData.icsActive) {
13844         /* Note: tournament rules require draw offers to be
13845            made after you make your move but before you punch
13846            your clock.  Currently ICS doesn't let you do that;
13847            instead, you immediately punch your clock after making
13848            a move, but you can offer a draw at any time. */
13849
13850         SendToICS(ics_prefix);
13851         SendToICS("draw\n");
13852         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13853     } else if (cmailMsgLoaded) {
13854         if (currentMove == cmailOldMove &&
13855             commentList[cmailOldMove] != NULL &&
13856             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13857                    "Black offers a draw" : "White offers a draw")) {
13858             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13859             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13860         } else if (currentMove == cmailOldMove + 1) {
13861             char *offer = WhiteOnMove(cmailOldMove) ?
13862               "White offers a draw" : "Black offers a draw";
13863             AppendComment(currentMove, offer, TRUE);
13864             DisplayComment(currentMove - 1, offer);
13865             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13866         } else {
13867             DisplayError(_("You must make your move before offering a draw"), 0);
13868             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13869         }
13870     } else if (first.offeredDraw) {
13871         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13872     } else {
13873         if (first.sendDrawOffers) {
13874             SendToProgram("draw\n", &first);
13875             userOfferedDraw = TRUE;
13876         }
13877     }
13878 }
13879
13880 void
13881 AdjournEvent()
13882 {
13883     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13884
13885     if (appData.icsActive) {
13886         SendToICS(ics_prefix);
13887         SendToICS("adjourn\n");
13888     } else {
13889         /* Currently GNU Chess doesn't offer or accept Adjourns */
13890     }
13891 }
13892
13893
13894 void
13895 AbortEvent()
13896 {
13897     /* Offer Abort or accept pending Abort offer from opponent */
13898
13899     if (appData.icsActive) {
13900         SendToICS(ics_prefix);
13901         SendToICS("abort\n");
13902     } else {
13903         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13904     }
13905 }
13906
13907 void
13908 ResignEvent()
13909 {
13910     /* Resign.  You can do this even if it's not your turn. */
13911
13912     if (appData.icsActive) {
13913         SendToICS(ics_prefix);
13914         SendToICS("resign\n");
13915     } else {
13916         switch (gameMode) {
13917           case MachinePlaysWhite:
13918             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13919             break;
13920           case MachinePlaysBlack:
13921             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13922             break;
13923           case EditGame:
13924             if (cmailMsgLoaded) {
13925                 TruncateGame();
13926                 if (WhiteOnMove(cmailOldMove)) {
13927                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13928                 } else {
13929                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13930                 }
13931                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13932             }
13933             break;
13934           default:
13935             break;
13936         }
13937     }
13938 }
13939
13940
13941 void
13942 StopObservingEvent()
13943 {
13944     /* Stop observing current games */
13945     SendToICS(ics_prefix);
13946     SendToICS("unobserve\n");
13947 }
13948
13949 void
13950 StopExaminingEvent()
13951 {
13952     /* Stop observing current game */
13953     SendToICS(ics_prefix);
13954     SendToICS("unexamine\n");
13955 }
13956
13957 void
13958 ForwardInner(target)
13959      int target;
13960 {
13961     int limit;
13962
13963     if (appData.debugMode)
13964         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13965                 target, currentMove, forwardMostMove);
13966
13967     if (gameMode == EditPosition)
13968       return;
13969
13970     if (gameMode == PlayFromGameFile && !pausing)
13971       PauseEvent();
13972
13973     if (gameMode == IcsExamining && pausing)
13974       limit = pauseExamForwardMostMove;
13975     else
13976       limit = forwardMostMove;
13977
13978     if (target > limit) target = limit;
13979
13980     if (target > 0 && moveList[target - 1][0]) {
13981         int fromX, fromY, toX, toY;
13982         toX = moveList[target - 1][2] - AAA;
13983         toY = moveList[target - 1][3] - ONE;
13984         if (moveList[target - 1][1] == '@') {
13985             if (appData.highlightLastMove) {
13986                 SetHighlights(-1, -1, toX, toY);
13987             }
13988         } else {
13989             fromX = moveList[target - 1][0] - AAA;
13990             fromY = moveList[target - 1][1] - ONE;
13991             if (target == currentMove + 1) {
13992                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13993             }
13994             if (appData.highlightLastMove) {
13995                 SetHighlights(fromX, fromY, toX, toY);
13996             }
13997         }
13998     }
13999     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14000         gameMode == Training || gameMode == PlayFromGameFile ||
14001         gameMode == AnalyzeFile) {
14002         while (currentMove < target) {
14003             SendMoveToProgram(currentMove++, &first);
14004         }
14005     } else {
14006         currentMove = target;
14007     }
14008
14009     if (gameMode == EditGame || gameMode == EndOfGame) {
14010         whiteTimeRemaining = timeRemaining[0][currentMove];
14011         blackTimeRemaining = timeRemaining[1][currentMove];
14012     }
14013     DisplayBothClocks();
14014     DisplayMove(currentMove - 1);
14015     DrawPosition(FALSE, boards[currentMove]);
14016     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14017     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14018         DisplayComment(currentMove - 1, commentList[currentMove]);
14019     }
14020     DisplayBook(currentMove);
14021 }
14022
14023
14024 void
14025 ForwardEvent()
14026 {
14027     if (gameMode == IcsExamining && !pausing) {
14028         SendToICS(ics_prefix);
14029         SendToICS("forward\n");
14030     } else {
14031         ForwardInner(currentMove + 1);
14032     }
14033 }
14034
14035 void
14036 ToEndEvent()
14037 {
14038     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14039         /* to optimze, we temporarily turn off analysis mode while we feed
14040          * the remaining moves to the engine. Otherwise we get analysis output
14041          * after each move.
14042          */
14043         if (first.analysisSupport) {
14044           SendToProgram("exit\nforce\n", &first);
14045           first.analyzing = FALSE;
14046         }
14047     }
14048
14049     if (gameMode == IcsExamining && !pausing) {
14050         SendToICS(ics_prefix);
14051         SendToICS("forward 999999\n");
14052     } else {
14053         ForwardInner(forwardMostMove);
14054     }
14055
14056     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14057         /* we have fed all the moves, so reactivate analysis mode */
14058         SendToProgram("analyze\n", &first);
14059         first.analyzing = TRUE;
14060         /*first.maybeThinking = TRUE;*/
14061         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14062     }
14063 }
14064
14065 void
14066 BackwardInner(target)
14067      int target;
14068 {
14069     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14070
14071     if (appData.debugMode)
14072         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14073                 target, currentMove, forwardMostMove);
14074
14075     if (gameMode == EditPosition) return;
14076     if (currentMove <= backwardMostMove) {
14077         ClearHighlights();
14078         DrawPosition(full_redraw, boards[currentMove]);
14079         return;
14080     }
14081     if (gameMode == PlayFromGameFile && !pausing)
14082       PauseEvent();
14083
14084     if (moveList[target][0]) {
14085         int fromX, fromY, toX, toY;
14086         toX = moveList[target][2] - AAA;
14087         toY = moveList[target][3] - ONE;
14088         if (moveList[target][1] == '@') {
14089             if (appData.highlightLastMove) {
14090                 SetHighlights(-1, -1, toX, toY);
14091             }
14092         } else {
14093             fromX = moveList[target][0] - AAA;
14094             fromY = moveList[target][1] - ONE;
14095             if (target == currentMove - 1) {
14096                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14097             }
14098             if (appData.highlightLastMove) {
14099                 SetHighlights(fromX, fromY, toX, toY);
14100             }
14101         }
14102     }
14103     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14104         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14105         while (currentMove > target) {
14106             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14107                 // null move cannot be undone. Reload program with move history before it.
14108                 int i;
14109                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14110                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14111                 }
14112                 SendBoard(&first, i); 
14113                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14114                 break;
14115             }
14116             SendToProgram("undo\n", &first);
14117             currentMove--;
14118         }
14119     } else {
14120         currentMove = target;
14121     }
14122
14123     if (gameMode == EditGame || gameMode == EndOfGame) {
14124         whiteTimeRemaining = timeRemaining[0][currentMove];
14125         blackTimeRemaining = timeRemaining[1][currentMove];
14126     }
14127     DisplayBothClocks();
14128     DisplayMove(currentMove - 1);
14129     DrawPosition(full_redraw, boards[currentMove]);
14130     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14131     // [HGM] PV info: routine tests if comment empty
14132     DisplayComment(currentMove - 1, commentList[currentMove]);
14133     DisplayBook(currentMove);
14134 }
14135
14136 void
14137 BackwardEvent()
14138 {
14139     if (gameMode == IcsExamining && !pausing) {
14140         SendToICS(ics_prefix);
14141         SendToICS("backward\n");
14142     } else {
14143         BackwardInner(currentMove - 1);
14144     }
14145 }
14146
14147 void
14148 ToStartEvent()
14149 {
14150     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14151         /* to optimize, we temporarily turn off analysis mode while we undo
14152          * all the moves. Otherwise we get analysis output after each undo.
14153          */
14154         if (first.analysisSupport) {
14155           SendToProgram("exit\nforce\n", &first);
14156           first.analyzing = FALSE;
14157         }
14158     }
14159
14160     if (gameMode == IcsExamining && !pausing) {
14161         SendToICS(ics_prefix);
14162         SendToICS("backward 999999\n");
14163     } else {
14164         BackwardInner(backwardMostMove);
14165     }
14166
14167     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14168         /* we have fed all the moves, so reactivate analysis mode */
14169         SendToProgram("analyze\n", &first);
14170         first.analyzing = TRUE;
14171         /*first.maybeThinking = TRUE;*/
14172         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14173     }
14174 }
14175
14176 void
14177 ToNrEvent(int to)
14178 {
14179   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14180   if (to >= forwardMostMove) to = forwardMostMove;
14181   if (to <= backwardMostMove) to = backwardMostMove;
14182   if (to < currentMove) {
14183     BackwardInner(to);
14184   } else {
14185     ForwardInner(to);
14186   }
14187 }
14188
14189 void
14190 RevertEvent(Boolean annotate)
14191 {
14192     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14193         return;
14194     }
14195     if (gameMode != IcsExamining) {
14196         DisplayError(_("You are not examining a game"), 0);
14197         return;
14198     }
14199     if (pausing) {
14200         DisplayError(_("You can't revert while pausing"), 0);
14201         return;
14202     }
14203     SendToICS(ics_prefix);
14204     SendToICS("revert\n");
14205 }
14206
14207 void
14208 RetractMoveEvent()
14209 {
14210     switch (gameMode) {
14211       case MachinePlaysWhite:
14212       case MachinePlaysBlack:
14213         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14214             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14215             return;
14216         }
14217         if (forwardMostMove < 2) return;
14218         currentMove = forwardMostMove = forwardMostMove - 2;
14219         whiteTimeRemaining = timeRemaining[0][currentMove];
14220         blackTimeRemaining = timeRemaining[1][currentMove];
14221         DisplayBothClocks();
14222         DisplayMove(currentMove - 1);
14223         ClearHighlights();/*!! could figure this out*/
14224         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14225         SendToProgram("remove\n", &first);
14226         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14227         break;
14228
14229       case BeginningOfGame:
14230       default:
14231         break;
14232
14233       case IcsPlayingWhite:
14234       case IcsPlayingBlack:
14235         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14236             SendToICS(ics_prefix);
14237             SendToICS("takeback 2\n");
14238         } else {
14239             SendToICS(ics_prefix);
14240             SendToICS("takeback 1\n");
14241         }
14242         break;
14243     }
14244 }
14245
14246 void
14247 MoveNowEvent()
14248 {
14249     ChessProgramState *cps;
14250
14251     switch (gameMode) {
14252       case MachinePlaysWhite:
14253         if (!WhiteOnMove(forwardMostMove)) {
14254             DisplayError(_("It is your turn"), 0);
14255             return;
14256         }
14257         cps = &first;
14258         break;
14259       case MachinePlaysBlack:
14260         if (WhiteOnMove(forwardMostMove)) {
14261             DisplayError(_("It is your turn"), 0);
14262             return;
14263         }
14264         cps = &first;
14265         break;
14266       case TwoMachinesPlay:
14267         if (WhiteOnMove(forwardMostMove) ==
14268             (first.twoMachinesColor[0] == 'w')) {
14269             cps = &first;
14270         } else {
14271             cps = &second;
14272         }
14273         break;
14274       case BeginningOfGame:
14275       default:
14276         return;
14277     }
14278     SendToProgram("?\n", cps);
14279 }
14280
14281 void
14282 TruncateGameEvent()
14283 {
14284     EditGameEvent();
14285     if (gameMode != EditGame) return;
14286     TruncateGame();
14287 }
14288
14289 void
14290 TruncateGame()
14291 {
14292     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14293     if (forwardMostMove > currentMove) {
14294         if (gameInfo.resultDetails != NULL) {
14295             free(gameInfo.resultDetails);
14296             gameInfo.resultDetails = NULL;
14297             gameInfo.result = GameUnfinished;
14298         }
14299         forwardMostMove = currentMove;
14300         HistorySet(parseList, backwardMostMove, forwardMostMove,
14301                    currentMove-1);
14302     }
14303 }
14304
14305 void
14306 HintEvent()
14307 {
14308     if (appData.noChessProgram) return;
14309     switch (gameMode) {
14310       case MachinePlaysWhite:
14311         if (WhiteOnMove(forwardMostMove)) {
14312             DisplayError(_("Wait until your turn"), 0);
14313             return;
14314         }
14315         break;
14316       case BeginningOfGame:
14317       case MachinePlaysBlack:
14318         if (!WhiteOnMove(forwardMostMove)) {
14319             DisplayError(_("Wait until your turn"), 0);
14320             return;
14321         }
14322         break;
14323       default:
14324         DisplayError(_("No hint available"), 0);
14325         return;
14326     }
14327     SendToProgram("hint\n", &first);
14328     hintRequested = TRUE;
14329 }
14330
14331 void
14332 BookEvent()
14333 {
14334     if (appData.noChessProgram) return;
14335     switch (gameMode) {
14336       case MachinePlaysWhite:
14337         if (WhiteOnMove(forwardMostMove)) {
14338             DisplayError(_("Wait until your turn"), 0);
14339             return;
14340         }
14341         break;
14342       case BeginningOfGame:
14343       case MachinePlaysBlack:
14344         if (!WhiteOnMove(forwardMostMove)) {
14345             DisplayError(_("Wait until your turn"), 0);
14346             return;
14347         }
14348         break;
14349       case EditPosition:
14350         EditPositionDone(TRUE);
14351         break;
14352       case TwoMachinesPlay:
14353         return;
14354       default:
14355         break;
14356     }
14357     SendToProgram("bk\n", &first);
14358     bookOutput[0] = NULLCHAR;
14359     bookRequested = TRUE;
14360 }
14361
14362 void
14363 AboutGameEvent()
14364 {
14365     char *tags = PGNTags(&gameInfo);
14366     TagsPopUp(tags, CmailMsg());
14367     free(tags);
14368 }
14369
14370 /* end button procedures */
14371
14372 void
14373 PrintPosition(fp, move)
14374      FILE *fp;
14375      int move;
14376 {
14377     int i, j;
14378
14379     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14380         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14381             char c = PieceToChar(boards[move][i][j]);
14382             fputc(c == 'x' ? '.' : c, fp);
14383             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14384         }
14385     }
14386     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14387       fprintf(fp, "white to play\n");
14388     else
14389       fprintf(fp, "black to play\n");
14390 }
14391
14392 void
14393 PrintOpponents(fp)
14394      FILE *fp;
14395 {
14396     if (gameInfo.white != NULL) {
14397         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14398     } else {
14399         fprintf(fp, "\n");
14400     }
14401 }
14402
14403 /* Find last component of program's own name, using some heuristics */
14404 void
14405 TidyProgramName(prog, host, buf)
14406      char *prog, *host, buf[MSG_SIZ];
14407 {
14408     char *p, *q;
14409     int local = (strcmp(host, "localhost") == 0);
14410     while (!local && (p = strchr(prog, ';')) != NULL) {
14411         p++;
14412         while (*p == ' ') p++;
14413         prog = p;
14414     }
14415     if (*prog == '"' || *prog == '\'') {
14416         q = strchr(prog + 1, *prog);
14417     } else {
14418         q = strchr(prog, ' ');
14419     }
14420     if (q == NULL) q = prog + strlen(prog);
14421     p = q;
14422     while (p >= prog && *p != '/' && *p != '\\') p--;
14423     p++;
14424     if(p == prog && *p == '"') p++;
14425     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14426     memcpy(buf, p, q - p);
14427     buf[q - p] = NULLCHAR;
14428     if (!local) {
14429         strcat(buf, "@");
14430         strcat(buf, host);
14431     }
14432 }
14433
14434 char *
14435 TimeControlTagValue()
14436 {
14437     char buf[MSG_SIZ];
14438     if (!appData.clockMode) {
14439       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14440     } else if (movesPerSession > 0) {
14441       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14442     } else if (timeIncrement == 0) {
14443       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14444     } else {
14445       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14446     }
14447     return StrSave(buf);
14448 }
14449
14450 void
14451 SetGameInfo()
14452 {
14453     /* This routine is used only for certain modes */
14454     VariantClass v = gameInfo.variant;
14455     ChessMove r = GameUnfinished;
14456     char *p = NULL;
14457
14458     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14459         r = gameInfo.result;
14460         p = gameInfo.resultDetails;
14461         gameInfo.resultDetails = NULL;
14462     }
14463     ClearGameInfo(&gameInfo);
14464     gameInfo.variant = v;
14465
14466     switch (gameMode) {
14467       case MachinePlaysWhite:
14468         gameInfo.event = StrSave( appData.pgnEventHeader );
14469         gameInfo.site = StrSave(HostName());
14470         gameInfo.date = PGNDate();
14471         gameInfo.round = StrSave("-");
14472         gameInfo.white = StrSave(first.tidy);
14473         gameInfo.black = StrSave(UserName());
14474         gameInfo.timeControl = TimeControlTagValue();
14475         break;
14476
14477       case MachinePlaysBlack:
14478         gameInfo.event = StrSave( appData.pgnEventHeader );
14479         gameInfo.site = StrSave(HostName());
14480         gameInfo.date = PGNDate();
14481         gameInfo.round = StrSave("-");
14482         gameInfo.white = StrSave(UserName());
14483         gameInfo.black = StrSave(first.tidy);
14484         gameInfo.timeControl = TimeControlTagValue();
14485         break;
14486
14487       case TwoMachinesPlay:
14488         gameInfo.event = StrSave( appData.pgnEventHeader );
14489         gameInfo.site = StrSave(HostName());
14490         gameInfo.date = PGNDate();
14491         if (roundNr > 0) {
14492             char buf[MSG_SIZ];
14493             snprintf(buf, MSG_SIZ, "%d", roundNr);
14494             gameInfo.round = StrSave(buf);
14495         } else {
14496             gameInfo.round = StrSave("-");
14497         }
14498         if (first.twoMachinesColor[0] == 'w') {
14499             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14500             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14501         } else {
14502             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14503             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14504         }
14505         gameInfo.timeControl = TimeControlTagValue();
14506         break;
14507
14508       case EditGame:
14509         gameInfo.event = StrSave("Edited game");
14510         gameInfo.site = StrSave(HostName());
14511         gameInfo.date = PGNDate();
14512         gameInfo.round = StrSave("-");
14513         gameInfo.white = StrSave("-");
14514         gameInfo.black = StrSave("-");
14515         gameInfo.result = r;
14516         gameInfo.resultDetails = p;
14517         break;
14518
14519       case EditPosition:
14520         gameInfo.event = StrSave("Edited position");
14521         gameInfo.site = StrSave(HostName());
14522         gameInfo.date = PGNDate();
14523         gameInfo.round = StrSave("-");
14524         gameInfo.white = StrSave("-");
14525         gameInfo.black = StrSave("-");
14526         break;
14527
14528       case IcsPlayingWhite:
14529       case IcsPlayingBlack:
14530       case IcsObserving:
14531       case IcsExamining:
14532         break;
14533
14534       case PlayFromGameFile:
14535         gameInfo.event = StrSave("Game from non-PGN file");
14536         gameInfo.site = StrSave(HostName());
14537         gameInfo.date = PGNDate();
14538         gameInfo.round = StrSave("-");
14539         gameInfo.white = StrSave("?");
14540         gameInfo.black = StrSave("?");
14541         break;
14542
14543       default:
14544         break;
14545     }
14546 }
14547
14548 void
14549 ReplaceComment(index, text)
14550      int index;
14551      char *text;
14552 {
14553     int len;
14554     char *p;
14555     float score;
14556
14557     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14558        pvInfoList[index-1].depth == len &&
14559        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14560        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14561     while (*text == '\n') text++;
14562     len = strlen(text);
14563     while (len > 0 && text[len - 1] == '\n') len--;
14564
14565     if (commentList[index] != NULL)
14566       free(commentList[index]);
14567
14568     if (len == 0) {
14569         commentList[index] = NULL;
14570         return;
14571     }
14572   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14573       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14574       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14575     commentList[index] = (char *) malloc(len + 2);
14576     strncpy(commentList[index], text, len);
14577     commentList[index][len] = '\n';
14578     commentList[index][len + 1] = NULLCHAR;
14579   } else {
14580     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14581     char *p;
14582     commentList[index] = (char *) malloc(len + 7);
14583     safeStrCpy(commentList[index], "{\n", 3);
14584     safeStrCpy(commentList[index]+2, text, len+1);
14585     commentList[index][len+2] = NULLCHAR;
14586     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14587     strcat(commentList[index], "\n}\n");
14588   }
14589 }
14590
14591 void
14592 CrushCRs(text)
14593      char *text;
14594 {
14595   char *p = text;
14596   char *q = text;
14597   char ch;
14598
14599   do {
14600     ch = *p++;
14601     if (ch == '\r') continue;
14602     *q++ = ch;
14603   } while (ch != '\0');
14604 }
14605
14606 void
14607 AppendComment(index, text, addBraces)
14608      int index;
14609      char *text;
14610      Boolean addBraces; // [HGM] braces: tells if we should add {}
14611 {
14612     int oldlen, len;
14613     char *old;
14614
14615 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14616     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14617
14618     CrushCRs(text);
14619     while (*text == '\n') text++;
14620     len = strlen(text);
14621     while (len > 0 && text[len - 1] == '\n') len--;
14622
14623     if (len == 0) return;
14624
14625     if (commentList[index] != NULL) {
14626       Boolean addClosingBrace = addBraces;
14627         old = commentList[index];
14628         oldlen = strlen(old);
14629         while(commentList[index][oldlen-1] ==  '\n')
14630           commentList[index][--oldlen] = NULLCHAR;
14631         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14632         safeStrCpy(commentList[index], old, oldlen + len + 6);
14633         free(old);
14634         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14635         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14636           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14637           while (*text == '\n') { text++; len--; }
14638           commentList[index][--oldlen] = NULLCHAR;
14639       }
14640         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14641         else          strcat(commentList[index], "\n");
14642         strcat(commentList[index], text);
14643         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14644         else          strcat(commentList[index], "\n");
14645     } else {
14646         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14647         if(addBraces)
14648           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14649         else commentList[index][0] = NULLCHAR;
14650         strcat(commentList[index], text);
14651         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14652         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14653     }
14654 }
14655
14656 static char * FindStr( char * text, char * sub_text )
14657 {
14658     char * result = strstr( text, sub_text );
14659
14660     if( result != NULL ) {
14661         result += strlen( sub_text );
14662     }
14663
14664     return result;
14665 }
14666
14667 /* [AS] Try to extract PV info from PGN comment */
14668 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14669 char *GetInfoFromComment( int index, char * text )
14670 {
14671     char * sep = text, *p;
14672
14673     if( text != NULL && index > 0 ) {
14674         int score = 0;
14675         int depth = 0;
14676         int time = -1, sec = 0, deci;
14677         char * s_eval = FindStr( text, "[%eval " );
14678         char * s_emt = FindStr( text, "[%emt " );
14679
14680         if( s_eval != NULL || s_emt != NULL ) {
14681             /* New style */
14682             char delim;
14683
14684             if( s_eval != NULL ) {
14685                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14686                     return text;
14687                 }
14688
14689                 if( delim != ']' ) {
14690                     return text;
14691                 }
14692             }
14693
14694             if( s_emt != NULL ) {
14695             }
14696                 return text;
14697         }
14698         else {
14699             /* We expect something like: [+|-]nnn.nn/dd */
14700             int score_lo = 0;
14701
14702             if(*text != '{') return text; // [HGM] braces: must be normal comment
14703
14704             sep = strchr( text, '/' );
14705             if( sep == NULL || sep < (text+4) ) {
14706                 return text;
14707             }
14708
14709             p = text;
14710             if(p[1] == '(') { // comment starts with PV
14711                p = strchr(p, ')'); // locate end of PV
14712                if(p == NULL || sep < p+5) return text;
14713                // at this point we have something like "{(.*) +0.23/6 ..."
14714                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14715                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14716                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14717             }
14718             time = -1; sec = -1; deci = -1;
14719             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14720                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14721                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14722                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14723                 return text;
14724             }
14725
14726             if( score_lo < 0 || score_lo >= 100 ) {
14727                 return text;
14728             }
14729
14730             if(sec >= 0) time = 600*time + 10*sec; else
14731             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14732
14733             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14734
14735             /* [HGM] PV time: now locate end of PV info */
14736             while( *++sep >= '0' && *sep <= '9'); // strip depth
14737             if(time >= 0)
14738             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14739             if(sec >= 0)
14740             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14741             if(deci >= 0)
14742             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14743             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14744         }
14745
14746         if( depth <= 0 ) {
14747             return text;
14748         }
14749
14750         if( time < 0 ) {
14751             time = -1;
14752         }
14753
14754         pvInfoList[index-1].depth = depth;
14755         pvInfoList[index-1].score = score;
14756         pvInfoList[index-1].time  = 10*time; // centi-sec
14757         if(*sep == '}') *sep = 0; else *--sep = '{';
14758         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14759     }
14760     return sep;
14761 }
14762
14763 void
14764 SendToProgram(message, cps)
14765      char *message;
14766      ChessProgramState *cps;
14767 {
14768     int count, outCount, error;
14769     char buf[MSG_SIZ];
14770
14771     if (cps->pr == NULL) return;
14772     Attention(cps);
14773
14774     if (appData.debugMode) {
14775         TimeMark now;
14776         GetTimeMark(&now);
14777         fprintf(debugFP, "%ld >%-6s: %s",
14778                 SubtractTimeMarks(&now, &programStartTime),
14779                 cps->which, message);
14780     }
14781
14782     count = strlen(message);
14783     outCount = OutputToProcess(cps->pr, message, count, &error);
14784     if (outCount < count && !exiting
14785                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14786       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14787       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14788         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14789             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14790                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14791                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14792                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14793             } else {
14794                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14795                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14796                 gameInfo.result = res;
14797             }
14798             gameInfo.resultDetails = StrSave(buf);
14799         }
14800         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14801         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14802     }
14803 }
14804
14805 void
14806 ReceiveFromProgram(isr, closure, message, count, error)
14807      InputSourceRef isr;
14808      VOIDSTAR closure;
14809      char *message;
14810      int count;
14811      int error;
14812 {
14813     char *end_str;
14814     char buf[MSG_SIZ];
14815     ChessProgramState *cps = (ChessProgramState *)closure;
14816
14817     if (isr != cps->isr) return; /* Killed intentionally */
14818     if (count <= 0) {
14819         if (count == 0) {
14820             RemoveInputSource(cps->isr);
14821             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14822             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14823                     _(cps->which), cps->program);
14824         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14825                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14826                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14827                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14828                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14829                 } else {
14830                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14831                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14832                     gameInfo.result = res;
14833                 }
14834                 gameInfo.resultDetails = StrSave(buf);
14835             }
14836             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14837             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14838         } else {
14839             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14840                     _(cps->which), cps->program);
14841             RemoveInputSource(cps->isr);
14842
14843             /* [AS] Program is misbehaving badly... kill it */
14844             if( count == -2 ) {
14845                 DestroyChildProcess( cps->pr, 9 );
14846                 cps->pr = NoProc;
14847             }
14848
14849             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14850         }
14851         return;
14852     }
14853
14854     if ((end_str = strchr(message, '\r')) != NULL)
14855       *end_str = NULLCHAR;
14856     if ((end_str = strchr(message, '\n')) != NULL)
14857       *end_str = NULLCHAR;
14858
14859     if (appData.debugMode) {
14860         TimeMark now; int print = 1;
14861         char *quote = ""; char c; int i;
14862
14863         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14864                 char start = message[0];
14865                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14866                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14867                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14868                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14869                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14870                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14871                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14872                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14873                    sscanf(message, "hint: %c", &c)!=1 && 
14874                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14875                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14876                     print = (appData.engineComments >= 2);
14877                 }
14878                 message[0] = start; // restore original message
14879         }
14880         if(print) {
14881                 GetTimeMark(&now);
14882                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14883                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14884                         quote,
14885                         message);
14886         }
14887     }
14888
14889     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14890     if (appData.icsEngineAnalyze) {
14891         if (strstr(message, "whisper") != NULL ||
14892              strstr(message, "kibitz") != NULL ||
14893             strstr(message, "tellics") != NULL) return;
14894     }
14895
14896     HandleMachineMove(message, cps);
14897 }
14898
14899
14900 void
14901 SendTimeControl(cps, mps, tc, inc, sd, st)
14902      ChessProgramState *cps;
14903      int mps, inc, sd, st;
14904      long tc;
14905 {
14906     char buf[MSG_SIZ];
14907     int seconds;
14908
14909     if( timeControl_2 > 0 ) {
14910         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14911             tc = timeControl_2;
14912         }
14913     }
14914     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14915     inc /= cps->timeOdds;
14916     st  /= cps->timeOdds;
14917
14918     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14919
14920     if (st > 0) {
14921       /* Set exact time per move, normally using st command */
14922       if (cps->stKludge) {
14923         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14924         seconds = st % 60;
14925         if (seconds == 0) {
14926           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14927         } else {
14928           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14929         }
14930       } else {
14931         snprintf(buf, MSG_SIZ, "st %d\n", st);
14932       }
14933     } else {
14934       /* Set conventional or incremental time control, using level command */
14935       if (seconds == 0) {
14936         /* Note old gnuchess bug -- minutes:seconds used to not work.
14937            Fixed in later versions, but still avoid :seconds
14938            when seconds is 0. */
14939         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14940       } else {
14941         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14942                  seconds, inc/1000.);
14943       }
14944     }
14945     SendToProgram(buf, cps);
14946
14947     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14948     /* Orthogonally, limit search to given depth */
14949     if (sd > 0) {
14950       if (cps->sdKludge) {
14951         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14952       } else {
14953         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14954       }
14955       SendToProgram(buf, cps);
14956     }
14957
14958     if(cps->nps >= 0) { /* [HGM] nps */
14959         if(cps->supportsNPS == FALSE)
14960           cps->nps = -1; // don't use if engine explicitly says not supported!
14961         else {
14962           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14963           SendToProgram(buf, cps);
14964         }
14965     }
14966 }
14967
14968 ChessProgramState *WhitePlayer()
14969 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14970 {
14971     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14972        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14973         return &second;
14974     return &first;
14975 }
14976
14977 void
14978 SendTimeRemaining(cps, machineWhite)
14979      ChessProgramState *cps;
14980      int /*boolean*/ machineWhite;
14981 {
14982     char message[MSG_SIZ];
14983     long time, otime;
14984
14985     /* Note: this routine must be called when the clocks are stopped
14986        or when they have *just* been set or switched; otherwise
14987        it will be off by the time since the current tick started.
14988     */
14989     if (machineWhite) {
14990         time = whiteTimeRemaining / 10;
14991         otime = blackTimeRemaining / 10;
14992     } else {
14993         time = blackTimeRemaining / 10;
14994         otime = whiteTimeRemaining / 10;
14995     }
14996     /* [HGM] translate opponent's time by time-odds factor */
14997     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14998     if (appData.debugMode) {
14999         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15000     }
15001
15002     if (time <= 0) time = 1;
15003     if (otime <= 0) otime = 1;
15004
15005     snprintf(message, MSG_SIZ, "time %ld\n", time);
15006     SendToProgram(message, cps);
15007
15008     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15009     SendToProgram(message, cps);
15010 }
15011
15012 int
15013 BoolFeature(p, name, loc, cps)
15014      char **p;
15015      char *name;
15016      int *loc;
15017      ChessProgramState *cps;
15018 {
15019   char buf[MSG_SIZ];
15020   int len = strlen(name);
15021   int val;
15022
15023   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15024     (*p) += len + 1;
15025     sscanf(*p, "%d", &val);
15026     *loc = (val != 0);
15027     while (**p && **p != ' ')
15028       (*p)++;
15029     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15030     SendToProgram(buf, cps);
15031     return TRUE;
15032   }
15033   return FALSE;
15034 }
15035
15036 int
15037 IntFeature(p, name, loc, cps)
15038      char **p;
15039      char *name;
15040      int *loc;
15041      ChessProgramState *cps;
15042 {
15043   char buf[MSG_SIZ];
15044   int len = strlen(name);
15045   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15046     (*p) += len + 1;
15047     sscanf(*p, "%d", loc);
15048     while (**p && **p != ' ') (*p)++;
15049     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15050     SendToProgram(buf, cps);
15051     return TRUE;
15052   }
15053   return FALSE;
15054 }
15055
15056 int
15057 StringFeature(p, name, loc, cps)
15058      char **p;
15059      char *name;
15060      char loc[];
15061      ChessProgramState *cps;
15062 {
15063   char buf[MSG_SIZ];
15064   int len = strlen(name);
15065   if (strncmp((*p), name, len) == 0
15066       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15067     (*p) += len + 2;
15068     sscanf(*p, "%[^\"]", loc);
15069     while (**p && **p != '\"') (*p)++;
15070     if (**p == '\"') (*p)++;
15071     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15072     SendToProgram(buf, cps);
15073     return TRUE;
15074   }
15075   return FALSE;
15076 }
15077
15078 int
15079 ParseOption(Option *opt, ChessProgramState *cps)
15080 // [HGM] options: process the string that defines an engine option, and determine
15081 // name, type, default value, and allowed value range
15082 {
15083         char *p, *q, buf[MSG_SIZ];
15084         int n, min = (-1)<<31, max = 1<<31, def;
15085
15086         if(p = strstr(opt->name, " -spin ")) {
15087             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15088             if(max < min) max = min; // enforce consistency
15089             if(def < min) def = min;
15090             if(def > max) def = max;
15091             opt->value = def;
15092             opt->min = min;
15093             opt->max = max;
15094             opt->type = Spin;
15095         } else if((p = strstr(opt->name, " -slider "))) {
15096             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15097             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15098             if(max < min) max = min; // enforce consistency
15099             if(def < min) def = min;
15100             if(def > max) def = max;
15101             opt->value = def;
15102             opt->min = min;
15103             opt->max = max;
15104             opt->type = Spin; // Slider;
15105         } else if((p = strstr(opt->name, " -string "))) {
15106             opt->textValue = p+9;
15107             opt->type = TextBox;
15108         } else if((p = strstr(opt->name, " -file "))) {
15109             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15110             opt->textValue = p+7;
15111             opt->type = FileName; // FileName;
15112         } else if((p = strstr(opt->name, " -path "))) {
15113             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15114             opt->textValue = p+7;
15115             opt->type = PathName; // PathName;
15116         } else if(p = strstr(opt->name, " -check ")) {
15117             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15118             opt->value = (def != 0);
15119             opt->type = CheckBox;
15120         } else if(p = strstr(opt->name, " -combo ")) {
15121             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15122             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15123             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15124             opt->value = n = 0;
15125             while(q = StrStr(q, " /// ")) {
15126                 n++; *q = 0;    // count choices, and null-terminate each of them
15127                 q += 5;
15128                 if(*q == '*') { // remember default, which is marked with * prefix
15129                     q++;
15130                     opt->value = n;
15131                 }
15132                 cps->comboList[cps->comboCnt++] = q;
15133             }
15134             cps->comboList[cps->comboCnt++] = NULL;
15135             opt->max = n + 1;
15136             opt->type = ComboBox;
15137         } else if(p = strstr(opt->name, " -button")) {
15138             opt->type = Button;
15139         } else if(p = strstr(opt->name, " -save")) {
15140             opt->type = SaveButton;
15141         } else return FALSE;
15142         *p = 0; // terminate option name
15143         // now look if the command-line options define a setting for this engine option.
15144         if(cps->optionSettings && cps->optionSettings[0])
15145             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15146         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15147           snprintf(buf, MSG_SIZ, "option %s", p);
15148                 if(p = strstr(buf, ",")) *p = 0;
15149                 if(q = strchr(buf, '=')) switch(opt->type) {
15150                     case ComboBox:
15151                         for(n=0; n<opt->max; n++)
15152                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15153                         break;
15154                     case TextBox:
15155                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15156                         break;
15157                     case Spin:
15158                     case CheckBox:
15159                         opt->value = atoi(q+1);
15160                     default:
15161                         break;
15162                 }
15163                 strcat(buf, "\n");
15164                 SendToProgram(buf, cps);
15165         }
15166         return TRUE;
15167 }
15168
15169 void
15170 FeatureDone(cps, val)
15171      ChessProgramState* cps;
15172      int val;
15173 {
15174   DelayedEventCallback cb = GetDelayedEvent();
15175   if ((cb == InitBackEnd3 && cps == &first) ||
15176       (cb == SettingsMenuIfReady && cps == &second) ||
15177       (cb == LoadEngine) ||
15178       (cb == TwoMachinesEventIfReady)) {
15179     CancelDelayedEvent();
15180     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15181   }
15182   cps->initDone = val;
15183 }
15184
15185 /* Parse feature command from engine */
15186 void
15187 ParseFeatures(args, cps)
15188      char* args;
15189      ChessProgramState *cps;
15190 {
15191   char *p = args;
15192   char *q;
15193   int val;
15194   char buf[MSG_SIZ];
15195
15196   for (;;) {
15197     while (*p == ' ') p++;
15198     if (*p == NULLCHAR) return;
15199
15200     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15201     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15202     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15203     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15204     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15205     if (BoolFeature(&p, "reuse", &val, cps)) {
15206       /* Engine can disable reuse, but can't enable it if user said no */
15207       if (!val) cps->reuse = FALSE;
15208       continue;
15209     }
15210     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15211     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15212       if (gameMode == TwoMachinesPlay) {
15213         DisplayTwoMachinesTitle();
15214       } else {
15215         DisplayTitle("");
15216       }
15217       continue;
15218     }
15219     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15220     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15221     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15222     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15223     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15224     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15225     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15226     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15227     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15228     if (IntFeature(&p, "done", &val, cps)) {
15229       FeatureDone(cps, val);
15230       continue;
15231     }
15232     /* Added by Tord: */
15233     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15234     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15235     /* End of additions by Tord */
15236
15237     /* [HGM] added features: */
15238     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15239     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15240     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15241     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15242     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15243     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15244     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15245         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15246           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15247             SendToProgram(buf, cps);
15248             continue;
15249         }
15250         if(cps->nrOptions >= MAX_OPTIONS) {
15251             cps->nrOptions--;
15252             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15253             DisplayError(buf, 0);
15254         }
15255         continue;
15256     }
15257     /* End of additions by HGM */
15258
15259     /* unknown feature: complain and skip */
15260     q = p;
15261     while (*q && *q != '=') q++;
15262     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15263     SendToProgram(buf, cps);
15264     p = q;
15265     if (*p == '=') {
15266       p++;
15267       if (*p == '\"') {
15268         p++;
15269         while (*p && *p != '\"') p++;
15270         if (*p == '\"') p++;
15271       } else {
15272         while (*p && *p != ' ') p++;
15273       }
15274     }
15275   }
15276
15277 }
15278
15279 void
15280 PeriodicUpdatesEvent(newState)
15281      int newState;
15282 {
15283     if (newState == appData.periodicUpdates)
15284       return;
15285
15286     appData.periodicUpdates=newState;
15287
15288     /* Display type changes, so update it now */
15289 //    DisplayAnalysis();
15290
15291     /* Get the ball rolling again... */
15292     if (newState) {
15293         AnalysisPeriodicEvent(1);
15294         StartAnalysisClock();
15295     }
15296 }
15297
15298 void
15299 PonderNextMoveEvent(newState)
15300      int newState;
15301 {
15302     if (newState == appData.ponderNextMove) return;
15303     if (gameMode == EditPosition) EditPositionDone(TRUE);
15304     if (newState) {
15305         SendToProgram("hard\n", &first);
15306         if (gameMode == TwoMachinesPlay) {
15307             SendToProgram("hard\n", &second);
15308         }
15309     } else {
15310         SendToProgram("easy\n", &first);
15311         thinkOutput[0] = NULLCHAR;
15312         if (gameMode == TwoMachinesPlay) {
15313             SendToProgram("easy\n", &second);
15314         }
15315     }
15316     appData.ponderNextMove = newState;
15317 }
15318
15319 void
15320 NewSettingEvent(option, feature, command, value)
15321      char *command;
15322      int option, value, *feature;
15323 {
15324     char buf[MSG_SIZ];
15325
15326     if (gameMode == EditPosition) EditPositionDone(TRUE);
15327     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15328     if(feature == NULL || *feature) SendToProgram(buf, &first);
15329     if (gameMode == TwoMachinesPlay) {
15330         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15331     }
15332 }
15333
15334 void
15335 ShowThinkingEvent()
15336 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15337 {
15338     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15339     int newState = appData.showThinking
15340         // [HGM] thinking: other features now need thinking output as well
15341         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15342
15343     if (oldState == newState) return;
15344     oldState = newState;
15345     if (gameMode == EditPosition) EditPositionDone(TRUE);
15346     if (oldState) {
15347         SendToProgram("post\n", &first);
15348         if (gameMode == TwoMachinesPlay) {
15349             SendToProgram("post\n", &second);
15350         }
15351     } else {
15352         SendToProgram("nopost\n", &first);
15353         thinkOutput[0] = NULLCHAR;
15354         if (gameMode == TwoMachinesPlay) {
15355             SendToProgram("nopost\n", &second);
15356         }
15357     }
15358 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15359 }
15360
15361 void
15362 AskQuestionEvent(title, question, replyPrefix, which)
15363      char *title; char *question; char *replyPrefix; char *which;
15364 {
15365   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15366   if (pr == NoProc) return;
15367   AskQuestion(title, question, replyPrefix, pr);
15368 }
15369
15370 void
15371 TypeInEvent(char firstChar)
15372 {
15373     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15374         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15375         gameMode == AnalyzeMode || gameMode == EditGame || 
15376         gameMode == EditPosition || gameMode == IcsExamining ||
15377         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15378         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15379                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15380                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15381         gameMode == Training) PopUpMoveDialog(firstChar);
15382 }
15383
15384 void
15385 TypeInDoneEvent(char *move)
15386 {
15387         Board board;
15388         int n, fromX, fromY, toX, toY;
15389         char promoChar;
15390         ChessMove moveType;
15391
15392         // [HGM] FENedit
15393         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15394                 EditPositionPasteFEN(move);
15395                 return;
15396         }
15397         // [HGM] movenum: allow move number to be typed in any mode
15398         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15399           ToNrEvent(2*n-1);
15400           return;
15401         }
15402
15403       if (gameMode != EditGame && currentMove != forwardMostMove && 
15404         gameMode != Training) {
15405         DisplayMoveError(_("Displayed move is not current"));
15406       } else {
15407         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15408           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15409         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15410         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15411           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15412           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15413         } else {
15414           DisplayMoveError(_("Could not parse move"));
15415         }
15416       }
15417 }
15418
15419 void
15420 DisplayMove(moveNumber)
15421      int moveNumber;
15422 {
15423     char message[MSG_SIZ];
15424     char res[MSG_SIZ];
15425     char cpThinkOutput[MSG_SIZ];
15426
15427     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15428
15429     if (moveNumber == forwardMostMove - 1 ||
15430         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15431
15432         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15433
15434         if (strchr(cpThinkOutput, '\n')) {
15435             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15436         }
15437     } else {
15438         *cpThinkOutput = NULLCHAR;
15439     }
15440
15441     /* [AS] Hide thinking from human user */
15442     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15443         *cpThinkOutput = NULLCHAR;
15444         if( thinkOutput[0] != NULLCHAR ) {
15445             int i;
15446
15447             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15448                 cpThinkOutput[i] = '.';
15449             }
15450             cpThinkOutput[i] = NULLCHAR;
15451             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15452         }
15453     }
15454
15455     if (moveNumber == forwardMostMove - 1 &&
15456         gameInfo.resultDetails != NULL) {
15457         if (gameInfo.resultDetails[0] == NULLCHAR) {
15458           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15459         } else {
15460           snprintf(res, MSG_SIZ, " {%s} %s",
15461                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15462         }
15463     } else {
15464         res[0] = NULLCHAR;
15465     }
15466
15467     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15468         DisplayMessage(res, cpThinkOutput);
15469     } else {
15470       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15471                 WhiteOnMove(moveNumber) ? " " : ".. ",
15472                 parseList[moveNumber], res);
15473         DisplayMessage(message, cpThinkOutput);
15474     }
15475 }
15476
15477 void
15478 DisplayComment(moveNumber, text)
15479      int moveNumber;
15480      char *text;
15481 {
15482     char title[MSG_SIZ];
15483
15484     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15485       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15486     } else {
15487       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15488               WhiteOnMove(moveNumber) ? " " : ".. ",
15489               parseList[moveNumber]);
15490     }
15491     if (text != NULL && (appData.autoDisplayComment || commentUp))
15492         CommentPopUp(title, text);
15493 }
15494
15495 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15496  * might be busy thinking or pondering.  It can be omitted if your
15497  * gnuchess is configured to stop thinking immediately on any user
15498  * input.  However, that gnuchess feature depends on the FIONREAD
15499  * ioctl, which does not work properly on some flavors of Unix.
15500  */
15501 void
15502 Attention(cps)
15503      ChessProgramState *cps;
15504 {
15505 #if ATTENTION
15506     if (!cps->useSigint) return;
15507     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15508     switch (gameMode) {
15509       case MachinePlaysWhite:
15510       case MachinePlaysBlack:
15511       case TwoMachinesPlay:
15512       case IcsPlayingWhite:
15513       case IcsPlayingBlack:
15514       case AnalyzeMode:
15515       case AnalyzeFile:
15516         /* Skip if we know it isn't thinking */
15517         if (!cps->maybeThinking) return;
15518         if (appData.debugMode)
15519           fprintf(debugFP, "Interrupting %s\n", cps->which);
15520         InterruptChildProcess(cps->pr);
15521         cps->maybeThinking = FALSE;
15522         break;
15523       default:
15524         break;
15525     }
15526 #endif /*ATTENTION*/
15527 }
15528
15529 int
15530 CheckFlags()
15531 {
15532     if (whiteTimeRemaining <= 0) {
15533         if (!whiteFlag) {
15534             whiteFlag = TRUE;
15535             if (appData.icsActive) {
15536                 if (appData.autoCallFlag &&
15537                     gameMode == IcsPlayingBlack && !blackFlag) {
15538                   SendToICS(ics_prefix);
15539                   SendToICS("flag\n");
15540                 }
15541             } else {
15542                 if (blackFlag) {
15543                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15544                 } else {
15545                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15546                     if (appData.autoCallFlag) {
15547                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15548                         return TRUE;
15549                     }
15550                 }
15551             }
15552         }
15553     }
15554     if (blackTimeRemaining <= 0) {
15555         if (!blackFlag) {
15556             blackFlag = TRUE;
15557             if (appData.icsActive) {
15558                 if (appData.autoCallFlag &&
15559                     gameMode == IcsPlayingWhite && !whiteFlag) {
15560                   SendToICS(ics_prefix);
15561                   SendToICS("flag\n");
15562                 }
15563             } else {
15564                 if (whiteFlag) {
15565                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15566                 } else {
15567                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15568                     if (appData.autoCallFlag) {
15569                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15570                         return TRUE;
15571                     }
15572                 }
15573             }
15574         }
15575     }
15576     return FALSE;
15577 }
15578
15579 void
15580 CheckTimeControl()
15581 {
15582     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15583         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15584
15585     /*
15586      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15587      */
15588     if ( !WhiteOnMove(forwardMostMove) ) {
15589         /* White made time control */
15590         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15591         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15592         /* [HGM] time odds: correct new time quota for time odds! */
15593                                             / WhitePlayer()->timeOdds;
15594         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15595     } else {
15596         lastBlack -= blackTimeRemaining;
15597         /* Black made time control */
15598         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15599                                             / WhitePlayer()->other->timeOdds;
15600         lastWhite = whiteTimeRemaining;
15601     }
15602 }
15603
15604 void
15605 DisplayBothClocks()
15606 {
15607     int wom = gameMode == EditPosition ?
15608       !blackPlaysFirst : WhiteOnMove(currentMove);
15609     DisplayWhiteClock(whiteTimeRemaining, wom);
15610     DisplayBlackClock(blackTimeRemaining, !wom);
15611 }
15612
15613
15614 /* Timekeeping seems to be a portability nightmare.  I think everyone
15615    has ftime(), but I'm really not sure, so I'm including some ifdefs
15616    to use other calls if you don't.  Clocks will be less accurate if
15617    you have neither ftime nor gettimeofday.
15618 */
15619
15620 /* VS 2008 requires the #include outside of the function */
15621 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15622 #include <sys/timeb.h>
15623 #endif
15624
15625 /* Get the current time as a TimeMark */
15626 void
15627 GetTimeMark(tm)
15628      TimeMark *tm;
15629 {
15630 #if HAVE_GETTIMEOFDAY
15631
15632     struct timeval timeVal;
15633     struct timezone timeZone;
15634
15635     gettimeofday(&timeVal, &timeZone);
15636     tm->sec = (long) timeVal.tv_sec;
15637     tm->ms = (int) (timeVal.tv_usec / 1000L);
15638
15639 #else /*!HAVE_GETTIMEOFDAY*/
15640 #if HAVE_FTIME
15641
15642 // include <sys/timeb.h> / moved to just above start of function
15643     struct timeb timeB;
15644
15645     ftime(&timeB);
15646     tm->sec = (long) timeB.time;
15647     tm->ms = (int) timeB.millitm;
15648
15649 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15650     tm->sec = (long) time(NULL);
15651     tm->ms = 0;
15652 #endif
15653 #endif
15654 }
15655
15656 /* Return the difference in milliseconds between two
15657    time marks.  We assume the difference will fit in a long!
15658 */
15659 long
15660 SubtractTimeMarks(tm2, tm1)
15661      TimeMark *tm2, *tm1;
15662 {
15663     return 1000L*(tm2->sec - tm1->sec) +
15664            (long) (tm2->ms - tm1->ms);
15665 }
15666
15667
15668 /*
15669  * Code to manage the game clocks.
15670  *
15671  * In tournament play, black starts the clock and then white makes a move.
15672  * We give the human user a slight advantage if he is playing white---the
15673  * clocks don't run until he makes his first move, so it takes zero time.
15674  * Also, we don't account for network lag, so we could get out of sync
15675  * with GNU Chess's clock -- but then, referees are always right.
15676  */
15677
15678 static TimeMark tickStartTM;
15679 static long intendedTickLength;
15680
15681 long
15682 NextTickLength(timeRemaining)
15683      long timeRemaining;
15684 {
15685     long nominalTickLength, nextTickLength;
15686
15687     if (timeRemaining > 0L && timeRemaining <= 10000L)
15688       nominalTickLength = 100L;
15689     else
15690       nominalTickLength = 1000L;
15691     nextTickLength = timeRemaining % nominalTickLength;
15692     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15693
15694     return nextTickLength;
15695 }
15696
15697 /* Adjust clock one minute up or down */
15698 void
15699 AdjustClock(Boolean which, int dir)
15700 {
15701     if(which) blackTimeRemaining += 60000*dir;
15702     else      whiteTimeRemaining += 60000*dir;
15703     DisplayBothClocks();
15704 }
15705
15706 /* Stop clocks and reset to a fresh time control */
15707 void
15708 ResetClocks()
15709 {
15710     (void) StopClockTimer();
15711     if (appData.icsActive) {
15712         whiteTimeRemaining = blackTimeRemaining = 0;
15713     } else if (searchTime) {
15714         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15715         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15716     } else { /* [HGM] correct new time quote for time odds */
15717         whiteTC = blackTC = fullTimeControlString;
15718         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15719         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15720     }
15721     if (whiteFlag || blackFlag) {
15722         DisplayTitle("");
15723         whiteFlag = blackFlag = FALSE;
15724     }
15725     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15726     DisplayBothClocks();
15727 }
15728
15729 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15730
15731 /* Decrement running clock by amount of time that has passed */
15732 void
15733 DecrementClocks()
15734 {
15735     long timeRemaining;
15736     long lastTickLength, fudge;
15737     TimeMark now;
15738
15739     if (!appData.clockMode) return;
15740     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15741
15742     GetTimeMark(&now);
15743
15744     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15745
15746     /* Fudge if we woke up a little too soon */
15747     fudge = intendedTickLength - lastTickLength;
15748     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15749
15750     if (WhiteOnMove(forwardMostMove)) {
15751         if(whiteNPS >= 0) lastTickLength = 0;
15752         timeRemaining = whiteTimeRemaining -= lastTickLength;
15753         if(timeRemaining < 0 && !appData.icsActive) {
15754             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15755             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15756                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15757                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15758             }
15759         }
15760         DisplayWhiteClock(whiteTimeRemaining - fudge,
15761                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15762     } else {
15763         if(blackNPS >= 0) lastTickLength = 0;
15764         timeRemaining = blackTimeRemaining -= lastTickLength;
15765         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15766             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15767             if(suddenDeath) {
15768                 blackStartMove = forwardMostMove;
15769                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15770             }
15771         }
15772         DisplayBlackClock(blackTimeRemaining - fudge,
15773                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15774     }
15775     if (CheckFlags()) return;
15776
15777     tickStartTM = now;
15778     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15779     StartClockTimer(intendedTickLength);
15780
15781     /* if the time remaining has fallen below the alarm threshold, sound the
15782      * alarm. if the alarm has sounded and (due to a takeback or time control
15783      * with increment) the time remaining has increased to a level above the
15784      * threshold, reset the alarm so it can sound again.
15785      */
15786
15787     if (appData.icsActive && appData.icsAlarm) {
15788
15789         /* make sure we are dealing with the user's clock */
15790         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15791                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15792            )) return;
15793
15794         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15795             alarmSounded = FALSE;
15796         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15797             PlayAlarmSound();
15798             alarmSounded = TRUE;
15799         }
15800     }
15801 }
15802
15803
15804 /* A player has just moved, so stop the previously running
15805    clock and (if in clock mode) start the other one.
15806    We redisplay both clocks in case we're in ICS mode, because
15807    ICS gives us an update to both clocks after every move.
15808    Note that this routine is called *after* forwardMostMove
15809    is updated, so the last fractional tick must be subtracted
15810    from the color that is *not* on move now.
15811 */
15812 void
15813 SwitchClocks(int newMoveNr)
15814 {
15815     long lastTickLength;
15816     TimeMark now;
15817     int flagged = FALSE;
15818
15819     GetTimeMark(&now);
15820
15821     if (StopClockTimer() && appData.clockMode) {
15822         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15823         if (!WhiteOnMove(forwardMostMove)) {
15824             if(blackNPS >= 0) lastTickLength = 0;
15825             blackTimeRemaining -= lastTickLength;
15826            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15827 //         if(pvInfoList[forwardMostMove].time == -1)
15828                  pvInfoList[forwardMostMove].time =               // use GUI time
15829                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15830         } else {
15831            if(whiteNPS >= 0) lastTickLength = 0;
15832            whiteTimeRemaining -= lastTickLength;
15833            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15834 //         if(pvInfoList[forwardMostMove].time == -1)
15835                  pvInfoList[forwardMostMove].time =
15836                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15837         }
15838         flagged = CheckFlags();
15839     }
15840     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15841     CheckTimeControl();
15842
15843     if (flagged || !appData.clockMode) return;
15844
15845     switch (gameMode) {
15846       case MachinePlaysBlack:
15847       case MachinePlaysWhite:
15848       case BeginningOfGame:
15849         if (pausing) return;
15850         break;
15851
15852       case EditGame:
15853       case PlayFromGameFile:
15854       case IcsExamining:
15855         return;
15856
15857       default:
15858         break;
15859     }
15860
15861     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15862         if(WhiteOnMove(forwardMostMove))
15863              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15864         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15865     }
15866
15867     tickStartTM = now;
15868     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15869       whiteTimeRemaining : blackTimeRemaining);
15870     StartClockTimer(intendedTickLength);
15871 }
15872
15873
15874 /* Stop both clocks */
15875 void
15876 StopClocks()
15877 {
15878     long lastTickLength;
15879     TimeMark now;
15880
15881     if (!StopClockTimer()) return;
15882     if (!appData.clockMode) return;
15883
15884     GetTimeMark(&now);
15885
15886     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15887     if (WhiteOnMove(forwardMostMove)) {
15888         if(whiteNPS >= 0) lastTickLength = 0;
15889         whiteTimeRemaining -= lastTickLength;
15890         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15891     } else {
15892         if(blackNPS >= 0) lastTickLength = 0;
15893         blackTimeRemaining -= lastTickLength;
15894         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15895     }
15896     CheckFlags();
15897 }
15898
15899 /* Start clock of player on move.  Time may have been reset, so
15900    if clock is already running, stop and restart it. */
15901 void
15902 StartClocks()
15903 {
15904     (void) StopClockTimer(); /* in case it was running already */
15905     DisplayBothClocks();
15906     if (CheckFlags()) return;
15907
15908     if (!appData.clockMode) return;
15909     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15910
15911     GetTimeMark(&tickStartTM);
15912     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15913       whiteTimeRemaining : blackTimeRemaining);
15914
15915    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15916     whiteNPS = blackNPS = -1;
15917     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15918        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15919         whiteNPS = first.nps;
15920     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15921        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15922         blackNPS = first.nps;
15923     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15924         whiteNPS = second.nps;
15925     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15926         blackNPS = second.nps;
15927     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15928
15929     StartClockTimer(intendedTickLength);
15930 }
15931
15932 char *
15933 TimeString(ms)
15934      long ms;
15935 {
15936     long second, minute, hour, day;
15937     char *sign = "";
15938     static char buf[32];
15939
15940     if (ms > 0 && ms <= 9900) {
15941       /* convert milliseconds to tenths, rounding up */
15942       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15943
15944       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15945       return buf;
15946     }
15947
15948     /* convert milliseconds to seconds, rounding up */
15949     /* use floating point to avoid strangeness of integer division
15950        with negative dividends on many machines */
15951     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15952
15953     if (second < 0) {
15954         sign = "-";
15955         second = -second;
15956     }
15957
15958     day = second / (60 * 60 * 24);
15959     second = second % (60 * 60 * 24);
15960     hour = second / (60 * 60);
15961     second = second % (60 * 60);
15962     minute = second / 60;
15963     second = second % 60;
15964
15965     if (day > 0)
15966       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15967               sign, day, hour, minute, second);
15968     else if (hour > 0)
15969       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15970     else
15971       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15972
15973     return buf;
15974 }
15975
15976
15977 /*
15978  * This is necessary because some C libraries aren't ANSI C compliant yet.
15979  */
15980 char *
15981 StrStr(string, match)
15982      char *string, *match;
15983 {
15984     int i, length;
15985
15986     length = strlen(match);
15987
15988     for (i = strlen(string) - length; i >= 0; i--, string++)
15989       if (!strncmp(match, string, length))
15990         return string;
15991
15992     return NULL;
15993 }
15994
15995 char *
15996 StrCaseStr(string, match)
15997      char *string, *match;
15998 {
15999     int i, j, length;
16000
16001     length = strlen(match);
16002
16003     for (i = strlen(string) - length; i >= 0; i--, string++) {
16004         for (j = 0; j < length; j++) {
16005             if (ToLower(match[j]) != ToLower(string[j]))
16006               break;
16007         }
16008         if (j == length) return string;
16009     }
16010
16011     return NULL;
16012 }
16013
16014 #ifndef _amigados
16015 int
16016 StrCaseCmp(s1, s2)
16017      char *s1, *s2;
16018 {
16019     char c1, c2;
16020
16021     for (;;) {
16022         c1 = ToLower(*s1++);
16023         c2 = ToLower(*s2++);
16024         if (c1 > c2) return 1;
16025         if (c1 < c2) return -1;
16026         if (c1 == NULLCHAR) return 0;
16027     }
16028 }
16029
16030
16031 int
16032 ToLower(c)
16033      int c;
16034 {
16035     return isupper(c) ? tolower(c) : c;
16036 }
16037
16038
16039 int
16040 ToUpper(c)
16041      int c;
16042 {
16043     return islower(c) ? toupper(c) : c;
16044 }
16045 #endif /* !_amigados    */
16046
16047 char *
16048 StrSave(s)
16049      char *s;
16050 {
16051   char *ret;
16052
16053   if ((ret = (char *) malloc(strlen(s) + 1)))
16054     {
16055       safeStrCpy(ret, s, strlen(s)+1);
16056     }
16057   return ret;
16058 }
16059
16060 char *
16061 StrSavePtr(s, savePtr)
16062      char *s, **savePtr;
16063 {
16064     if (*savePtr) {
16065         free(*savePtr);
16066     }
16067     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16068       safeStrCpy(*savePtr, s, strlen(s)+1);
16069     }
16070     return(*savePtr);
16071 }
16072
16073 char *
16074 PGNDate()
16075 {
16076     time_t clock;
16077     struct tm *tm;
16078     char buf[MSG_SIZ];
16079
16080     clock = time((time_t *)NULL);
16081     tm = localtime(&clock);
16082     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16083             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16084     return StrSave(buf);
16085 }
16086
16087
16088 char *
16089 PositionToFEN(move, overrideCastling)
16090      int move;
16091      char *overrideCastling;
16092 {
16093     int i, j, fromX, fromY, toX, toY;
16094     int whiteToPlay;
16095     char buf[MSG_SIZ];
16096     char *p, *q;
16097     int emptycount;
16098     ChessSquare piece;
16099
16100     whiteToPlay = (gameMode == EditPosition) ?
16101       !blackPlaysFirst : (move % 2 == 0);
16102     p = buf;
16103
16104     /* Piece placement data */
16105     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16106         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16107         emptycount = 0;
16108         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16109             if (boards[move][i][j] == EmptySquare) {
16110                 emptycount++;
16111             } else { ChessSquare piece = boards[move][i][j];
16112                 if (emptycount > 0) {
16113                     if(emptycount<10) /* [HGM] can be >= 10 */
16114                         *p++ = '0' + emptycount;
16115                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16116                     emptycount = 0;
16117                 }
16118                 if(PieceToChar(piece) == '+') {
16119                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16120                     *p++ = '+';
16121                     piece = (ChessSquare)(DEMOTED piece);
16122                 }
16123                 *p++ = PieceToChar(piece);
16124                 if(p[-1] == '~') {
16125                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16126                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16127                     *p++ = '~';
16128                 }
16129             }
16130         }
16131         if (emptycount > 0) {
16132             if(emptycount<10) /* [HGM] can be >= 10 */
16133                 *p++ = '0' + emptycount;
16134             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16135             emptycount = 0;
16136         }
16137         *p++ = '/';
16138     }
16139     *(p - 1) = ' ';
16140
16141     /* [HGM] print Crazyhouse or Shogi holdings */
16142     if( gameInfo.holdingsWidth ) {
16143         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16144         q = p;
16145         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16146             piece = boards[move][i][BOARD_WIDTH-1];
16147             if( piece != EmptySquare )
16148               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16149                   *p++ = PieceToChar(piece);
16150         }
16151         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16152             piece = boards[move][BOARD_HEIGHT-i-1][0];
16153             if( piece != EmptySquare )
16154               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16155                   *p++ = PieceToChar(piece);
16156         }
16157
16158         if( q == p ) *p++ = '-';
16159         *p++ = ']';
16160         *p++ = ' ';
16161     }
16162
16163     /* Active color */
16164     *p++ = whiteToPlay ? 'w' : 'b';
16165     *p++ = ' ';
16166
16167   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16168     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16169   } else {
16170   if(nrCastlingRights) {
16171      q = p;
16172      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16173        /* [HGM] write directly from rights */
16174            if(boards[move][CASTLING][2] != NoRights &&
16175               boards[move][CASTLING][0] != NoRights   )
16176                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16177            if(boards[move][CASTLING][2] != NoRights &&
16178               boards[move][CASTLING][1] != NoRights   )
16179                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16180            if(boards[move][CASTLING][5] != NoRights &&
16181               boards[move][CASTLING][3] != NoRights   )
16182                 *p++ = boards[move][CASTLING][3] + AAA;
16183            if(boards[move][CASTLING][5] != NoRights &&
16184               boards[move][CASTLING][4] != NoRights   )
16185                 *p++ = boards[move][CASTLING][4] + AAA;
16186      } else {
16187
16188         /* [HGM] write true castling rights */
16189         if( nrCastlingRights == 6 ) {
16190             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16191                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16192             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16193                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16194             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16195                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16196             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16197                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16198         }
16199      }
16200      if (q == p) *p++ = '-'; /* No castling rights */
16201      *p++ = ' ';
16202   }
16203
16204   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16205      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16206     /* En passant target square */
16207     if (move > backwardMostMove) {
16208         fromX = moveList[move - 1][0] - AAA;
16209         fromY = moveList[move - 1][1] - ONE;
16210         toX = moveList[move - 1][2] - AAA;
16211         toY = moveList[move - 1][3] - ONE;
16212         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16213             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16214             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16215             fromX == toX) {
16216             /* 2-square pawn move just happened */
16217             *p++ = toX + AAA;
16218             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16219         } else {
16220             *p++ = '-';
16221         }
16222     } else if(move == backwardMostMove) {
16223         // [HGM] perhaps we should always do it like this, and forget the above?
16224         if((signed char)boards[move][EP_STATUS] >= 0) {
16225             *p++ = boards[move][EP_STATUS] + AAA;
16226             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16227         } else {
16228             *p++ = '-';
16229         }
16230     } else {
16231         *p++ = '-';
16232     }
16233     *p++ = ' ';
16234   }
16235   }
16236
16237     /* [HGM] find reversible plies */
16238     {   int i = 0, j=move;
16239
16240         if (appData.debugMode) { int k;
16241             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16242             for(k=backwardMostMove; k<=forwardMostMove; k++)
16243                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16244
16245         }
16246
16247         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16248         if( j == backwardMostMove ) i += initialRulePlies;
16249         sprintf(p, "%d ", i);
16250         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16251     }
16252     /* Fullmove number */
16253     sprintf(p, "%d", (move / 2) + 1);
16254
16255     return StrSave(buf);
16256 }
16257
16258 Boolean
16259 ParseFEN(board, blackPlaysFirst, fen)
16260     Board board;
16261      int *blackPlaysFirst;
16262      char *fen;
16263 {
16264     int i, j;
16265     char *p, c;
16266     int emptycount;
16267     ChessSquare piece;
16268
16269     p = fen;
16270
16271     /* [HGM] by default clear Crazyhouse holdings, if present */
16272     if(gameInfo.holdingsWidth) {
16273        for(i=0; i<BOARD_HEIGHT; i++) {
16274            board[i][0]             = EmptySquare; /* black holdings */
16275            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16276            board[i][1]             = (ChessSquare) 0; /* black counts */
16277            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16278        }
16279     }
16280
16281     /* Piece placement data */
16282     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16283         j = 0;
16284         for (;;) {
16285             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16286                 if (*p == '/') p++;
16287                 emptycount = gameInfo.boardWidth - j;
16288                 while (emptycount--)
16289                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16290                 break;
16291 #if(BOARD_FILES >= 10)
16292             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16293                 p++; emptycount=10;
16294                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16295                 while (emptycount--)
16296                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16297 #endif
16298             } else if (isdigit(*p)) {
16299                 emptycount = *p++ - '0';
16300                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16301                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16302                 while (emptycount--)
16303                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16304             } else if (*p == '+' || isalpha(*p)) {
16305                 if (j >= gameInfo.boardWidth) return FALSE;
16306                 if(*p=='+') {
16307                     piece = CharToPiece(*++p);
16308                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16309                     piece = (ChessSquare) (PROMOTED piece ); p++;
16310                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16311                 } else piece = CharToPiece(*p++);
16312
16313                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16314                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16315                     piece = (ChessSquare) (PROMOTED piece);
16316                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16317                     p++;
16318                 }
16319                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16320             } else {
16321                 return FALSE;
16322             }
16323         }
16324     }
16325     while (*p == '/' || *p == ' ') p++;
16326
16327     /* [HGM] look for Crazyhouse holdings here */
16328     while(*p==' ') p++;
16329     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16330         if(*p == '[') p++;
16331         if(*p == '-' ) p++; /* empty holdings */ else {
16332             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16333             /* if we would allow FEN reading to set board size, we would   */
16334             /* have to add holdings and shift the board read so far here   */
16335             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16336                 p++;
16337                 if((int) piece >= (int) BlackPawn ) {
16338                     i = (int)piece - (int)BlackPawn;
16339                     i = PieceToNumber((ChessSquare)i);
16340                     if( i >= gameInfo.holdingsSize ) return FALSE;
16341                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16342                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16343                 } else {
16344                     i = (int)piece - (int)WhitePawn;
16345                     i = PieceToNumber((ChessSquare)i);
16346                     if( i >= gameInfo.holdingsSize ) return FALSE;
16347                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16348                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16349                 }
16350             }
16351         }
16352         if(*p == ']') p++;
16353     }
16354
16355     while(*p == ' ') p++;
16356
16357     /* Active color */
16358     c = *p++;
16359     if(appData.colorNickNames) {
16360       if( c == appData.colorNickNames[0] ) c = 'w'; else
16361       if( c == appData.colorNickNames[1] ) c = 'b';
16362     }
16363     switch (c) {
16364       case 'w':
16365         *blackPlaysFirst = FALSE;
16366         break;
16367       case 'b':
16368         *blackPlaysFirst = TRUE;
16369         break;
16370       default:
16371         return FALSE;
16372     }
16373
16374     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16375     /* return the extra info in global variiables             */
16376
16377     /* set defaults in case FEN is incomplete */
16378     board[EP_STATUS] = EP_UNKNOWN;
16379     for(i=0; i<nrCastlingRights; i++ ) {
16380         board[CASTLING][i] =
16381             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16382     }   /* assume possible unless obviously impossible */
16383     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16384     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16385     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16386                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16387     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16388     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16389     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16390                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16391     FENrulePlies = 0;
16392
16393     while(*p==' ') p++;
16394     if(nrCastlingRights) {
16395       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16396           /* castling indicator present, so default becomes no castlings */
16397           for(i=0; i<nrCastlingRights; i++ ) {
16398                  board[CASTLING][i] = NoRights;
16399           }
16400       }
16401       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16402              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16403              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16404              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16405         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16406
16407         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16408             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16409             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16410         }
16411         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16412             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16413         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16414                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16415         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16416                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16417         switch(c) {
16418           case'K':
16419               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16420               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16421               board[CASTLING][2] = whiteKingFile;
16422               break;
16423           case'Q':
16424               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16425               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16426               board[CASTLING][2] = whiteKingFile;
16427               break;
16428           case'k':
16429               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16430               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16431               board[CASTLING][5] = blackKingFile;
16432               break;
16433           case'q':
16434               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16435               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16436               board[CASTLING][5] = blackKingFile;
16437           case '-':
16438               break;
16439           default: /* FRC castlings */
16440               if(c >= 'a') { /* black rights */
16441                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16442                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16443                   if(i == BOARD_RGHT) break;
16444                   board[CASTLING][5] = i;
16445                   c -= AAA;
16446                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16447                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16448                   if(c > i)
16449                       board[CASTLING][3] = c;
16450                   else
16451                       board[CASTLING][4] = c;
16452               } else { /* white rights */
16453                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16454                     if(board[0][i] == WhiteKing) break;
16455                   if(i == BOARD_RGHT) break;
16456                   board[CASTLING][2] = i;
16457                   c -= AAA - 'a' + 'A';
16458                   if(board[0][c] >= WhiteKing) break;
16459                   if(c > i)
16460                       board[CASTLING][0] = c;
16461                   else
16462                       board[CASTLING][1] = c;
16463               }
16464         }
16465       }
16466       for(i=0; i<nrCastlingRights; i++)
16467         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16468     if (appData.debugMode) {
16469         fprintf(debugFP, "FEN castling rights:");
16470         for(i=0; i<nrCastlingRights; i++)
16471         fprintf(debugFP, " %d", board[CASTLING][i]);
16472         fprintf(debugFP, "\n");
16473     }
16474
16475       while(*p==' ') p++;
16476     }
16477
16478     /* read e.p. field in games that know e.p. capture */
16479     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16480        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16481       if(*p=='-') {
16482         p++; board[EP_STATUS] = EP_NONE;
16483       } else {
16484          char c = *p++ - AAA;
16485
16486          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16487          if(*p >= '0' && *p <='9') p++;
16488          board[EP_STATUS] = c;
16489       }
16490     }
16491
16492
16493     if(sscanf(p, "%d", &i) == 1) {
16494         FENrulePlies = i; /* 50-move ply counter */
16495         /* (The move number is still ignored)    */
16496     }
16497
16498     return TRUE;
16499 }
16500
16501 void
16502 EditPositionPasteFEN(char *fen)
16503 {
16504   if (fen != NULL) {
16505     Board initial_position;
16506
16507     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16508       DisplayError(_("Bad FEN position in clipboard"), 0);
16509       return ;
16510     } else {
16511       int savedBlackPlaysFirst = blackPlaysFirst;
16512       EditPositionEvent();
16513       blackPlaysFirst = savedBlackPlaysFirst;
16514       CopyBoard(boards[0], initial_position);
16515       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16516       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16517       DisplayBothClocks();
16518       DrawPosition(FALSE, boards[currentMove]);
16519     }
16520   }
16521 }
16522
16523 static char cseq[12] = "\\   ";
16524
16525 Boolean set_cont_sequence(char *new_seq)
16526 {
16527     int len;
16528     Boolean ret;
16529
16530     // handle bad attempts to set the sequence
16531         if (!new_seq)
16532                 return 0; // acceptable error - no debug
16533
16534     len = strlen(new_seq);
16535     ret = (len > 0) && (len < sizeof(cseq));
16536     if (ret)
16537       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16538     else if (appData.debugMode)
16539       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16540     return ret;
16541 }
16542
16543 /*
16544     reformat a source message so words don't cross the width boundary.  internal
16545     newlines are not removed.  returns the wrapped size (no null character unless
16546     included in source message).  If dest is NULL, only calculate the size required
16547     for the dest buffer.  lp argument indicats line position upon entry, and it's
16548     passed back upon exit.
16549 */
16550 int wrap(char *dest, char *src, int count, int width, int *lp)
16551 {
16552     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16553
16554     cseq_len = strlen(cseq);
16555     old_line = line = *lp;
16556     ansi = len = clen = 0;
16557
16558     for (i=0; i < count; i++)
16559     {
16560         if (src[i] == '\033')
16561             ansi = 1;
16562
16563         // if we hit the width, back up
16564         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16565         {
16566             // store i & len in case the word is too long
16567             old_i = i, old_len = len;
16568
16569             // find the end of the last word
16570             while (i && src[i] != ' ' && src[i] != '\n')
16571             {
16572                 i--;
16573                 len--;
16574             }
16575
16576             // word too long?  restore i & len before splitting it
16577             if ((old_i-i+clen) >= width)
16578             {
16579                 i = old_i;
16580                 len = old_len;
16581             }
16582
16583             // extra space?
16584             if (i && src[i-1] == ' ')
16585                 len--;
16586
16587             if (src[i] != ' ' && src[i] != '\n')
16588             {
16589                 i--;
16590                 if (len)
16591                     len--;
16592             }
16593
16594             // now append the newline and continuation sequence
16595             if (dest)
16596                 dest[len] = '\n';
16597             len++;
16598             if (dest)
16599                 strncpy(dest+len, cseq, cseq_len);
16600             len += cseq_len;
16601             line = cseq_len;
16602             clen = cseq_len;
16603             continue;
16604         }
16605
16606         if (dest)
16607             dest[len] = src[i];
16608         len++;
16609         if (!ansi)
16610             line++;
16611         if (src[i] == '\n')
16612             line = 0;
16613         if (src[i] == 'm')
16614             ansi = 0;
16615     }
16616     if (dest && appData.debugMode)
16617     {
16618         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16619             count, width, line, len, *lp);
16620         show_bytes(debugFP, src, count);
16621         fprintf(debugFP, "\ndest: ");
16622         show_bytes(debugFP, dest, len);
16623         fprintf(debugFP, "\n");
16624     }
16625     *lp = dest ? line : old_line;
16626
16627     return len;
16628 }
16629
16630 // [HGM] vari: routines for shelving variations
16631 Boolean modeRestore = FALSE;
16632
16633 void
16634 PushInner(int firstMove, int lastMove)
16635 {
16636         int i, j, nrMoves = lastMove - firstMove;
16637
16638         // push current tail of game on stack
16639         savedResult[storedGames] = gameInfo.result;
16640         savedDetails[storedGames] = gameInfo.resultDetails;
16641         gameInfo.resultDetails = NULL;
16642         savedFirst[storedGames] = firstMove;
16643         savedLast [storedGames] = lastMove;
16644         savedFramePtr[storedGames] = framePtr;
16645         framePtr -= nrMoves; // reserve space for the boards
16646         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16647             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16648             for(j=0; j<MOVE_LEN; j++)
16649                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16650             for(j=0; j<2*MOVE_LEN; j++)
16651                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16652             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16653             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16654             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16655             pvInfoList[firstMove+i-1].depth = 0;
16656             commentList[framePtr+i] = commentList[firstMove+i];
16657             commentList[firstMove+i] = NULL;
16658         }
16659
16660         storedGames++;
16661         forwardMostMove = firstMove; // truncate game so we can start variation
16662 }
16663
16664 void
16665 PushTail(int firstMove, int lastMove)
16666 {
16667         if(appData.icsActive) { // only in local mode
16668                 forwardMostMove = currentMove; // mimic old ICS behavior
16669                 return;
16670         }
16671         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16672
16673         PushInner(firstMove, lastMove);
16674         if(storedGames == 1) GreyRevert(FALSE);
16675         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16676 }
16677
16678 void
16679 PopInner(Boolean annotate)
16680 {
16681         int i, j, nrMoves;
16682         char buf[8000], moveBuf[20];
16683
16684         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16685         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16686         nrMoves = savedLast[storedGames] - currentMove;
16687         if(annotate) {
16688                 int cnt = 10;
16689                 if(!WhiteOnMove(currentMove))
16690                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16691                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16692                 for(i=currentMove; i<forwardMostMove; i++) {
16693                         if(WhiteOnMove(i))
16694                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16695                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16696                         strcat(buf, moveBuf);
16697                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16698                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16699                 }
16700                 strcat(buf, ")");
16701         }
16702         for(i=1; i<=nrMoves; i++) { // copy last variation back
16703             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16704             for(j=0; j<MOVE_LEN; j++)
16705                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16706             for(j=0; j<2*MOVE_LEN; j++)
16707                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16708             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16709             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16710             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16711             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16712             commentList[currentMove+i] = commentList[framePtr+i];
16713             commentList[framePtr+i] = NULL;
16714         }
16715         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16716         framePtr = savedFramePtr[storedGames];
16717         gameInfo.result = savedResult[storedGames];
16718         if(gameInfo.resultDetails != NULL) {
16719             free(gameInfo.resultDetails);
16720       }
16721         gameInfo.resultDetails = savedDetails[storedGames];
16722         forwardMostMove = currentMove + nrMoves;
16723 }
16724
16725 Boolean
16726 PopTail(Boolean annotate)
16727 {
16728         if(appData.icsActive) return FALSE; // only in local mode
16729         if(!storedGames) return FALSE; // sanity
16730         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16731
16732         PopInner(annotate);
16733         if(currentMove < forwardMostMove) ForwardEvent(); else
16734         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16735
16736         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16737         return TRUE;
16738 }
16739
16740 void
16741 CleanupTail()
16742 {       // remove all shelved variations
16743         int i;
16744         for(i=0; i<storedGames; i++) {
16745             if(savedDetails[i])
16746                 free(savedDetails[i]);
16747             savedDetails[i] = NULL;
16748         }
16749         for(i=framePtr; i<MAX_MOVES; i++) {
16750                 if(commentList[i]) free(commentList[i]);
16751                 commentList[i] = NULL;
16752         }
16753         framePtr = MAX_MOVES-1;
16754         storedGames = 0;
16755 }
16756
16757 void
16758 LoadVariation(int index, char *text)
16759 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16760         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16761         int level = 0, move;
16762
16763         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16764         // first find outermost bracketing variation
16765         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16766             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16767                 if(*p == '{') wait = '}'; else
16768                 if(*p == '[') wait = ']'; else
16769                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16770                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16771             }
16772             if(*p == wait) wait = NULLCHAR; // closing ]} found
16773             p++;
16774         }
16775         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16776         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16777         end[1] = NULLCHAR; // clip off comment beyond variation
16778         ToNrEvent(currentMove-1);
16779         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16780         // kludge: use ParsePV() to append variation to game
16781         move = currentMove;
16782         ParsePV(start, TRUE, TRUE);
16783         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16784         ClearPremoveHighlights();
16785         CommentPopDown();
16786         ToNrEvent(currentMove+1);
16787 }
16788