05ce95132d66040ca4495a2c08741e283d7234da
[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[0]) { // an engine was selected from the combo box
892         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
893         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
894         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
895         ParseArgsFromString(buf);
896         SwapEngines(i);
897         ReplaceEngine(cps, i);
898         return;
899     }
900     p = engineName;
901     while(q = strchr(p, SLASH)) p = q+1;
902     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
903     if(engineDir[0] != NULLCHAR)
904         appData.directory[i] = engineDir;
905     else if(p != engineName) { // derive directory from engine path, when not given
906         p[-1] = 0;
907         appData.directory[i] = strdup(engineName);
908         p[-1] = SLASH;
909     } else appData.directory[i] = ".";
910     if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
911     if(params[0]) {
912         snprintf(command, MSG_SIZ, "%s %s", p, params);
913         p = command;
914     }
915     appData.chessProgram[i] = strdup(p);
916     appData.isUCI[i] = isUCI;
917     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
918     appData.hasOwnBookUCI[i] = hasBook;
919     if(!nickName[0]) useNick = FALSE;
920     if(useNick) ASSIGN(appData.pgnName[i], nickName);
921     if(addToList) {
922         int len;
923         char quote;
924         q = firstChessProgramNames;
925         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
926         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
927         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
928                         quote, p, quote, appData.directory[i], 
929                         useNick ? " -fn \"" : "",
930                         useNick ? nickName : "",
931                         useNick ? "\"" : "",
932                         v1 ? " -firstProtocolVersion 1" : "",
933                         hasBook ? "" : " -fNoOwnBookUCI",
934                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
935                         storeVariant ? " -variant " : "",
936                         storeVariant ? VariantName(gameInfo.variant) : "");
937         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
938         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
939         if(q)   free(q);
940     }
941     ReplaceEngine(cps, i);
942 }
943
944 void
945 InitTimeControls()
946 {
947     int matched, min, sec;
948     /*
949      * Parse timeControl resource
950      */
951     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
952                           appData.movesPerSession)) {
953         char buf[MSG_SIZ];
954         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
955         DisplayFatalError(buf, 0, 2);
956     }
957
958     /*
959      * Parse searchTime resource
960      */
961     if (*appData.searchTime != NULLCHAR) {
962         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
963         if (matched == 1) {
964             searchTime = min * 60;
965         } else if (matched == 2) {
966             searchTime = min * 60 + sec;
967         } else {
968             char buf[MSG_SIZ];
969             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
970             DisplayFatalError(buf, 0, 2);
971         }
972     }
973 }
974
975 void
976 InitBackEnd1()
977 {
978
979     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
980     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
981
982     GetTimeMark(&programStartTime);
983     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
984     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
985
986     ClearProgramStats();
987     programStats.ok_to_send = 1;
988     programStats.seen_stat = 0;
989
990     /*
991      * Initialize game list
992      */
993     ListNew(&gameList);
994
995
996     /*
997      * Internet chess server status
998      */
999     if (appData.icsActive) {
1000         appData.matchMode = FALSE;
1001         appData.matchGames = 0;
1002 #if ZIPPY
1003         appData.noChessProgram = !appData.zippyPlay;
1004 #else
1005         appData.zippyPlay = FALSE;
1006         appData.zippyTalk = FALSE;
1007         appData.noChessProgram = TRUE;
1008 #endif
1009         if (*appData.icsHelper != NULLCHAR) {
1010             appData.useTelnet = TRUE;
1011             appData.telnetProgram = appData.icsHelper;
1012         }
1013     } else {
1014         appData.zippyTalk = appData.zippyPlay = FALSE;
1015     }
1016
1017     /* [AS] Initialize pv info list [HGM] and game state */
1018     {
1019         int i, j;
1020
1021         for( i=0; i<=framePtr; i++ ) {
1022             pvInfoList[i].depth = -1;
1023             boards[i][EP_STATUS] = EP_NONE;
1024             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1025         }
1026     }
1027
1028     InitTimeControls();
1029
1030     /* [AS] Adjudication threshold */
1031     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1032
1033     InitEngine(&first, 0);
1034     InitEngine(&second, 1);
1035     CommonEngineInit();
1036
1037     pairing.which = "pairing"; // pairing engine
1038     pairing.pr = NoProc;
1039     pairing.isr = NULL;
1040     pairing.program = appData.pairingEngine;
1041     pairing.host = "localhost";
1042     pairing.dir = ".";
1043
1044     if (appData.icsActive) {
1045         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1046     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1047         appData.clockMode = FALSE;
1048         first.sendTime = second.sendTime = 0;
1049     }
1050
1051 #if ZIPPY
1052     /* Override some settings from environment variables, for backward
1053        compatibility.  Unfortunately it's not feasible to have the env
1054        vars just set defaults, at least in xboard.  Ugh.
1055     */
1056     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1057       ZippyInit();
1058     }
1059 #endif
1060
1061     if (!appData.icsActive) {
1062       char buf[MSG_SIZ];
1063       int len;
1064
1065       /* Check for variants that are supported only in ICS mode,
1066          or not at all.  Some that are accepted here nevertheless
1067          have bugs; see comments below.
1068       */
1069       VariantClass variant = StringToVariant(appData.variant);
1070       switch (variant) {
1071       case VariantBughouse:     /* need four players and two boards */
1072       case VariantKriegspiel:   /* need to hide pieces and move details */
1073         /* case VariantFischeRandom: (Fabien: moved below) */
1074         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1075         if( (len > MSG_SIZ) && appData.debugMode )
1076           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1077
1078         DisplayFatalError(buf, 0, 2);
1079         return;
1080
1081       case VariantUnknown:
1082       case VariantLoadable:
1083       case Variant29:
1084       case Variant30:
1085       case Variant31:
1086       case Variant32:
1087       case Variant33:
1088       case Variant34:
1089       case Variant35:
1090       case Variant36:
1091       default:
1092         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1093         if( (len > MSG_SIZ) && appData.debugMode )
1094           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1095
1096         DisplayFatalError(buf, 0, 2);
1097         return;
1098
1099       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1100       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1101       case VariantGothic:     /* [HGM] should work */
1102       case VariantCapablanca: /* [HGM] should work */
1103       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1104       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1105       case VariantKnightmate: /* [HGM] should work */
1106       case VariantCylinder:   /* [HGM] untested */
1107       case VariantFalcon:     /* [HGM] untested */
1108       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1109                                  offboard interposition not understood */
1110       case VariantNormal:     /* definitely works! */
1111       case VariantWildCastle: /* pieces not automatically shuffled */
1112       case VariantNoCastle:   /* pieces not automatically shuffled */
1113       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1114       case VariantLosers:     /* should work except for win condition,
1115                                  and doesn't know captures are mandatory */
1116       case VariantSuicide:    /* should work except for win condition,
1117                                  and doesn't know captures are mandatory */
1118       case VariantGiveaway:   /* should work except for win condition,
1119                                  and doesn't know captures are mandatory */
1120       case VariantTwoKings:   /* should work */
1121       case VariantAtomic:     /* should work except for win condition */
1122       case Variant3Check:     /* should work except for win condition */
1123       case VariantShatranj:   /* should work except for all win conditions */
1124       case VariantMakruk:     /* should work except for draw countdown */
1125       case VariantBerolina:   /* might work if TestLegality is off */
1126       case VariantCapaRandom: /* should work */
1127       case VariantJanus:      /* should work */
1128       case VariantSuper:      /* experimental */
1129       case VariantGreat:      /* experimental, requires legality testing to be off */
1130       case VariantSChess:     /* S-Chess, should work */
1131       case VariantGrand:      /* should work */
1132       case VariantSpartan:    /* should work */
1133         break;
1134       }
1135     }
1136
1137 }
1138
1139 int NextIntegerFromString( char ** str, long * value )
1140 {
1141     int result = -1;
1142     char * s = *str;
1143
1144     while( *s == ' ' || *s == '\t' ) {
1145         s++;
1146     }
1147
1148     *value = 0;
1149
1150     if( *s >= '0' && *s <= '9' ) {
1151         while( *s >= '0' && *s <= '9' ) {
1152             *value = *value * 10 + (*s - '0');
1153             s++;
1154         }
1155
1156         result = 0;
1157     }
1158
1159     *str = s;
1160
1161     return result;
1162 }
1163
1164 int NextTimeControlFromString( char ** str, long * value )
1165 {
1166     long temp;
1167     int result = NextIntegerFromString( str, &temp );
1168
1169     if( result == 0 ) {
1170         *value = temp * 60; /* Minutes */
1171         if( **str == ':' ) {
1172             (*str)++;
1173             result = NextIntegerFromString( str, &temp );
1174             *value += temp; /* Seconds */
1175         }
1176     }
1177
1178     return result;
1179 }
1180
1181 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1182 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1183     int result = -1, type = 0; long temp, temp2;
1184
1185     if(**str != ':') return -1; // old params remain in force!
1186     (*str)++;
1187     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1188     if( NextIntegerFromString( str, &temp ) ) return -1;
1189     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1190
1191     if(**str != '/') {
1192         /* time only: incremental or sudden-death time control */
1193         if(**str == '+') { /* increment follows; read it */
1194             (*str)++;
1195             if(**str == '!') type = *(*str)++; // Bronstein TC
1196             if(result = NextIntegerFromString( str, &temp2)) return -1;
1197             *inc = temp2 * 1000;
1198             if(**str == '.') { // read fraction of increment
1199                 char *start = ++(*str);
1200                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1201                 temp2 *= 1000;
1202                 while(start++ < *str) temp2 /= 10;
1203                 *inc += temp2;
1204             }
1205         } else *inc = 0;
1206         *moves = 0; *tc = temp * 1000; *incType = type;
1207         return 0;
1208     }
1209
1210     (*str)++; /* classical time control */
1211     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1212
1213     if(result == 0) {
1214         *moves = temp;
1215         *tc    = temp2 * 1000;
1216         *inc   = 0;
1217         *incType = type;
1218     }
1219     return result;
1220 }
1221
1222 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1223 {   /* [HGM] get time to add from the multi-session time-control string */
1224     int incType, moves=1; /* kludge to force reading of first session */
1225     long time, increment;
1226     char *s = tcString;
1227
1228     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1229     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1230     do {
1231         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1232         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1233         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1234         if(movenr == -1) return time;    /* last move before new session     */
1235         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1236         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1237         if(!moves) return increment;     /* current session is incremental   */
1238         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1239     } while(movenr >= -1);               /* try again for next session       */
1240
1241     return 0; // no new time quota on this move
1242 }
1243
1244 int
1245 ParseTimeControl(tc, ti, mps)
1246      char *tc;
1247      float ti;
1248      int mps;
1249 {
1250   long tc1;
1251   long tc2;
1252   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1253   int min, sec=0;
1254
1255   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1256   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1257       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1258   if(ti > 0) {
1259
1260     if(mps)
1261       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1262     else 
1263       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1264   } else {
1265     if(mps)
1266       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1267     else 
1268       snprintf(buf, MSG_SIZ, ":%s", mytc);
1269   }
1270   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1271   
1272   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1273     return FALSE;
1274   }
1275
1276   if( *tc == '/' ) {
1277     /* Parse second time control */
1278     tc++;
1279
1280     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1281       return FALSE;
1282     }
1283
1284     if( tc2 == 0 ) {
1285       return FALSE;
1286     }
1287
1288     timeControl_2 = tc2 * 1000;
1289   }
1290   else {
1291     timeControl_2 = 0;
1292   }
1293
1294   if( tc1 == 0 ) {
1295     return FALSE;
1296   }
1297
1298   timeControl = tc1 * 1000;
1299
1300   if (ti >= 0) {
1301     timeIncrement = ti * 1000;  /* convert to ms */
1302     movesPerSession = 0;
1303   } else {
1304     timeIncrement = 0;
1305     movesPerSession = mps;
1306   }
1307   return TRUE;
1308 }
1309
1310 void
1311 InitBackEnd2()
1312 {
1313     if (appData.debugMode) {
1314         fprintf(debugFP, "%s\n", programVersion);
1315     }
1316
1317     set_cont_sequence(appData.wrapContSeq);
1318     if (appData.matchGames > 0) {
1319         appData.matchMode = TRUE;
1320     } else if (appData.matchMode) {
1321         appData.matchGames = 1;
1322     }
1323     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1324         appData.matchGames = appData.sameColorGames;
1325     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1326         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1327         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1328     }
1329     Reset(TRUE, FALSE);
1330     if (appData.noChessProgram || first.protocolVersion == 1) {
1331       InitBackEnd3();
1332     } else {
1333       /* kludge: allow timeout for initial "feature" commands */
1334       FreezeUI();
1335       DisplayMessage("", _("Starting chess program"));
1336       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1337     }
1338 }
1339
1340 int
1341 CalculateIndex(int index, int gameNr)
1342 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1343     int res;
1344     if(index > 0) return index; // fixed nmber
1345     if(index == 0) return 1;
1346     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1347     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1348     return res;
1349 }
1350
1351 int
1352 LoadGameOrPosition(int gameNr)
1353 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1354     if (*appData.loadGameFile != NULLCHAR) {
1355         if (!LoadGameFromFile(appData.loadGameFile,
1356                 CalculateIndex(appData.loadGameIndex, gameNr),
1357                               appData.loadGameFile, FALSE)) {
1358             DisplayFatalError(_("Bad game file"), 0, 1);
1359             return 0;
1360         }
1361     } else if (*appData.loadPositionFile != NULLCHAR) {
1362         if (!LoadPositionFromFile(appData.loadPositionFile,
1363                 CalculateIndex(appData.loadPositionIndex, gameNr),
1364                                   appData.loadPositionFile)) {
1365             DisplayFatalError(_("Bad position file"), 0, 1);
1366             return 0;
1367         }
1368     }
1369     return 1;
1370 }
1371
1372 void
1373 ReserveGame(int gameNr, char resChar)
1374 {
1375     FILE *tf = fopen(appData.tourneyFile, "r+");
1376     char *p, *q, c, buf[MSG_SIZ];
1377     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1378     safeStrCpy(buf, lastMsg, MSG_SIZ);
1379     DisplayMessage(_("Pick new game"), "");
1380     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1381     ParseArgsFromFile(tf);
1382     p = q = appData.results;
1383     if(appData.debugMode) {
1384       char *r = appData.participants;
1385       fprintf(debugFP, "results = '%s'\n", p);
1386       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1387       fprintf(debugFP, "\n");
1388     }
1389     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1390     nextGame = q - p;
1391     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1392     safeStrCpy(q, p, strlen(p) + 2);
1393     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1394     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1395     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1396         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1397         q[nextGame] = '*';
1398     }
1399     fseek(tf, -(strlen(p)+4), SEEK_END);
1400     c = fgetc(tf);
1401     if(c != '"') // depending on DOS or Unix line endings we can be one off
1402          fseek(tf, -(strlen(p)+2), SEEK_END);
1403     else fseek(tf, -(strlen(p)+3), SEEK_END);
1404     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1405     DisplayMessage(buf, "");
1406     free(p); appData.results = q;
1407     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1408        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1409         UnloadEngine(&first);  // next game belongs to other pairing;
1410         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1411     }
1412 }
1413
1414 void
1415 MatchEvent(int mode)
1416 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1417         int dummy;
1418         if(matchMode) { // already in match mode: switch it off
1419             abortMatch = TRUE;
1420             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1421             return;
1422         }
1423 //      if(gameMode != BeginningOfGame) {
1424 //          DisplayError(_("You can only start a match from the initial position."), 0);
1425 //          return;
1426 //      }
1427         abortMatch = FALSE;
1428         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1429         /* Set up machine vs. machine match */
1430         nextGame = 0;
1431         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1432         if(appData.tourneyFile[0]) {
1433             ReserveGame(-1, 0);
1434             if(nextGame > appData.matchGames) {
1435                 char buf[MSG_SIZ];
1436                 if(strchr(appData.results, '*') == NULL) {
1437                     FILE *f;
1438                     appData.tourneyCycles++;
1439                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1440                         fclose(f);
1441                         NextTourneyGame(-1, &dummy);
1442                         ReserveGame(-1, 0);
1443                         if(nextGame <= appData.matchGames) {
1444                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1445                             matchMode = mode;
1446                             ScheduleDelayedEvent(NextMatchGame, 10000);
1447                             return;
1448                         }
1449                     }
1450                 }
1451                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1452                 DisplayError(buf, 0);
1453                 appData.tourneyFile[0] = 0;
1454                 return;
1455             }
1456         } else
1457         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1458             DisplayFatalError(_("Can't have a match with no chess programs"),
1459                               0, 2);
1460             return;
1461         }
1462         matchMode = mode;
1463         matchGame = roundNr = 1;
1464         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1465         NextMatchGame();
1466 }
1467
1468 void
1469 InitBackEnd3 P((void))
1470 {
1471     GameMode initialMode;
1472     char buf[MSG_SIZ];
1473     int err, len;
1474
1475     InitChessProgram(&first, startedFromSetupPosition);
1476
1477     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1478         free(programVersion);
1479         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1480         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1481     }
1482
1483     if (appData.icsActive) {
1484 #ifdef WIN32
1485         /* [DM] Make a console window if needed [HGM] merged ifs */
1486         ConsoleCreate();
1487 #endif
1488         err = establish();
1489         if (err != 0)
1490           {
1491             if (*appData.icsCommPort != NULLCHAR)
1492               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1493                              appData.icsCommPort);
1494             else
1495               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1496                         appData.icsHost, appData.icsPort);
1497
1498             if( (len > MSG_SIZ) && appData.debugMode )
1499               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1500
1501             DisplayFatalError(buf, err, 1);
1502             return;
1503         }
1504         SetICSMode();
1505         telnetISR =
1506           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1507         fromUserISR =
1508           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1509         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1510             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1511     } else if (appData.noChessProgram) {
1512         SetNCPMode();
1513     } else {
1514         SetGNUMode();
1515     }
1516
1517     if (*appData.cmailGameName != NULLCHAR) {
1518         SetCmailMode();
1519         OpenLoopback(&cmailPR);
1520         cmailISR =
1521           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1522     }
1523
1524     ThawUI();
1525     DisplayMessage("", "");
1526     if (StrCaseCmp(appData.initialMode, "") == 0) {
1527       initialMode = BeginningOfGame;
1528       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1529         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1530         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1531         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1532         ModeHighlight();
1533       }
1534     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1535       initialMode = TwoMachinesPlay;
1536     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1537       initialMode = AnalyzeFile;
1538     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1539       initialMode = AnalyzeMode;
1540     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1541       initialMode = MachinePlaysWhite;
1542     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1543       initialMode = MachinePlaysBlack;
1544     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1545       initialMode = EditGame;
1546     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1547       initialMode = EditPosition;
1548     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1549       initialMode = Training;
1550     } else {
1551       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1552       if( (len > MSG_SIZ) && appData.debugMode )
1553         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1554
1555       DisplayFatalError(buf, 0, 2);
1556       return;
1557     }
1558
1559     if (appData.matchMode) {
1560         if(appData.tourneyFile[0]) { // start tourney from command line
1561             FILE *f;
1562             if(f = fopen(appData.tourneyFile, "r")) {
1563                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1564                 fclose(f);
1565                 appData.clockMode = TRUE;
1566                 SetGNUMode();
1567             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1568         }
1569         MatchEvent(TRUE);
1570     } else if (*appData.cmailGameName != NULLCHAR) {
1571         /* Set up cmail mode */
1572         ReloadCmailMsgEvent(TRUE);
1573     } else {
1574         /* Set up other modes */
1575         if (initialMode == AnalyzeFile) {
1576           if (*appData.loadGameFile == NULLCHAR) {
1577             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1578             return;
1579           }
1580         }
1581         if (*appData.loadGameFile != NULLCHAR) {
1582             (void) LoadGameFromFile(appData.loadGameFile,
1583                                     appData.loadGameIndex,
1584                                     appData.loadGameFile, TRUE);
1585         } else if (*appData.loadPositionFile != NULLCHAR) {
1586             (void) LoadPositionFromFile(appData.loadPositionFile,
1587                                         appData.loadPositionIndex,
1588                                         appData.loadPositionFile);
1589             /* [HGM] try to make self-starting even after FEN load */
1590             /* to allow automatic setup of fairy variants with wtm */
1591             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1592                 gameMode = BeginningOfGame;
1593                 setboardSpoiledMachineBlack = 1;
1594             }
1595             /* [HGM] loadPos: make that every new game uses the setup */
1596             /* from file as long as we do not switch variant          */
1597             if(!blackPlaysFirst) {
1598                 startedFromPositionFile = TRUE;
1599                 CopyBoard(filePosition, boards[0]);
1600             }
1601         }
1602         if (initialMode == AnalyzeMode) {
1603           if (appData.noChessProgram) {
1604             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1605             return;
1606           }
1607           if (appData.icsActive) {
1608             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1609             return;
1610           }
1611           AnalyzeModeEvent();
1612         } else if (initialMode == AnalyzeFile) {
1613           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1614           ShowThinkingEvent();
1615           AnalyzeFileEvent();
1616           AnalysisPeriodicEvent(1);
1617         } else if (initialMode == MachinePlaysWhite) {
1618           if (appData.noChessProgram) {
1619             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1620                               0, 2);
1621             return;
1622           }
1623           if (appData.icsActive) {
1624             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1625                               0, 2);
1626             return;
1627           }
1628           MachineWhiteEvent();
1629         } else if (initialMode == MachinePlaysBlack) {
1630           if (appData.noChessProgram) {
1631             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1632                               0, 2);
1633             return;
1634           }
1635           if (appData.icsActive) {
1636             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1637                               0, 2);
1638             return;
1639           }
1640           MachineBlackEvent();
1641         } else if (initialMode == TwoMachinesPlay) {
1642           if (appData.noChessProgram) {
1643             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1644                               0, 2);
1645             return;
1646           }
1647           if (appData.icsActive) {
1648             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1649                               0, 2);
1650             return;
1651           }
1652           TwoMachinesEvent();
1653         } else if (initialMode == EditGame) {
1654           EditGameEvent();
1655         } else if (initialMode == EditPosition) {
1656           EditPositionEvent();
1657         } else if (initialMode == Training) {
1658           if (*appData.loadGameFile == NULLCHAR) {
1659             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1660             return;
1661           }
1662           TrainingEvent();
1663         }
1664     }
1665 }
1666
1667 /*
1668  * Establish will establish a contact to a remote host.port.
1669  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1670  *  used to talk to the host.
1671  * Returns 0 if okay, error code if not.
1672  */
1673 int
1674 establish()
1675 {
1676     char buf[MSG_SIZ];
1677
1678     if (*appData.icsCommPort != NULLCHAR) {
1679         /* Talk to the host through a serial comm port */
1680         return OpenCommPort(appData.icsCommPort, &icsPR);
1681
1682     } else if (*appData.gateway != NULLCHAR) {
1683         if (*appData.remoteShell == NULLCHAR) {
1684             /* Use the rcmd protocol to run telnet program on a gateway host */
1685             snprintf(buf, sizeof(buf), "%s %s %s",
1686                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1687             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1688
1689         } else {
1690             /* Use the rsh program to run telnet program on a gateway host */
1691             if (*appData.remoteUser == NULLCHAR) {
1692                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1693                         appData.gateway, appData.telnetProgram,
1694                         appData.icsHost, appData.icsPort);
1695             } else {
1696                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1697                         appData.remoteShell, appData.gateway,
1698                         appData.remoteUser, appData.telnetProgram,
1699                         appData.icsHost, appData.icsPort);
1700             }
1701             return StartChildProcess(buf, "", &icsPR);
1702
1703         }
1704     } else if (appData.useTelnet) {
1705         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1706
1707     } else {
1708         /* TCP socket interface differs somewhat between
1709            Unix and NT; handle details in the front end.
1710            */
1711         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1712     }
1713 }
1714
1715 void EscapeExpand(char *p, char *q)
1716 {       // [HGM] initstring: routine to shape up string arguments
1717         while(*p++ = *q++) if(p[-1] == '\\')
1718             switch(*q++) {
1719                 case 'n': p[-1] = '\n'; break;
1720                 case 'r': p[-1] = '\r'; break;
1721                 case 't': p[-1] = '\t'; break;
1722                 case '\\': p[-1] = '\\'; break;
1723                 case 0: *p = 0; return;
1724                 default: p[-1] = q[-1]; break;
1725             }
1726 }
1727
1728 void
1729 show_bytes(fp, buf, count)
1730      FILE *fp;
1731      char *buf;
1732      int count;
1733 {
1734     while (count--) {
1735         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1736             fprintf(fp, "\\%03o", *buf & 0xff);
1737         } else {
1738             putc(*buf, fp);
1739         }
1740         buf++;
1741     }
1742     fflush(fp);
1743 }
1744
1745 /* Returns an errno value */
1746 int
1747 OutputMaybeTelnet(pr, message, count, outError)
1748      ProcRef pr;
1749      char *message;
1750      int count;
1751      int *outError;
1752 {
1753     char buf[8192], *p, *q, *buflim;
1754     int left, newcount, outcount;
1755
1756     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1757         *appData.gateway != NULLCHAR) {
1758         if (appData.debugMode) {
1759             fprintf(debugFP, ">ICS: ");
1760             show_bytes(debugFP, message, count);
1761             fprintf(debugFP, "\n");
1762         }
1763         return OutputToProcess(pr, message, count, outError);
1764     }
1765
1766     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1767     p = message;
1768     q = buf;
1769     left = count;
1770     newcount = 0;
1771     while (left) {
1772         if (q >= buflim) {
1773             if (appData.debugMode) {
1774                 fprintf(debugFP, ">ICS: ");
1775                 show_bytes(debugFP, buf, newcount);
1776                 fprintf(debugFP, "\n");
1777             }
1778             outcount = OutputToProcess(pr, buf, newcount, outError);
1779             if (outcount < newcount) return -1; /* to be sure */
1780             q = buf;
1781             newcount = 0;
1782         }
1783         if (*p == '\n') {
1784             *q++ = '\r';
1785             newcount++;
1786         } else if (((unsigned char) *p) == TN_IAC) {
1787             *q++ = (char) TN_IAC;
1788             newcount ++;
1789         }
1790         *q++ = *p++;
1791         newcount++;
1792         left--;
1793     }
1794     if (appData.debugMode) {
1795         fprintf(debugFP, ">ICS: ");
1796         show_bytes(debugFP, buf, newcount);
1797         fprintf(debugFP, "\n");
1798     }
1799     outcount = OutputToProcess(pr, buf, newcount, outError);
1800     if (outcount < newcount) return -1; /* to be sure */
1801     return count;
1802 }
1803
1804 void
1805 read_from_player(isr, closure, message, count, error)
1806      InputSourceRef isr;
1807      VOIDSTAR closure;
1808      char *message;
1809      int count;
1810      int error;
1811 {
1812     int outError, outCount;
1813     static int gotEof = 0;
1814
1815     /* Pass data read from player on to ICS */
1816     if (count > 0) {
1817         gotEof = 0;
1818         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1819         if (outCount < count) {
1820             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1821         }
1822     } else if (count < 0) {
1823         RemoveInputSource(isr);
1824         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1825     } else if (gotEof++ > 0) {
1826         RemoveInputSource(isr);
1827         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1828     }
1829 }
1830
1831 void
1832 KeepAlive()
1833 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1834     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1835     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1836     SendToICS("date\n");
1837     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1838 }
1839
1840 /* added routine for printf style output to ics */
1841 void ics_printf(char *format, ...)
1842 {
1843     char buffer[MSG_SIZ];
1844     va_list args;
1845
1846     va_start(args, format);
1847     vsnprintf(buffer, sizeof(buffer), format, args);
1848     buffer[sizeof(buffer)-1] = '\0';
1849     SendToICS(buffer);
1850     va_end(args);
1851 }
1852
1853 void
1854 SendToICS(s)
1855      char *s;
1856 {
1857     int count, outCount, outError;
1858
1859     if (icsPR == NULL) return;
1860
1861     count = strlen(s);
1862     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1863     if (outCount < count) {
1864         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1865     }
1866 }
1867
1868 /* This is used for sending logon scripts to the ICS. Sending
1869    without a delay causes problems when using timestamp on ICC
1870    (at least on my machine). */
1871 void
1872 SendToICSDelayed(s,msdelay)
1873      char *s;
1874      long msdelay;
1875 {
1876     int count, outCount, outError;
1877
1878     if (icsPR == NULL) return;
1879
1880     count = strlen(s);
1881     if (appData.debugMode) {
1882         fprintf(debugFP, ">ICS: ");
1883         show_bytes(debugFP, s, count);
1884         fprintf(debugFP, "\n");
1885     }
1886     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1887                                       msdelay);
1888     if (outCount < count) {
1889         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1890     }
1891 }
1892
1893
1894 /* Remove all highlighting escape sequences in s
1895    Also deletes any suffix starting with '('
1896    */
1897 char *
1898 StripHighlightAndTitle(s)
1899      char *s;
1900 {
1901     static char retbuf[MSG_SIZ];
1902     char *p = retbuf;
1903
1904     while (*s != NULLCHAR) {
1905         while (*s == '\033') {
1906             while (*s != NULLCHAR && !isalpha(*s)) s++;
1907             if (*s != NULLCHAR) s++;
1908         }
1909         while (*s != NULLCHAR && *s != '\033') {
1910             if (*s == '(' || *s == '[') {
1911                 *p = NULLCHAR;
1912                 return retbuf;
1913             }
1914             *p++ = *s++;
1915         }
1916     }
1917     *p = NULLCHAR;
1918     return retbuf;
1919 }
1920
1921 /* Remove all highlighting escape sequences in s */
1922 char *
1923 StripHighlight(s)
1924      char *s;
1925 {
1926     static char retbuf[MSG_SIZ];
1927     char *p = retbuf;
1928
1929     while (*s != NULLCHAR) {
1930         while (*s == '\033') {
1931             while (*s != NULLCHAR && !isalpha(*s)) s++;
1932             if (*s != NULLCHAR) s++;
1933         }
1934         while (*s != NULLCHAR && *s != '\033') {
1935             *p++ = *s++;
1936         }
1937     }
1938     *p = NULLCHAR;
1939     return retbuf;
1940 }
1941
1942 char *variantNames[] = VARIANT_NAMES;
1943 char *
1944 VariantName(v)
1945      VariantClass v;
1946 {
1947     return variantNames[v];
1948 }
1949
1950
1951 /* Identify a variant from the strings the chess servers use or the
1952    PGN Variant tag names we use. */
1953 VariantClass
1954 StringToVariant(e)
1955      char *e;
1956 {
1957     char *p;
1958     int wnum = -1;
1959     VariantClass v = VariantNormal;
1960     int i, found = FALSE;
1961     char buf[MSG_SIZ];
1962     int len;
1963
1964     if (!e) return v;
1965
1966     /* [HGM] skip over optional board-size prefixes */
1967     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1968         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1969         while( *e++ != '_');
1970     }
1971
1972     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1973         v = VariantNormal;
1974         found = TRUE;
1975     } else
1976     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1977       if (StrCaseStr(e, variantNames[i])) {
1978         v = (VariantClass) i;
1979         found = TRUE;
1980         break;
1981       }
1982     }
1983
1984     if (!found) {
1985       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1986           || StrCaseStr(e, "wild/fr")
1987           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1988         v = VariantFischeRandom;
1989       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1990                  (i = 1, p = StrCaseStr(e, "w"))) {
1991         p += i;
1992         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1993         if (isdigit(*p)) {
1994           wnum = atoi(p);
1995         } else {
1996           wnum = -1;
1997         }
1998         switch (wnum) {
1999         case 0: /* FICS only, actually */
2000         case 1:
2001           /* Castling legal even if K starts on d-file */
2002           v = VariantWildCastle;
2003           break;
2004         case 2:
2005         case 3:
2006         case 4:
2007           /* Castling illegal even if K & R happen to start in
2008              normal positions. */
2009           v = VariantNoCastle;
2010           break;
2011         case 5:
2012         case 7:
2013         case 8:
2014         case 10:
2015         case 11:
2016         case 12:
2017         case 13:
2018         case 14:
2019         case 15:
2020         case 18:
2021         case 19:
2022           /* Castling legal iff K & R start in normal positions */
2023           v = VariantNormal;
2024           break;
2025         case 6:
2026         case 20:
2027         case 21:
2028           /* Special wilds for position setup; unclear what to do here */
2029           v = VariantLoadable;
2030           break;
2031         case 9:
2032           /* Bizarre ICC game */
2033           v = VariantTwoKings;
2034           break;
2035         case 16:
2036           v = VariantKriegspiel;
2037           break;
2038         case 17:
2039           v = VariantLosers;
2040           break;
2041         case 22:
2042           v = VariantFischeRandom;
2043           break;
2044         case 23:
2045           v = VariantCrazyhouse;
2046           break;
2047         case 24:
2048           v = VariantBughouse;
2049           break;
2050         case 25:
2051           v = Variant3Check;
2052           break;
2053         case 26:
2054           /* Not quite the same as FICS suicide! */
2055           v = VariantGiveaway;
2056           break;
2057         case 27:
2058           v = VariantAtomic;
2059           break;
2060         case 28:
2061           v = VariantShatranj;
2062           break;
2063
2064         /* Temporary names for future ICC types.  The name *will* change in
2065            the next xboard/WinBoard release after ICC defines it. */
2066         case 29:
2067           v = Variant29;
2068           break;
2069         case 30:
2070           v = Variant30;
2071           break;
2072         case 31:
2073           v = Variant31;
2074           break;
2075         case 32:
2076           v = Variant32;
2077           break;
2078         case 33:
2079           v = Variant33;
2080           break;
2081         case 34:
2082           v = Variant34;
2083           break;
2084         case 35:
2085           v = Variant35;
2086           break;
2087         case 36:
2088           v = Variant36;
2089           break;
2090         case 37:
2091           v = VariantShogi;
2092           break;
2093         case 38:
2094           v = VariantXiangqi;
2095           break;
2096         case 39:
2097           v = VariantCourier;
2098           break;
2099         case 40:
2100           v = VariantGothic;
2101           break;
2102         case 41:
2103           v = VariantCapablanca;
2104           break;
2105         case 42:
2106           v = VariantKnightmate;
2107           break;
2108         case 43:
2109           v = VariantFairy;
2110           break;
2111         case 44:
2112           v = VariantCylinder;
2113           break;
2114         case 45:
2115           v = VariantFalcon;
2116           break;
2117         case 46:
2118           v = VariantCapaRandom;
2119           break;
2120         case 47:
2121           v = VariantBerolina;
2122           break;
2123         case 48:
2124           v = VariantJanus;
2125           break;
2126         case 49:
2127           v = VariantSuper;
2128           break;
2129         case 50:
2130           v = VariantGreat;
2131           break;
2132         case -1:
2133           /* Found "wild" or "w" in the string but no number;
2134              must assume it's normal chess. */
2135           v = VariantNormal;
2136           break;
2137         default:
2138           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2139           if( (len > MSG_SIZ) && appData.debugMode )
2140             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2141
2142           DisplayError(buf, 0);
2143           v = VariantUnknown;
2144           break;
2145         }
2146       }
2147     }
2148     if (appData.debugMode) {
2149       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2150               e, wnum, VariantName(v));
2151     }
2152     return v;
2153 }
2154
2155 static int leftover_start = 0, leftover_len = 0;
2156 char star_match[STAR_MATCH_N][MSG_SIZ];
2157
2158 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2159    advance *index beyond it, and set leftover_start to the new value of
2160    *index; else return FALSE.  If pattern contains the character '*', it
2161    matches any sequence of characters not containing '\r', '\n', or the
2162    character following the '*' (if any), and the matched sequence(s) are
2163    copied into star_match.
2164    */
2165 int
2166 looking_at(buf, index, pattern)
2167      char *buf;
2168      int *index;
2169      char *pattern;
2170 {
2171     char *bufp = &buf[*index], *patternp = pattern;
2172     int star_count = 0;
2173     char *matchp = star_match[0];
2174
2175     for (;;) {
2176         if (*patternp == NULLCHAR) {
2177             *index = leftover_start = bufp - buf;
2178             *matchp = NULLCHAR;
2179             return TRUE;
2180         }
2181         if (*bufp == NULLCHAR) return FALSE;
2182         if (*patternp == '*') {
2183             if (*bufp == *(patternp + 1)) {
2184                 *matchp = NULLCHAR;
2185                 matchp = star_match[++star_count];
2186                 patternp += 2;
2187                 bufp++;
2188                 continue;
2189             } else if (*bufp == '\n' || *bufp == '\r') {
2190                 patternp++;
2191                 if (*patternp == NULLCHAR)
2192                   continue;
2193                 else
2194                   return FALSE;
2195             } else {
2196                 *matchp++ = *bufp++;
2197                 continue;
2198             }
2199         }
2200         if (*patternp != *bufp) return FALSE;
2201         patternp++;
2202         bufp++;
2203     }
2204 }
2205
2206 void
2207 SendToPlayer(data, length)
2208      char *data;
2209      int length;
2210 {
2211     int error, outCount;
2212     outCount = OutputToProcess(NoProc, data, length, &error);
2213     if (outCount < length) {
2214         DisplayFatalError(_("Error writing to display"), error, 1);
2215     }
2216 }
2217
2218 void
2219 PackHolding(packed, holding)
2220      char packed[];
2221      char *holding;
2222 {
2223     char *p = holding;
2224     char *q = packed;
2225     int runlength = 0;
2226     int curr = 9999;
2227     do {
2228         if (*p == curr) {
2229             runlength++;
2230         } else {
2231             switch (runlength) {
2232               case 0:
2233                 break;
2234               case 1:
2235                 *q++ = curr;
2236                 break;
2237               case 2:
2238                 *q++ = curr;
2239                 *q++ = curr;
2240                 break;
2241               default:
2242                 sprintf(q, "%d", runlength);
2243                 while (*q) q++;
2244                 *q++ = curr;
2245                 break;
2246             }
2247             runlength = 1;
2248             curr = *p;
2249         }
2250     } while (*p++);
2251     *q = NULLCHAR;
2252 }
2253
2254 /* Telnet protocol requests from the front end */
2255 void
2256 TelnetRequest(ddww, option)
2257      unsigned char ddww, option;
2258 {
2259     unsigned char msg[3];
2260     int outCount, outError;
2261
2262     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2263
2264     if (appData.debugMode) {
2265         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2266         switch (ddww) {
2267           case TN_DO:
2268             ddwwStr = "DO";
2269             break;
2270           case TN_DONT:
2271             ddwwStr = "DONT";
2272             break;
2273           case TN_WILL:
2274             ddwwStr = "WILL";
2275             break;
2276           case TN_WONT:
2277             ddwwStr = "WONT";
2278             break;
2279           default:
2280             ddwwStr = buf1;
2281             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2282             break;
2283         }
2284         switch (option) {
2285           case TN_ECHO:
2286             optionStr = "ECHO";
2287             break;
2288           default:
2289             optionStr = buf2;
2290             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2291             break;
2292         }
2293         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2294     }
2295     msg[0] = TN_IAC;
2296     msg[1] = ddww;
2297     msg[2] = option;
2298     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2299     if (outCount < 3) {
2300         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2301     }
2302 }
2303
2304 void
2305 DoEcho()
2306 {
2307     if (!appData.icsActive) return;
2308     TelnetRequest(TN_DO, TN_ECHO);
2309 }
2310
2311 void
2312 DontEcho()
2313 {
2314     if (!appData.icsActive) return;
2315     TelnetRequest(TN_DONT, TN_ECHO);
2316 }
2317
2318 void
2319 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2320 {
2321     /* put the holdings sent to us by the server on the board holdings area */
2322     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2323     char p;
2324     ChessSquare piece;
2325
2326     if(gameInfo.holdingsWidth < 2)  return;
2327     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2328         return; // prevent overwriting by pre-board holdings
2329
2330     if( (int)lowestPiece >= BlackPawn ) {
2331         holdingsColumn = 0;
2332         countsColumn = 1;
2333         holdingsStartRow = BOARD_HEIGHT-1;
2334         direction = -1;
2335     } else {
2336         holdingsColumn = BOARD_WIDTH-1;
2337         countsColumn = BOARD_WIDTH-2;
2338         holdingsStartRow = 0;
2339         direction = 1;
2340     }
2341
2342     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2343         board[i][holdingsColumn] = EmptySquare;
2344         board[i][countsColumn]   = (ChessSquare) 0;
2345     }
2346     while( (p=*holdings++) != NULLCHAR ) {
2347         piece = CharToPiece( ToUpper(p) );
2348         if(piece == EmptySquare) continue;
2349         /*j = (int) piece - (int) WhitePawn;*/
2350         j = PieceToNumber(piece);
2351         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2352         if(j < 0) continue;               /* should not happen */
2353         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2354         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2355         board[holdingsStartRow+j*direction][countsColumn]++;
2356     }
2357 }
2358
2359
2360 void
2361 VariantSwitch(Board board, VariantClass newVariant)
2362 {
2363    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2364    static Board oldBoard;
2365
2366    startedFromPositionFile = FALSE;
2367    if(gameInfo.variant == newVariant) return;
2368
2369    /* [HGM] This routine is called each time an assignment is made to
2370     * gameInfo.variant during a game, to make sure the board sizes
2371     * are set to match the new variant. If that means adding or deleting
2372     * holdings, we shift the playing board accordingly
2373     * This kludge is needed because in ICS observe mode, we get boards
2374     * of an ongoing game without knowing the variant, and learn about the
2375     * latter only later. This can be because of the move list we requested,
2376     * in which case the game history is refilled from the beginning anyway,
2377     * but also when receiving holdings of a crazyhouse game. In the latter
2378     * case we want to add those holdings to the already received position.
2379     */
2380
2381
2382    if (appData.debugMode) {
2383      fprintf(debugFP, "Switch board from %s to %s\n",
2384              VariantName(gameInfo.variant), VariantName(newVariant));
2385      setbuf(debugFP, NULL);
2386    }
2387    shuffleOpenings = 0;       /* [HGM] shuffle */
2388    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2389    switch(newVariant)
2390      {
2391      case VariantShogi:
2392        newWidth = 9;  newHeight = 9;
2393        gameInfo.holdingsSize = 7;
2394      case VariantBughouse:
2395      case VariantCrazyhouse:
2396        newHoldingsWidth = 2; break;
2397      case VariantGreat:
2398        newWidth = 10;
2399      case VariantSuper:
2400        newHoldingsWidth = 2;
2401        gameInfo.holdingsSize = 8;
2402        break;
2403      case VariantGothic:
2404      case VariantCapablanca:
2405      case VariantCapaRandom:
2406        newWidth = 10;
2407      default:
2408        newHoldingsWidth = gameInfo.holdingsSize = 0;
2409      };
2410
2411    if(newWidth  != gameInfo.boardWidth  ||
2412       newHeight != gameInfo.boardHeight ||
2413       newHoldingsWidth != gameInfo.holdingsWidth ) {
2414
2415      /* shift position to new playing area, if needed */
2416      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2417        for(i=0; i<BOARD_HEIGHT; i++)
2418          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2419            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2420              board[i][j];
2421        for(i=0; i<newHeight; i++) {
2422          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2423          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2424        }
2425      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2426        for(i=0; i<BOARD_HEIGHT; i++)
2427          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2428            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2429              board[i][j];
2430      }
2431      gameInfo.boardWidth  = newWidth;
2432      gameInfo.boardHeight = newHeight;
2433      gameInfo.holdingsWidth = newHoldingsWidth;
2434      gameInfo.variant = newVariant;
2435      InitDrawingSizes(-2, 0);
2436    } else gameInfo.variant = newVariant;
2437    CopyBoard(oldBoard, board);   // remember correctly formatted board
2438      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2439    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2440 }
2441
2442 static int loggedOn = FALSE;
2443
2444 /*-- Game start info cache: --*/
2445 int gs_gamenum;
2446 char gs_kind[MSG_SIZ];
2447 static char player1Name[128] = "";
2448 static char player2Name[128] = "";
2449 static char cont_seq[] = "\n\\   ";
2450 static int player1Rating = -1;
2451 static int player2Rating = -1;
2452 /*----------------------------*/
2453
2454 ColorClass curColor = ColorNormal;
2455 int suppressKibitz = 0;
2456
2457 // [HGM] seekgraph
2458 Boolean soughtPending = FALSE;
2459 Boolean seekGraphUp;
2460 #define MAX_SEEK_ADS 200
2461 #define SQUARE 0x80
2462 char *seekAdList[MAX_SEEK_ADS];
2463 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2464 float tcList[MAX_SEEK_ADS];
2465 char colorList[MAX_SEEK_ADS];
2466 int nrOfSeekAds = 0;
2467 int minRating = 1010, maxRating = 2800;
2468 int hMargin = 10, vMargin = 20, h, w;
2469 extern int squareSize, lineGap;
2470
2471 void
2472 PlotSeekAd(int i)
2473 {
2474         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2475         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2476         if(r < minRating+100 && r >=0 ) r = minRating+100;
2477         if(r > maxRating) r = maxRating;
2478         if(tc < 1.) tc = 1.;
2479         if(tc > 95.) tc = 95.;
2480         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2481         y = ((double)r - minRating)/(maxRating - minRating)
2482             * (h-vMargin-squareSize/8-1) + vMargin;
2483         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2484         if(strstr(seekAdList[i], " u ")) color = 1;
2485         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2486            !strstr(seekAdList[i], "bullet") &&
2487            !strstr(seekAdList[i], "blitz") &&
2488            !strstr(seekAdList[i], "standard") ) color = 2;
2489         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2490         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2491 }
2492
2493 void
2494 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2495 {
2496         char buf[MSG_SIZ], *ext = "";
2497         VariantClass v = StringToVariant(type);
2498         if(strstr(type, "wild")) {
2499             ext = type + 4; // append wild number
2500             if(v == VariantFischeRandom) type = "chess960"; else
2501             if(v == VariantLoadable) type = "setup"; else
2502             type = VariantName(v);
2503         }
2504         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2505         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2506             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2507             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2508             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2509             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2510             seekNrList[nrOfSeekAds] = nr;
2511             zList[nrOfSeekAds] = 0;
2512             seekAdList[nrOfSeekAds++] = StrSave(buf);
2513             if(plot) PlotSeekAd(nrOfSeekAds-1);
2514         }
2515 }
2516
2517 void
2518 EraseSeekDot(int i)
2519 {
2520     int x = xList[i], y = yList[i], d=squareSize/4, k;
2521     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2522     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2523     // now replot every dot that overlapped
2524     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2525         int xx = xList[k], yy = yList[k];
2526         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2527             DrawSeekDot(xx, yy, colorList[k]);
2528     }
2529 }
2530
2531 void
2532 RemoveSeekAd(int nr)
2533 {
2534         int i;
2535         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2536             EraseSeekDot(i);
2537             if(seekAdList[i]) free(seekAdList[i]);
2538             seekAdList[i] = seekAdList[--nrOfSeekAds];
2539             seekNrList[i] = seekNrList[nrOfSeekAds];
2540             ratingList[i] = ratingList[nrOfSeekAds];
2541             colorList[i]  = colorList[nrOfSeekAds];
2542             tcList[i] = tcList[nrOfSeekAds];
2543             xList[i]  = xList[nrOfSeekAds];
2544             yList[i]  = yList[nrOfSeekAds];
2545             zList[i]  = zList[nrOfSeekAds];
2546             seekAdList[nrOfSeekAds] = NULL;
2547             break;
2548         }
2549 }
2550
2551 Boolean
2552 MatchSoughtLine(char *line)
2553 {
2554     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2555     int nr, base, inc, u=0; char dummy;
2556
2557     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2558        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2559        (u=1) &&
2560        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2561         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2562         // match: compact and save the line
2563         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2564         return TRUE;
2565     }
2566     return FALSE;
2567 }
2568
2569 int
2570 DrawSeekGraph()
2571 {
2572     int i;
2573     if(!seekGraphUp) return FALSE;
2574     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2575     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2576
2577     DrawSeekBackground(0, 0, w, h);
2578     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2579     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2580     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2581         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2582         yy = h-1-yy;
2583         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2584         if(i%500 == 0) {
2585             char buf[MSG_SIZ];
2586             snprintf(buf, MSG_SIZ, "%d", i);
2587             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2588         }
2589     }
2590     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2591     for(i=1; i<100; i+=(i<10?1:5)) {
2592         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2593         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2594         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2595             char buf[MSG_SIZ];
2596             snprintf(buf, MSG_SIZ, "%d", i);
2597             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2598         }
2599     }
2600     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2601     return TRUE;
2602 }
2603
2604 int SeekGraphClick(ClickType click, int x, int y, int moving)
2605 {
2606     static int lastDown = 0, displayed = 0, lastSecond;
2607     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2608         if(click == Release || moving) return FALSE;
2609         nrOfSeekAds = 0;
2610         soughtPending = TRUE;
2611         SendToICS(ics_prefix);
2612         SendToICS("sought\n"); // should this be "sought all"?
2613     } else { // issue challenge based on clicked ad
2614         int dist = 10000; int i, closest = 0, second = 0;
2615         for(i=0; i<nrOfSeekAds; i++) {
2616             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2617             if(d < dist) { dist = d; closest = i; }
2618             second += (d - zList[i] < 120); // count in-range ads
2619             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2620         }
2621         if(dist < 120) {
2622             char buf[MSG_SIZ];
2623             second = (second > 1);
2624             if(displayed != closest || second != lastSecond) {
2625                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2626                 lastSecond = second; displayed = closest;
2627             }
2628             if(click == Press) {
2629                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2630                 lastDown = closest;
2631                 return TRUE;
2632             } // on press 'hit', only show info
2633             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2634             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2635             SendToICS(ics_prefix);
2636             SendToICS(buf);
2637             return TRUE; // let incoming board of started game pop down the graph
2638         } else if(click == Release) { // release 'miss' is ignored
2639             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2640             if(moving == 2) { // right up-click
2641                 nrOfSeekAds = 0; // refresh graph
2642                 soughtPending = TRUE;
2643                 SendToICS(ics_prefix);
2644                 SendToICS("sought\n"); // should this be "sought all"?
2645             }
2646             return TRUE;
2647         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2648         // press miss or release hit 'pop down' seek graph
2649         seekGraphUp = FALSE;
2650         DrawPosition(TRUE, NULL);
2651     }
2652     return TRUE;
2653 }
2654
2655 void
2656 read_from_ics(isr, closure, data, count, error)
2657      InputSourceRef isr;
2658      VOIDSTAR closure;
2659      char *data;
2660      int count;
2661      int error;
2662 {
2663 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2664 #define STARTED_NONE 0
2665 #define STARTED_MOVES 1
2666 #define STARTED_BOARD 2
2667 #define STARTED_OBSERVE 3
2668 #define STARTED_HOLDINGS 4
2669 #define STARTED_CHATTER 5
2670 #define STARTED_COMMENT 6
2671 #define STARTED_MOVES_NOHIDE 7
2672
2673     static int started = STARTED_NONE;
2674     static char parse[20000];
2675     static int parse_pos = 0;
2676     static char buf[BUF_SIZE + 1];
2677     static int firstTime = TRUE, intfSet = FALSE;
2678     static ColorClass prevColor = ColorNormal;
2679     static int savingComment = FALSE;
2680     static int cmatch = 0; // continuation sequence match
2681     char *bp;
2682     char str[MSG_SIZ];
2683     int i, oldi;
2684     int buf_len;
2685     int next_out;
2686     int tkind;
2687     int backup;    /* [DM] For zippy color lines */
2688     char *p;
2689     char talker[MSG_SIZ]; // [HGM] chat
2690     int channel;
2691
2692     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2693
2694     if (appData.debugMode) {
2695       if (!error) {
2696         fprintf(debugFP, "<ICS: ");
2697         show_bytes(debugFP, data, count);
2698         fprintf(debugFP, "\n");
2699       }
2700     }
2701
2702     if (appData.debugMode) { int f = forwardMostMove;
2703         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2704                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2705                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2706     }
2707     if (count > 0) {
2708         /* If last read ended with a partial line that we couldn't parse,
2709            prepend it to the new read and try again. */
2710         if (leftover_len > 0) {
2711             for (i=0; i<leftover_len; i++)
2712               buf[i] = buf[leftover_start + i];
2713         }
2714
2715     /* copy new characters into the buffer */
2716     bp = buf + leftover_len;
2717     buf_len=leftover_len;
2718     for (i=0; i<count; i++)
2719     {
2720         // ignore these
2721         if (data[i] == '\r')
2722             continue;
2723
2724         // join lines split by ICS?
2725         if (!appData.noJoin)
2726         {
2727             /*
2728                 Joining just consists of finding matches against the
2729                 continuation sequence, and discarding that sequence
2730                 if found instead of copying it.  So, until a match
2731                 fails, there's nothing to do since it might be the
2732                 complete sequence, and thus, something we don't want
2733                 copied.
2734             */
2735             if (data[i] == cont_seq[cmatch])
2736             {
2737                 cmatch++;
2738                 if (cmatch == strlen(cont_seq))
2739                 {
2740                     cmatch = 0; // complete match.  just reset the counter
2741
2742                     /*
2743                         it's possible for the ICS to not include the space
2744                         at the end of the last word, making our [correct]
2745                         join operation fuse two separate words.  the server
2746                         does this when the space occurs at the width setting.
2747                     */
2748                     if (!buf_len || buf[buf_len-1] != ' ')
2749                     {
2750                         *bp++ = ' ';
2751                         buf_len++;
2752                     }
2753                 }
2754                 continue;
2755             }
2756             else if (cmatch)
2757             {
2758                 /*
2759                     match failed, so we have to copy what matched before
2760                     falling through and copying this character.  In reality,
2761                     this will only ever be just the newline character, but
2762                     it doesn't hurt to be precise.
2763                 */
2764                 strncpy(bp, cont_seq, cmatch);
2765                 bp += cmatch;
2766                 buf_len += cmatch;
2767                 cmatch = 0;
2768             }
2769         }
2770
2771         // copy this char
2772         *bp++ = data[i];
2773         buf_len++;
2774     }
2775
2776         buf[buf_len] = NULLCHAR;
2777 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2778         next_out = 0;
2779         leftover_start = 0;
2780
2781         i = 0;
2782         while (i < buf_len) {
2783             /* Deal with part of the TELNET option negotiation
2784                protocol.  We refuse to do anything beyond the
2785                defaults, except that we allow the WILL ECHO option,
2786                which ICS uses to turn off password echoing when we are
2787                directly connected to it.  We reject this option
2788                if localLineEditing mode is on (always on in xboard)
2789                and we are talking to port 23, which might be a real
2790                telnet server that will try to keep WILL ECHO on permanently.
2791              */
2792             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2793                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2794                 unsigned char option;
2795                 oldi = i;
2796                 switch ((unsigned char) buf[++i]) {
2797                   case TN_WILL:
2798                     if (appData.debugMode)
2799                       fprintf(debugFP, "\n<WILL ");
2800                     switch (option = (unsigned char) buf[++i]) {
2801                       case TN_ECHO:
2802                         if (appData.debugMode)
2803                           fprintf(debugFP, "ECHO ");
2804                         /* Reply only if this is a change, according
2805                            to the protocol rules. */
2806                         if (remoteEchoOption) break;
2807                         if (appData.localLineEditing &&
2808                             atoi(appData.icsPort) == TN_PORT) {
2809                             TelnetRequest(TN_DONT, TN_ECHO);
2810                         } else {
2811                             EchoOff();
2812                             TelnetRequest(TN_DO, TN_ECHO);
2813                             remoteEchoOption = TRUE;
2814                         }
2815                         break;
2816                       default:
2817                         if (appData.debugMode)
2818                           fprintf(debugFP, "%d ", option);
2819                         /* Whatever this is, we don't want it. */
2820                         TelnetRequest(TN_DONT, option);
2821                         break;
2822                     }
2823                     break;
2824                   case TN_WONT:
2825                     if (appData.debugMode)
2826                       fprintf(debugFP, "\n<WONT ");
2827                     switch (option = (unsigned char) buf[++i]) {
2828                       case TN_ECHO:
2829                         if (appData.debugMode)
2830                           fprintf(debugFP, "ECHO ");
2831                         /* Reply only if this is a change, according
2832                            to the protocol rules. */
2833                         if (!remoteEchoOption) break;
2834                         EchoOn();
2835                         TelnetRequest(TN_DONT, TN_ECHO);
2836                         remoteEchoOption = FALSE;
2837                         break;
2838                       default:
2839                         if (appData.debugMode)
2840                           fprintf(debugFP, "%d ", (unsigned char) option);
2841                         /* Whatever this is, it must already be turned
2842                            off, because we never agree to turn on
2843                            anything non-default, so according to the
2844                            protocol rules, we don't reply. */
2845                         break;
2846                     }
2847                     break;
2848                   case TN_DO:
2849                     if (appData.debugMode)
2850                       fprintf(debugFP, "\n<DO ");
2851                     switch (option = (unsigned char) buf[++i]) {
2852                       default:
2853                         /* Whatever this is, we refuse to do it. */
2854                         if (appData.debugMode)
2855                           fprintf(debugFP, "%d ", option);
2856                         TelnetRequest(TN_WONT, option);
2857                         break;
2858                     }
2859                     break;
2860                   case TN_DONT:
2861                     if (appData.debugMode)
2862                       fprintf(debugFP, "\n<DONT ");
2863                     switch (option = (unsigned char) buf[++i]) {
2864                       default:
2865                         if (appData.debugMode)
2866                           fprintf(debugFP, "%d ", option);
2867                         /* Whatever this is, we are already not doing
2868                            it, because we never agree to do anything
2869                            non-default, so according to the protocol
2870                            rules, we don't reply. */
2871                         break;
2872                     }
2873                     break;
2874                   case TN_IAC:
2875                     if (appData.debugMode)
2876                       fprintf(debugFP, "\n<IAC ");
2877                     /* Doubled IAC; pass it through */
2878                     i--;
2879                     break;
2880                   default:
2881                     if (appData.debugMode)
2882                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2883                     /* Drop all other telnet commands on the floor */
2884                     break;
2885                 }
2886                 if (oldi > next_out)
2887                   SendToPlayer(&buf[next_out], oldi - next_out);
2888                 if (++i > next_out)
2889                   next_out = i;
2890                 continue;
2891             }
2892
2893             /* OK, this at least will *usually* work */
2894             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2895                 loggedOn = TRUE;
2896             }
2897
2898             if (loggedOn && !intfSet) {
2899                 if (ics_type == ICS_ICC) {
2900                   snprintf(str, MSG_SIZ,
2901                           "/set-quietly interface %s\n/set-quietly style 12\n",
2902                           programVersion);
2903                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2904                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2905                 } else if (ics_type == ICS_CHESSNET) {
2906                   snprintf(str, MSG_SIZ, "/style 12\n");
2907                 } else {
2908                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2909                   strcat(str, programVersion);
2910                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2911                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2912                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2913 #ifdef WIN32
2914                   strcat(str, "$iset nohighlight 1\n");
2915 #endif
2916                   strcat(str, "$iset lock 1\n$style 12\n");
2917                 }
2918                 SendToICS(str);
2919                 NotifyFrontendLogin();
2920                 intfSet = TRUE;
2921             }
2922
2923             if (started == STARTED_COMMENT) {
2924                 /* Accumulate characters in comment */
2925                 parse[parse_pos++] = buf[i];
2926                 if (buf[i] == '\n') {
2927                     parse[parse_pos] = NULLCHAR;
2928                     if(chattingPartner>=0) {
2929                         char mess[MSG_SIZ];
2930                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2931                         OutputChatMessage(chattingPartner, mess);
2932                         chattingPartner = -1;
2933                         next_out = i+1; // [HGM] suppress printing in ICS window
2934                     } else
2935                     if(!suppressKibitz) // [HGM] kibitz
2936                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2937                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2938                         int nrDigit = 0, nrAlph = 0, j;
2939                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2940                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2941                         parse[parse_pos] = NULLCHAR;
2942                         // try to be smart: if it does not look like search info, it should go to
2943                         // ICS interaction window after all, not to engine-output window.
2944                         for(j=0; j<parse_pos; j++) { // count letters and digits
2945                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2946                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2947                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2948                         }
2949                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2950                             int depth=0; float score;
2951                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2952                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2953                                 pvInfoList[forwardMostMove-1].depth = depth;
2954                                 pvInfoList[forwardMostMove-1].score = 100*score;
2955                             }
2956                             OutputKibitz(suppressKibitz, parse);
2957                         } else {
2958                             char tmp[MSG_SIZ];
2959                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2960                             SendToPlayer(tmp, strlen(tmp));
2961                         }
2962                         next_out = i+1; // [HGM] suppress printing in ICS window
2963                     }
2964                     started = STARTED_NONE;
2965                 } else {
2966                     /* Don't match patterns against characters in comment */
2967                     i++;
2968                     continue;
2969                 }
2970             }
2971             if (started == STARTED_CHATTER) {
2972                 if (buf[i] != '\n') {
2973                     /* Don't match patterns against characters in chatter */
2974                     i++;
2975                     continue;
2976                 }
2977                 started = STARTED_NONE;
2978                 if(suppressKibitz) next_out = i+1;
2979             }
2980
2981             /* Kludge to deal with rcmd protocol */
2982             if (firstTime && looking_at(buf, &i, "\001*")) {
2983                 DisplayFatalError(&buf[1], 0, 1);
2984                 continue;
2985             } else {
2986                 firstTime = FALSE;
2987             }
2988
2989             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2990                 ics_type = ICS_ICC;
2991                 ics_prefix = "/";
2992                 if (appData.debugMode)
2993                   fprintf(debugFP, "ics_type %d\n", ics_type);
2994                 continue;
2995             }
2996             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2997                 ics_type = ICS_FICS;
2998                 ics_prefix = "$";
2999                 if (appData.debugMode)
3000                   fprintf(debugFP, "ics_type %d\n", ics_type);
3001                 continue;
3002             }
3003             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3004                 ics_type = ICS_CHESSNET;
3005                 ics_prefix = "/";
3006                 if (appData.debugMode)
3007                   fprintf(debugFP, "ics_type %d\n", ics_type);
3008                 continue;
3009             }
3010
3011             if (!loggedOn &&
3012                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3013                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3014                  looking_at(buf, &i, "will be \"*\""))) {
3015               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3016               continue;
3017             }
3018
3019             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3020               char buf[MSG_SIZ];
3021               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3022               DisplayIcsInteractionTitle(buf);
3023               have_set_title = TRUE;
3024             }
3025
3026             /* skip finger notes */
3027             if (started == STARTED_NONE &&
3028                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3029                  (buf[i] == '1' && buf[i+1] == '0')) &&
3030                 buf[i+2] == ':' && buf[i+3] == ' ') {
3031               started = STARTED_CHATTER;
3032               i += 3;
3033               continue;
3034             }
3035
3036             oldi = i;
3037             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3038             if(appData.seekGraph) {
3039                 if(soughtPending && MatchSoughtLine(buf+i)) {
3040                     i = strstr(buf+i, "rated") - buf;
3041                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3042                     next_out = leftover_start = i;
3043                     started = STARTED_CHATTER;
3044                     suppressKibitz = TRUE;
3045                     continue;
3046                 }
3047                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3048                         && looking_at(buf, &i, "* ads displayed")) {
3049                     soughtPending = FALSE;
3050                     seekGraphUp = TRUE;
3051                     DrawSeekGraph();
3052                     continue;
3053                 }
3054                 if(appData.autoRefresh) {
3055                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3056                         int s = (ics_type == ICS_ICC); // ICC format differs
3057                         if(seekGraphUp)
3058                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3059                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3060                         looking_at(buf, &i, "*% "); // eat prompt
3061                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3062                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3063                         next_out = i; // suppress
3064                         continue;
3065                     }
3066                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3067                         char *p = star_match[0];
3068                         while(*p) {
3069                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3070                             while(*p && *p++ != ' '); // next
3071                         }
3072                         looking_at(buf, &i, "*% "); // eat prompt
3073                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3074                         next_out = i;
3075                         continue;
3076                     }
3077                 }
3078             }
3079
3080             /* skip formula vars */
3081             if (started == STARTED_NONE &&
3082                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3083               started = STARTED_CHATTER;
3084               i += 3;
3085               continue;
3086             }
3087
3088             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3089             if (appData.autoKibitz && started == STARTED_NONE &&
3090                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3091                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3092                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3093                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3094                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3095                         suppressKibitz = TRUE;
3096                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3097                         next_out = i;
3098                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3099                                 && (gameMode == IcsPlayingWhite)) ||
3100                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3101                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3102                             started = STARTED_CHATTER; // own kibitz we simply discard
3103                         else {
3104                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3105                             parse_pos = 0; parse[0] = NULLCHAR;
3106                             savingComment = TRUE;
3107                             suppressKibitz = gameMode != IcsObserving ? 2 :
3108                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3109                         }
3110                         continue;
3111                 } else
3112                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3113                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3114                          && atoi(star_match[0])) {
3115                     // suppress the acknowledgements of our own autoKibitz
3116                     char *p;
3117                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3118                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3119                     SendToPlayer(star_match[0], strlen(star_match[0]));
3120                     if(looking_at(buf, &i, "*% ")) // eat prompt
3121                         suppressKibitz = FALSE;
3122                     next_out = i;
3123                     continue;
3124                 }
3125             } // [HGM] kibitz: end of patch
3126
3127             // [HGM] chat: intercept tells by users for which we have an open chat window
3128             channel = -1;
3129             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3130                                            looking_at(buf, &i, "* whispers:") ||
3131                                            looking_at(buf, &i, "* kibitzes:") ||
3132                                            looking_at(buf, &i, "* shouts:") ||
3133                                            looking_at(buf, &i, "* c-shouts:") ||
3134                                            looking_at(buf, &i, "--> * ") ||
3135                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3136                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3137                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3138                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3139                 int p;
3140                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3141                 chattingPartner = -1;
3142
3143                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3144                 for(p=0; p<MAX_CHAT; p++) {
3145                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3146                     talker[0] = '['; strcat(talker, "] ");
3147                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3148                     chattingPartner = p; break;
3149                     }
3150                 } else
3151                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3152                 for(p=0; p<MAX_CHAT; p++) {
3153                     if(!strcmp("kibitzes", chatPartner[p])) {
3154                         talker[0] = '['; strcat(talker, "] ");
3155                         chattingPartner = p; break;
3156                     }
3157                 } else
3158                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3159                 for(p=0; p<MAX_CHAT; p++) {
3160                     if(!strcmp("whispers", chatPartner[p])) {
3161                         talker[0] = '['; strcat(talker, "] ");
3162                         chattingPartner = p; break;
3163                     }
3164                 } else
3165                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3166                   if(buf[i-8] == '-' && buf[i-3] == 't')
3167                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3168                     if(!strcmp("c-shouts", chatPartner[p])) {
3169                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3170                         chattingPartner = p; break;
3171                     }
3172                   }
3173                   if(chattingPartner < 0)
3174                   for(p=0; p<MAX_CHAT; p++) {
3175                     if(!strcmp("shouts", chatPartner[p])) {
3176                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3177                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3178                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3179                         chattingPartner = p; break;
3180                     }
3181                   }
3182                 }
3183                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3184                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3185                     talker[0] = 0; Colorize(ColorTell, FALSE);
3186                     chattingPartner = p; break;
3187                 }
3188                 if(chattingPartner<0) i = oldi; else {
3189                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3190                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3191                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3192                     started = STARTED_COMMENT;
3193                     parse_pos = 0; parse[0] = NULLCHAR;
3194                     savingComment = 3 + chattingPartner; // counts as TRUE
3195                     suppressKibitz = TRUE;
3196                     continue;
3197                 }
3198             } // [HGM] chat: end of patch
3199
3200           backup = i;
3201             if (appData.zippyTalk || appData.zippyPlay) {
3202                 /* [DM] Backup address for color zippy lines */
3203 #if ZIPPY
3204                if (loggedOn == TRUE)
3205                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3206                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3207 #endif
3208             } // [DM] 'else { ' deleted
3209                 if (
3210                     /* Regular tells and says */
3211                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3212                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3213                     looking_at(buf, &i, "* says: ") ||
3214                     /* Don't color "message" or "messages" output */
3215                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3216                     looking_at(buf, &i, "*. * at *:*: ") ||
3217                     looking_at(buf, &i, "--* (*:*): ") ||
3218                     /* Message notifications (same color as tells) */
3219                     looking_at(buf, &i, "* has left a message ") ||
3220                     looking_at(buf, &i, "* just sent you a message:\n") ||
3221                     /* Whispers and kibitzes */
3222                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3223                     looking_at(buf, &i, "* kibitzes: ") ||
3224                     /* Channel tells */
3225                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3226
3227                   if (tkind == 1 && strchr(star_match[0], ':')) {
3228                       /* Avoid "tells you:" spoofs in channels */
3229                      tkind = 3;
3230                   }
3231                   if (star_match[0][0] == NULLCHAR ||
3232                       strchr(star_match[0], ' ') ||
3233                       (tkind == 3 && strchr(star_match[1], ' '))) {
3234                     /* Reject bogus matches */
3235                     i = oldi;
3236                   } else {
3237                     if (appData.colorize) {
3238                       if (oldi > next_out) {
3239                         SendToPlayer(&buf[next_out], oldi - next_out);
3240                         next_out = oldi;
3241                       }
3242                       switch (tkind) {
3243                       case 1:
3244                         Colorize(ColorTell, FALSE);
3245                         curColor = ColorTell;
3246                         break;
3247                       case 2:
3248                         Colorize(ColorKibitz, FALSE);
3249                         curColor = ColorKibitz;
3250                         break;
3251                       case 3:
3252                         p = strrchr(star_match[1], '(');
3253                         if (p == NULL) {
3254                           p = star_match[1];
3255                         } else {
3256                           p++;
3257                         }
3258                         if (atoi(p) == 1) {
3259                           Colorize(ColorChannel1, FALSE);
3260                           curColor = ColorChannel1;
3261                         } else {
3262                           Colorize(ColorChannel, FALSE);
3263                           curColor = ColorChannel;
3264                         }
3265                         break;
3266                       case 5:
3267                         curColor = ColorNormal;
3268                         break;
3269                       }
3270                     }
3271                     if (started == STARTED_NONE && appData.autoComment &&
3272                         (gameMode == IcsObserving ||
3273                          gameMode == IcsPlayingWhite ||
3274                          gameMode == IcsPlayingBlack)) {
3275                       parse_pos = i - oldi;
3276                       memcpy(parse, &buf[oldi], parse_pos);
3277                       parse[parse_pos] = NULLCHAR;
3278                       started = STARTED_COMMENT;
3279                       savingComment = TRUE;
3280                     } else {
3281                       started = STARTED_CHATTER;
3282                       savingComment = FALSE;
3283                     }
3284                     loggedOn = TRUE;
3285                     continue;
3286                   }
3287                 }
3288
3289                 if (looking_at(buf, &i, "* s-shouts: ") ||
3290                     looking_at(buf, &i, "* c-shouts: ")) {
3291                     if (appData.colorize) {
3292                         if (oldi > next_out) {
3293                             SendToPlayer(&buf[next_out], oldi - next_out);
3294                             next_out = oldi;
3295                         }
3296                         Colorize(ColorSShout, FALSE);
3297                         curColor = ColorSShout;
3298                     }
3299                     loggedOn = TRUE;
3300                     started = STARTED_CHATTER;
3301                     continue;
3302                 }
3303
3304                 if (looking_at(buf, &i, "--->")) {
3305                     loggedOn = TRUE;
3306                     continue;
3307                 }
3308
3309                 if (looking_at(buf, &i, "* shouts: ") ||
3310                     looking_at(buf, &i, "--> ")) {
3311                     if (appData.colorize) {
3312                         if (oldi > next_out) {
3313                             SendToPlayer(&buf[next_out], oldi - next_out);
3314                             next_out = oldi;
3315                         }
3316                         Colorize(ColorShout, FALSE);
3317                         curColor = ColorShout;
3318                     }
3319                     loggedOn = TRUE;
3320                     started = STARTED_CHATTER;
3321                     continue;
3322                 }
3323
3324                 if (looking_at( buf, &i, "Challenge:")) {
3325                     if (appData.colorize) {
3326                         if (oldi > next_out) {
3327                             SendToPlayer(&buf[next_out], oldi - next_out);
3328                             next_out = oldi;
3329                         }
3330                         Colorize(ColorChallenge, FALSE);
3331                         curColor = ColorChallenge;
3332                     }
3333                     loggedOn = TRUE;
3334                     continue;
3335                 }
3336
3337                 if (looking_at(buf, &i, "* offers you") ||
3338                     looking_at(buf, &i, "* offers to be") ||
3339                     looking_at(buf, &i, "* would like to") ||
3340                     looking_at(buf, &i, "* requests to") ||
3341                     looking_at(buf, &i, "Your opponent offers") ||
3342                     looking_at(buf, &i, "Your opponent requests")) {
3343
3344                     if (appData.colorize) {
3345                         if (oldi > next_out) {
3346                             SendToPlayer(&buf[next_out], oldi - next_out);
3347                             next_out = oldi;
3348                         }
3349                         Colorize(ColorRequest, FALSE);
3350                         curColor = ColorRequest;
3351                     }
3352                     continue;
3353                 }
3354
3355                 if (looking_at(buf, &i, "* (*) seeking")) {
3356                     if (appData.colorize) {
3357                         if (oldi > next_out) {
3358                             SendToPlayer(&buf[next_out], oldi - next_out);
3359                             next_out = oldi;
3360                         }
3361                         Colorize(ColorSeek, FALSE);
3362                         curColor = ColorSeek;
3363                     }
3364                     continue;
3365             }
3366
3367           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3368
3369             if (looking_at(buf, &i, "\\   ")) {
3370                 if (prevColor != ColorNormal) {
3371                     if (oldi > next_out) {
3372                         SendToPlayer(&buf[next_out], oldi - next_out);
3373                         next_out = oldi;
3374                     }
3375                     Colorize(prevColor, TRUE);
3376                     curColor = prevColor;
3377                 }
3378                 if (savingComment) {
3379                     parse_pos = i - oldi;
3380                     memcpy(parse, &buf[oldi], parse_pos);
3381                     parse[parse_pos] = NULLCHAR;
3382                     started = STARTED_COMMENT;
3383                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3384                         chattingPartner = savingComment - 3; // kludge to remember the box
3385                 } else {
3386                     started = STARTED_CHATTER;
3387                 }
3388                 continue;
3389             }
3390
3391             if (looking_at(buf, &i, "Black Strength :") ||
3392                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3393                 looking_at(buf, &i, "<10>") ||
3394                 looking_at(buf, &i, "#@#")) {
3395                 /* Wrong board style */
3396                 loggedOn = TRUE;
3397                 SendToICS(ics_prefix);
3398                 SendToICS("set style 12\n");
3399                 SendToICS(ics_prefix);
3400                 SendToICS("refresh\n");
3401                 continue;
3402             }
3403
3404             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3405                 ICSInitScript();
3406                 have_sent_ICS_logon = 1;
3407                 continue;
3408             }
3409
3410             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3411                 (looking_at(buf, &i, "\n<12> ") ||
3412                  looking_at(buf, &i, "<12> "))) {
3413                 loggedOn = TRUE;
3414                 if (oldi > next_out) {
3415                     SendToPlayer(&buf[next_out], oldi - next_out);
3416                 }
3417                 next_out = i;
3418                 started = STARTED_BOARD;
3419                 parse_pos = 0;
3420                 continue;
3421             }
3422
3423             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3424                 looking_at(buf, &i, "<b1> ")) {
3425                 if (oldi > next_out) {
3426                     SendToPlayer(&buf[next_out], oldi - next_out);
3427                 }
3428                 next_out = i;
3429                 started = STARTED_HOLDINGS;
3430                 parse_pos = 0;
3431                 continue;
3432             }
3433
3434             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3435                 loggedOn = TRUE;
3436                 /* Header for a move list -- first line */
3437
3438                 switch (ics_getting_history) {
3439                   case H_FALSE:
3440                     switch (gameMode) {
3441                       case IcsIdle:
3442                       case BeginningOfGame:
3443                         /* User typed "moves" or "oldmoves" while we
3444                            were idle.  Pretend we asked for these
3445                            moves and soak them up so user can step
3446                            through them and/or save them.
3447                            */
3448                         Reset(FALSE, TRUE);
3449                         gameMode = IcsObserving;
3450                         ModeHighlight();
3451                         ics_gamenum = -1;
3452                         ics_getting_history = H_GOT_UNREQ_HEADER;
3453                         break;
3454                       case EditGame: /*?*/
3455                       case EditPosition: /*?*/
3456                         /* Should above feature work in these modes too? */
3457                         /* For now it doesn't */
3458                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3459                         break;
3460                       default:
3461                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3462                         break;
3463                     }
3464                     break;
3465                   case H_REQUESTED:
3466                     /* Is this the right one? */
3467                     if (gameInfo.white && gameInfo.black &&
3468                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3469                         strcmp(gameInfo.black, star_match[2]) == 0) {
3470                         /* All is well */
3471                         ics_getting_history = H_GOT_REQ_HEADER;
3472                     }
3473                     break;
3474                   case H_GOT_REQ_HEADER:
3475                   case H_GOT_UNREQ_HEADER:
3476                   case H_GOT_UNWANTED_HEADER:
3477                   case H_GETTING_MOVES:
3478                     /* Should not happen */
3479                     DisplayError(_("Error gathering move list: two headers"), 0);
3480                     ics_getting_history = H_FALSE;
3481                     break;
3482                 }
3483
3484                 /* Save player ratings into gameInfo if needed */
3485                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3486                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3487                     (gameInfo.whiteRating == -1 ||
3488                      gameInfo.blackRating == -1)) {
3489
3490                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3491                     gameInfo.blackRating = string_to_rating(star_match[3]);
3492                     if (appData.debugMode)
3493                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3494                               gameInfo.whiteRating, gameInfo.blackRating);
3495                 }
3496                 continue;
3497             }
3498
3499             if (looking_at(buf, &i,
3500               "* * match, initial time: * minute*, increment: * second")) {
3501                 /* Header for a move list -- second line */
3502                 /* Initial board will follow if this is a wild game */
3503                 if (gameInfo.event != NULL) free(gameInfo.event);
3504                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3505                 gameInfo.event = StrSave(str);
3506                 /* [HGM] we switched variant. Translate boards if needed. */
3507                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3508                 continue;
3509             }
3510
3511             if (looking_at(buf, &i, "Move  ")) {
3512                 /* Beginning of a move list */
3513                 switch (ics_getting_history) {
3514                   case H_FALSE:
3515                     /* Normally should not happen */
3516                     /* Maybe user hit reset while we were parsing */
3517                     break;
3518                   case H_REQUESTED:
3519                     /* Happens if we are ignoring a move list that is not
3520                      * the one we just requested.  Common if the user
3521                      * tries to observe two games without turning off
3522                      * getMoveList */
3523                     break;
3524                   case H_GETTING_MOVES:
3525                     /* Should not happen */
3526                     DisplayError(_("Error gathering move list: nested"), 0);
3527                     ics_getting_history = H_FALSE;
3528                     break;
3529                   case H_GOT_REQ_HEADER:
3530                     ics_getting_history = H_GETTING_MOVES;
3531                     started = STARTED_MOVES;
3532                     parse_pos = 0;
3533                     if (oldi > next_out) {
3534                         SendToPlayer(&buf[next_out], oldi - next_out);
3535                     }
3536                     break;
3537                   case H_GOT_UNREQ_HEADER:
3538                     ics_getting_history = H_GETTING_MOVES;
3539                     started = STARTED_MOVES_NOHIDE;
3540                     parse_pos = 0;
3541                     break;
3542                   case H_GOT_UNWANTED_HEADER:
3543                     ics_getting_history = H_FALSE;
3544                     break;
3545                 }
3546                 continue;
3547             }
3548
3549             if (looking_at(buf, &i, "% ") ||
3550                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3551                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3552                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3553                     soughtPending = FALSE;
3554                     seekGraphUp = TRUE;
3555                     DrawSeekGraph();
3556                 }
3557                 if(suppressKibitz) next_out = i;
3558                 savingComment = FALSE;
3559                 suppressKibitz = 0;
3560                 switch (started) {
3561                   case STARTED_MOVES:
3562                   case STARTED_MOVES_NOHIDE:
3563                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3564                     parse[parse_pos + i - oldi] = NULLCHAR;
3565                     ParseGameHistory(parse);
3566 #if ZIPPY
3567                     if (appData.zippyPlay && first.initDone) {
3568                         FeedMovesToProgram(&first, forwardMostMove);
3569                         if (gameMode == IcsPlayingWhite) {
3570                             if (WhiteOnMove(forwardMostMove)) {
3571                                 if (first.sendTime) {
3572                                   if (first.useColors) {
3573                                     SendToProgram("black\n", &first);
3574                                   }
3575                                   SendTimeRemaining(&first, TRUE);
3576                                 }
3577                                 if (first.useColors) {
3578                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3579                                 }
3580                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3581                                 first.maybeThinking = TRUE;
3582                             } else {
3583                                 if (first.usePlayother) {
3584                                   if (first.sendTime) {
3585                                     SendTimeRemaining(&first, TRUE);
3586                                   }
3587                                   SendToProgram("playother\n", &first);
3588                                   firstMove = FALSE;
3589                                 } else {
3590                                   firstMove = TRUE;
3591                                 }
3592                             }
3593                         } else if (gameMode == IcsPlayingBlack) {
3594                             if (!WhiteOnMove(forwardMostMove)) {
3595                                 if (first.sendTime) {
3596                                   if (first.useColors) {
3597                                     SendToProgram("white\n", &first);
3598                                   }
3599                                   SendTimeRemaining(&first, FALSE);
3600                                 }
3601                                 if (first.useColors) {
3602                                   SendToProgram("black\n", &first);
3603                                 }
3604                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3605                                 first.maybeThinking = TRUE;
3606                             } else {
3607                                 if (first.usePlayother) {
3608                                   if (first.sendTime) {
3609                                     SendTimeRemaining(&first, FALSE);
3610                                   }
3611                                   SendToProgram("playother\n", &first);
3612                                   firstMove = FALSE;
3613                                 } else {
3614                                   firstMove = TRUE;
3615                                 }
3616                             }
3617                         }
3618                     }
3619 #endif
3620                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3621                         /* Moves came from oldmoves or moves command
3622                            while we weren't doing anything else.
3623                            */
3624                         currentMove = forwardMostMove;
3625                         ClearHighlights();/*!!could figure this out*/
3626                         flipView = appData.flipView;
3627                         DrawPosition(TRUE, boards[currentMove]);
3628                         DisplayBothClocks();
3629                         snprintf(str, MSG_SIZ, "%s vs. %s",
3630                                 gameInfo.white, gameInfo.black);
3631                         DisplayTitle(str);
3632                         gameMode = IcsIdle;
3633                     } else {
3634                         /* Moves were history of an active game */
3635                         if (gameInfo.resultDetails != NULL) {
3636                             free(gameInfo.resultDetails);
3637                             gameInfo.resultDetails = NULL;
3638                         }
3639                     }
3640                     HistorySet(parseList, backwardMostMove,
3641                                forwardMostMove, currentMove-1);
3642                     DisplayMove(currentMove - 1);
3643                     if (started == STARTED_MOVES) next_out = i;
3644                     started = STARTED_NONE;
3645                     ics_getting_history = H_FALSE;
3646                     break;
3647
3648                   case STARTED_OBSERVE:
3649                     started = STARTED_NONE;
3650                     SendToICS(ics_prefix);
3651                     SendToICS("refresh\n");
3652                     break;
3653
3654                   default:
3655                     break;
3656                 }
3657                 if(bookHit) { // [HGM] book: simulate book reply
3658                     static char bookMove[MSG_SIZ]; // a bit generous?
3659
3660                     programStats.nodes = programStats.depth = programStats.time =
3661                     programStats.score = programStats.got_only_move = 0;
3662                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3663
3664                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3665                     strcat(bookMove, bookHit);
3666                     HandleMachineMove(bookMove, &first);
3667                 }
3668                 continue;
3669             }
3670
3671             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3672                  started == STARTED_HOLDINGS ||
3673                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3674                 /* Accumulate characters in move list or board */
3675                 parse[parse_pos++] = buf[i];
3676             }
3677
3678             /* Start of game messages.  Mostly we detect start of game
3679                when the first board image arrives.  On some versions
3680                of the ICS, though, we need to do a "refresh" after starting
3681                to observe in order to get the current board right away. */
3682             if (looking_at(buf, &i, "Adding game * to observation list")) {
3683                 started = STARTED_OBSERVE;
3684                 continue;
3685             }
3686
3687             /* Handle auto-observe */
3688             if (appData.autoObserve &&
3689                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3690                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3691                 char *player;
3692                 /* Choose the player that was highlighted, if any. */
3693                 if (star_match[0][0] == '\033' ||
3694                     star_match[1][0] != '\033') {
3695                     player = star_match[0];
3696                 } else {
3697                     player = star_match[2];
3698                 }
3699                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3700                         ics_prefix, StripHighlightAndTitle(player));
3701                 SendToICS(str);
3702
3703                 /* Save ratings from notify string */
3704                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3705                 player1Rating = string_to_rating(star_match[1]);
3706                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3707                 player2Rating = string_to_rating(star_match[3]);
3708
3709                 if (appData.debugMode)
3710                   fprintf(debugFP,
3711                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3712                           player1Name, player1Rating,
3713                           player2Name, player2Rating);
3714
3715                 continue;
3716             }
3717
3718             /* Deal with automatic examine mode after a game,
3719                and with IcsObserving -> IcsExamining transition */
3720             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3721                 looking_at(buf, &i, "has made you an examiner of game *")) {
3722
3723                 int gamenum = atoi(star_match[0]);
3724                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3725                     gamenum == ics_gamenum) {
3726                     /* We were already playing or observing this game;
3727                        no need to refetch history */
3728                     gameMode = IcsExamining;
3729                     if (pausing) {
3730                         pauseExamForwardMostMove = forwardMostMove;
3731                     } else if (currentMove < forwardMostMove) {
3732                         ForwardInner(forwardMostMove);
3733                     }
3734                 } else {
3735                     /* I don't think this case really can happen */
3736                     SendToICS(ics_prefix);
3737                     SendToICS("refresh\n");
3738                 }
3739                 continue;
3740             }
3741
3742             /* Error messages */
3743 //          if (ics_user_moved) {
3744             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3745                 if (looking_at(buf, &i, "Illegal move") ||
3746                     looking_at(buf, &i, "Not a legal move") ||
3747                     looking_at(buf, &i, "Your king is in check") ||
3748                     looking_at(buf, &i, "It isn't your turn") ||
3749                     looking_at(buf, &i, "It is not your move")) {
3750                     /* Illegal move */
3751                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3752                         currentMove = forwardMostMove-1;
3753                         DisplayMove(currentMove - 1); /* before DMError */
3754                         DrawPosition(FALSE, boards[currentMove]);
3755                         SwitchClocks(forwardMostMove-1); // [HGM] race
3756                         DisplayBothClocks();
3757                     }
3758                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3759                     ics_user_moved = 0;
3760                     continue;
3761                 }
3762             }
3763
3764             if (looking_at(buf, &i, "still have time") ||
3765                 looking_at(buf, &i, "not out of time") ||
3766                 looking_at(buf, &i, "either player is out of time") ||
3767                 looking_at(buf, &i, "has timeseal; checking")) {
3768                 /* We must have called his flag a little too soon */
3769                 whiteFlag = blackFlag = FALSE;
3770                 continue;
3771             }
3772
3773             if (looking_at(buf, &i, "added * seconds to") ||
3774                 looking_at(buf, &i, "seconds were added to")) {
3775                 /* Update the clocks */
3776                 SendToICS(ics_prefix);
3777                 SendToICS("refresh\n");
3778                 continue;
3779             }
3780
3781             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3782                 ics_clock_paused = TRUE;
3783                 StopClocks();
3784                 continue;
3785             }
3786
3787             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3788                 ics_clock_paused = FALSE;
3789                 StartClocks();
3790                 continue;
3791             }
3792
3793             /* Grab player ratings from the Creating: message.
3794                Note we have to check for the special case when
3795                the ICS inserts things like [white] or [black]. */
3796             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3797                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3798                 /* star_matches:
3799                    0    player 1 name (not necessarily white)
3800                    1    player 1 rating
3801                    2    empty, white, or black (IGNORED)
3802                    3    player 2 name (not necessarily black)
3803                    4    player 2 rating
3804
3805                    The names/ratings are sorted out when the game
3806                    actually starts (below).
3807                 */
3808                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3809                 player1Rating = string_to_rating(star_match[1]);
3810                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3811                 player2Rating = string_to_rating(star_match[4]);
3812
3813                 if (appData.debugMode)
3814                   fprintf(debugFP,
3815                           "Ratings from 'Creating:' %s %d, %s %d\n",
3816                           player1Name, player1Rating,
3817                           player2Name, player2Rating);
3818
3819                 continue;
3820             }
3821
3822             /* Improved generic start/end-of-game messages */
3823             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3824                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3825                 /* If tkind == 0: */
3826                 /* star_match[0] is the game number */
3827                 /*           [1] is the white player's name */
3828                 /*           [2] is the black player's name */
3829                 /* For end-of-game: */
3830                 /*           [3] is the reason for the game end */
3831                 /*           [4] is a PGN end game-token, preceded by " " */
3832                 /* For start-of-game: */
3833                 /*           [3] begins with "Creating" or "Continuing" */
3834                 /*           [4] is " *" or empty (don't care). */
3835                 int gamenum = atoi(star_match[0]);
3836                 char *whitename, *blackname, *why, *endtoken;
3837                 ChessMove endtype = EndOfFile;
3838
3839                 if (tkind == 0) {
3840                   whitename = star_match[1];
3841                   blackname = star_match[2];
3842                   why = star_match[3];
3843                   endtoken = star_match[4];
3844                 } else {
3845                   whitename = star_match[1];
3846                   blackname = star_match[3];
3847                   why = star_match[5];
3848                   endtoken = star_match[6];
3849                 }
3850
3851                 /* Game start messages */
3852                 if (strncmp(why, "Creating ", 9) == 0 ||
3853                     strncmp(why, "Continuing ", 11) == 0) {
3854                     gs_gamenum = gamenum;
3855                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3856                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3857 #if ZIPPY
3858                     if (appData.zippyPlay) {
3859                         ZippyGameStart(whitename, blackname);
3860                     }
3861 #endif /*ZIPPY*/
3862                     partnerBoardValid = FALSE; // [HGM] bughouse
3863                     continue;
3864                 }
3865
3866                 /* Game end messages */
3867                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3868                     ics_gamenum != gamenum) {
3869                     continue;
3870                 }
3871                 while (endtoken[0] == ' ') endtoken++;
3872                 switch (endtoken[0]) {
3873                   case '*':
3874                   default:
3875                     endtype = GameUnfinished;
3876                     break;
3877                   case '0':
3878                     endtype = BlackWins;
3879                     break;
3880                   case '1':
3881                     if (endtoken[1] == '/')
3882                       endtype = GameIsDrawn;
3883                     else
3884                       endtype = WhiteWins;
3885                     break;
3886                 }
3887                 GameEnds(endtype, why, GE_ICS);
3888 #if ZIPPY
3889                 if (appData.zippyPlay && first.initDone) {
3890                     ZippyGameEnd(endtype, why);
3891                     if (first.pr == NULL) {
3892                       /* Start the next process early so that we'll
3893                          be ready for the next challenge */
3894                       StartChessProgram(&first);
3895                     }
3896                     /* Send "new" early, in case this command takes
3897                        a long time to finish, so that we'll be ready
3898                        for the next challenge. */
3899                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3900                     Reset(TRUE, TRUE);
3901                 }
3902 #endif /*ZIPPY*/
3903                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3904                 continue;
3905             }
3906
3907             if (looking_at(buf, &i, "Removing game * from observation") ||
3908                 looking_at(buf, &i, "no longer observing game *") ||
3909                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3910                 if (gameMode == IcsObserving &&
3911                     atoi(star_match[0]) == ics_gamenum)
3912                   {
3913                       /* icsEngineAnalyze */
3914                       if (appData.icsEngineAnalyze) {
3915                             ExitAnalyzeMode();
3916                             ModeHighlight();
3917                       }
3918                       StopClocks();
3919                       gameMode = IcsIdle;
3920                       ics_gamenum = -1;
3921                       ics_user_moved = FALSE;
3922                   }
3923                 continue;
3924             }
3925
3926             if (looking_at(buf, &i, "no longer examining game *")) {
3927                 if (gameMode == IcsExamining &&
3928                     atoi(star_match[0]) == ics_gamenum)
3929                   {
3930                       gameMode = IcsIdle;
3931                       ics_gamenum = -1;
3932                       ics_user_moved = FALSE;
3933                   }
3934                 continue;
3935             }
3936
3937             /* Advance leftover_start past any newlines we find,
3938                so only partial lines can get reparsed */
3939             if (looking_at(buf, &i, "\n")) {
3940                 prevColor = curColor;
3941                 if (curColor != ColorNormal) {
3942                     if (oldi > next_out) {
3943                         SendToPlayer(&buf[next_out], oldi - next_out);
3944                         next_out = oldi;
3945                     }
3946                     Colorize(ColorNormal, FALSE);
3947                     curColor = ColorNormal;
3948                 }
3949                 if (started == STARTED_BOARD) {
3950                     started = STARTED_NONE;
3951                     parse[parse_pos] = NULLCHAR;
3952                     ParseBoard12(parse);
3953                     ics_user_moved = 0;
3954
3955                     /* Send premove here */
3956                     if (appData.premove) {
3957                       char str[MSG_SIZ];
3958                       if (currentMove == 0 &&
3959                           gameMode == IcsPlayingWhite &&
3960                           appData.premoveWhite) {
3961                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3962                         if (appData.debugMode)
3963                           fprintf(debugFP, "Sending premove:\n");
3964                         SendToICS(str);
3965                       } else if (currentMove == 1 &&
3966                                  gameMode == IcsPlayingBlack &&
3967                                  appData.premoveBlack) {
3968                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3969                         if (appData.debugMode)
3970                           fprintf(debugFP, "Sending premove:\n");
3971                         SendToICS(str);
3972                       } else if (gotPremove) {
3973                         gotPremove = 0;
3974                         ClearPremoveHighlights();
3975                         if (appData.debugMode)
3976                           fprintf(debugFP, "Sending premove:\n");
3977                           UserMoveEvent(premoveFromX, premoveFromY,
3978                                         premoveToX, premoveToY,
3979                                         premovePromoChar);
3980                       }
3981                     }
3982
3983                     /* Usually suppress following prompt */
3984                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3985                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3986                         if (looking_at(buf, &i, "*% ")) {
3987                             savingComment = FALSE;
3988                             suppressKibitz = 0;
3989                         }
3990                     }
3991                     next_out = i;
3992                 } else if (started == STARTED_HOLDINGS) {
3993                     int gamenum;
3994                     char new_piece[MSG_SIZ];
3995                     started = STARTED_NONE;
3996                     parse[parse_pos] = NULLCHAR;
3997                     if (appData.debugMode)
3998                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3999                                                         parse, currentMove);
4000                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4001                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4002                         if (gameInfo.variant == VariantNormal) {
4003                           /* [HGM] We seem to switch variant during a game!
4004                            * Presumably no holdings were displayed, so we have
4005                            * to move the position two files to the right to
4006                            * create room for them!
4007                            */
4008                           VariantClass newVariant;
4009                           switch(gameInfo.boardWidth) { // base guess on board width
4010                                 case 9:  newVariant = VariantShogi; break;
4011                                 case 10: newVariant = VariantGreat; break;
4012                                 default: newVariant = VariantCrazyhouse; break;
4013                           }
4014                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4015                           /* Get a move list just to see the header, which
4016                              will tell us whether this is really bug or zh */
4017                           if (ics_getting_history == H_FALSE) {
4018                             ics_getting_history = H_REQUESTED;
4019                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4020                             SendToICS(str);
4021                           }
4022                         }
4023                         new_piece[0] = NULLCHAR;
4024                         sscanf(parse, "game %d white [%s black [%s <- %s",
4025                                &gamenum, white_holding, black_holding,
4026                                new_piece);
4027                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4028                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4029                         /* [HGM] copy holdings to board holdings area */
4030                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4031                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4032                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4033 #if ZIPPY
4034                         if (appData.zippyPlay && first.initDone) {
4035                             ZippyHoldings(white_holding, black_holding,
4036                                           new_piece);
4037                         }
4038 #endif /*ZIPPY*/
4039                         if (tinyLayout || smallLayout) {
4040                             char wh[16], bh[16];
4041                             PackHolding(wh, white_holding);
4042                             PackHolding(bh, black_holding);
4043                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4044                                     gameInfo.white, gameInfo.black);
4045                         } else {
4046                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4047                                     gameInfo.white, white_holding,
4048                                     gameInfo.black, black_holding);
4049                         }
4050                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4051                         DrawPosition(FALSE, boards[currentMove]);
4052                         DisplayTitle(str);
4053                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4054                         sscanf(parse, "game %d white [%s black [%s <- %s",
4055                                &gamenum, white_holding, black_holding,
4056                                new_piece);
4057                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4058                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4059                         /* [HGM] copy holdings to partner-board holdings area */
4060                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4061                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4062                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4063                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4064                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4065                       }
4066                     }
4067                     /* Suppress following prompt */
4068                     if (looking_at(buf, &i, "*% ")) {
4069                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4070                         savingComment = FALSE;
4071                         suppressKibitz = 0;
4072                     }
4073                     next_out = i;
4074                 }
4075                 continue;
4076             }
4077
4078             i++;                /* skip unparsed character and loop back */
4079         }
4080
4081         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4082 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4083 //          SendToPlayer(&buf[next_out], i - next_out);
4084             started != STARTED_HOLDINGS && leftover_start > next_out) {
4085             SendToPlayer(&buf[next_out], leftover_start - next_out);
4086             next_out = i;
4087         }
4088
4089         leftover_len = buf_len - leftover_start;
4090         /* if buffer ends with something we couldn't parse,
4091            reparse it after appending the next read */
4092
4093     } else if (count == 0) {
4094         RemoveInputSource(isr);
4095         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4096     } else {
4097         DisplayFatalError(_("Error reading from ICS"), error, 1);
4098     }
4099 }
4100
4101
4102 /* Board style 12 looks like this:
4103
4104    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4105
4106  * The "<12> " is stripped before it gets to this routine.  The two
4107  * trailing 0's (flip state and clock ticking) are later addition, and
4108  * some chess servers may not have them, or may have only the first.
4109  * Additional trailing fields may be added in the future.
4110  */
4111
4112 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4113
4114 #define RELATION_OBSERVING_PLAYED    0
4115 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4116 #define RELATION_PLAYING_MYMOVE      1
4117 #define RELATION_PLAYING_NOTMYMOVE  -1
4118 #define RELATION_EXAMINING           2
4119 #define RELATION_ISOLATED_BOARD     -3
4120 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4121
4122 void
4123 ParseBoard12(string)
4124      char *string;
4125 {
4126     GameMode newGameMode;
4127     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4128     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4129     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4130     char to_play, board_chars[200];
4131     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4132     char black[32], white[32];
4133     Board board;
4134     int prevMove = currentMove;
4135     int ticking = 2;
4136     ChessMove moveType;
4137     int fromX, fromY, toX, toY;
4138     char promoChar;
4139     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4140     char *bookHit = NULL; // [HGM] book
4141     Boolean weird = FALSE, reqFlag = FALSE;
4142
4143     fromX = fromY = toX = toY = -1;
4144
4145     newGame = FALSE;
4146
4147     if (appData.debugMode)
4148       fprintf(debugFP, _("Parsing board: %s\n"), string);
4149
4150     move_str[0] = NULLCHAR;
4151     elapsed_time[0] = NULLCHAR;
4152     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4153         int  i = 0, j;
4154         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4155             if(string[i] == ' ') { ranks++; files = 0; }
4156             else files++;
4157             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4158             i++;
4159         }
4160         for(j = 0; j <i; j++) board_chars[j] = string[j];
4161         board_chars[i] = '\0';
4162         string += i + 1;
4163     }
4164     n = sscanf(string, PATTERN, &to_play, &double_push,
4165                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4166                &gamenum, white, black, &relation, &basetime, &increment,
4167                &white_stren, &black_stren, &white_time, &black_time,
4168                &moveNum, str, elapsed_time, move_str, &ics_flip,
4169                &ticking);
4170
4171     if (n < 21) {
4172         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4173         DisplayError(str, 0);
4174         return;
4175     }
4176
4177     /* Convert the move number to internal form */
4178     moveNum = (moveNum - 1) * 2;
4179     if (to_play == 'B') moveNum++;
4180     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4181       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4182                         0, 1);
4183       return;
4184     }
4185
4186     switch (relation) {
4187       case RELATION_OBSERVING_PLAYED:
4188       case RELATION_OBSERVING_STATIC:
4189         if (gamenum == -1) {
4190             /* Old ICC buglet */
4191             relation = RELATION_OBSERVING_STATIC;
4192         }
4193         newGameMode = IcsObserving;
4194         break;
4195       case RELATION_PLAYING_MYMOVE:
4196       case RELATION_PLAYING_NOTMYMOVE:
4197         newGameMode =
4198           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4199             IcsPlayingWhite : IcsPlayingBlack;
4200         break;
4201       case RELATION_EXAMINING:
4202         newGameMode = IcsExamining;
4203         break;
4204       case RELATION_ISOLATED_BOARD:
4205       default:
4206         /* Just display this board.  If user was doing something else,
4207            we will forget about it until the next board comes. */
4208         newGameMode = IcsIdle;
4209         break;
4210       case RELATION_STARTING_POSITION:
4211         newGameMode = gameMode;
4212         break;
4213     }
4214
4215     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4216          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4217       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4218       char *toSqr;
4219       for (k = 0; k < ranks; k++) {
4220         for (j = 0; j < files; j++)
4221           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4222         if(gameInfo.holdingsWidth > 1) {
4223              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4224              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4225         }
4226       }
4227       CopyBoard(partnerBoard, board);
4228       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4229         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4230         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4231       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4232       if(toSqr = strchr(str, '-')) {
4233         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4234         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4235       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4236       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4237       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4238       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4239       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4240       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4241                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4242       DisplayMessage(partnerStatus, "");
4243         partnerBoardValid = TRUE;
4244       return;
4245     }
4246
4247     /* Modify behavior for initial board display on move listing
4248        of wild games.
4249        */
4250     switch (ics_getting_history) {
4251       case H_FALSE:
4252       case H_REQUESTED:
4253         break;
4254       case H_GOT_REQ_HEADER:
4255       case H_GOT_UNREQ_HEADER:
4256         /* This is the initial position of the current game */
4257         gamenum = ics_gamenum;
4258         moveNum = 0;            /* old ICS bug workaround */
4259         if (to_play == 'B') {
4260           startedFromSetupPosition = TRUE;
4261           blackPlaysFirst = TRUE;
4262           moveNum = 1;
4263           if (forwardMostMove == 0) forwardMostMove = 1;
4264           if (backwardMostMove == 0) backwardMostMove = 1;
4265           if (currentMove == 0) currentMove = 1;
4266         }
4267         newGameMode = gameMode;
4268         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4269         break;
4270       case H_GOT_UNWANTED_HEADER:
4271         /* This is an initial board that we don't want */
4272         return;
4273       case H_GETTING_MOVES:
4274         /* Should not happen */
4275         DisplayError(_("Error gathering move list: extra board"), 0);
4276         ics_getting_history = H_FALSE;
4277         return;
4278     }
4279
4280    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4281                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4282      /* [HGM] We seem to have switched variant unexpectedly
4283       * Try to guess new variant from board size
4284       */
4285           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4286           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4287           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4288           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4289           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4290           if(!weird) newVariant = VariantNormal;
4291           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4292           /* Get a move list just to see the header, which
4293              will tell us whether this is really bug or zh */
4294           if (ics_getting_history == H_FALSE) {
4295             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4296             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4297             SendToICS(str);
4298           }
4299     }
4300
4301     /* Take action if this is the first board of a new game, or of a
4302        different game than is currently being displayed.  */
4303     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4304         relation == RELATION_ISOLATED_BOARD) {
4305
4306         /* Forget the old game and get the history (if any) of the new one */
4307         if (gameMode != BeginningOfGame) {
4308           Reset(TRUE, TRUE);
4309         }
4310         newGame = TRUE;
4311         if (appData.autoRaiseBoard) BoardToTop();
4312         prevMove = -3;
4313         if (gamenum == -1) {
4314             newGameMode = IcsIdle;
4315         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4316                    appData.getMoveList && !reqFlag) {
4317             /* Need to get game history */
4318             ics_getting_history = H_REQUESTED;
4319             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4320             SendToICS(str);
4321         }
4322
4323         /* Initially flip the board to have black on the bottom if playing
4324            black or if the ICS flip flag is set, but let the user change
4325            it with the Flip View button. */
4326         flipView = appData.autoFlipView ?
4327           (newGameMode == IcsPlayingBlack) || ics_flip :
4328           appData.flipView;
4329
4330         /* Done with values from previous mode; copy in new ones */
4331         gameMode = newGameMode;
4332         ModeHighlight();
4333         ics_gamenum = gamenum;
4334         if (gamenum == gs_gamenum) {
4335             int klen = strlen(gs_kind);
4336             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4337             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4338             gameInfo.event = StrSave(str);
4339         } else {
4340             gameInfo.event = StrSave("ICS game");
4341         }
4342         gameInfo.site = StrSave(appData.icsHost);
4343         gameInfo.date = PGNDate();
4344         gameInfo.round = StrSave("-");
4345         gameInfo.white = StrSave(white);
4346         gameInfo.black = StrSave(black);
4347         timeControl = basetime * 60 * 1000;
4348         timeControl_2 = 0;
4349         timeIncrement = increment * 1000;
4350         movesPerSession = 0;
4351         gameInfo.timeControl = TimeControlTagValue();
4352         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4353   if (appData.debugMode) {
4354     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4355     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4356     setbuf(debugFP, NULL);
4357   }
4358
4359         gameInfo.outOfBook = NULL;
4360
4361         /* Do we have the ratings? */
4362         if (strcmp(player1Name, white) == 0 &&
4363             strcmp(player2Name, black) == 0) {
4364             if (appData.debugMode)
4365               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4366                       player1Rating, player2Rating);
4367             gameInfo.whiteRating = player1Rating;
4368             gameInfo.blackRating = player2Rating;
4369         } else if (strcmp(player2Name, white) == 0 &&
4370                    strcmp(player1Name, black) == 0) {
4371             if (appData.debugMode)
4372               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4373                       player2Rating, player1Rating);
4374             gameInfo.whiteRating = player2Rating;
4375             gameInfo.blackRating = player1Rating;
4376         }
4377         player1Name[0] = player2Name[0] = NULLCHAR;
4378
4379         /* Silence shouts if requested */
4380         if (appData.quietPlay &&
4381             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4382             SendToICS(ics_prefix);
4383             SendToICS("set shout 0\n");
4384         }
4385     }
4386
4387     /* Deal with midgame name changes */
4388     if (!newGame) {
4389         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4390             if (gameInfo.white) free(gameInfo.white);
4391             gameInfo.white = StrSave(white);
4392         }
4393         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4394             if (gameInfo.black) free(gameInfo.black);
4395             gameInfo.black = StrSave(black);
4396         }
4397     }
4398
4399     /* Throw away game result if anything actually changes in examine mode */
4400     if (gameMode == IcsExamining && !newGame) {
4401         gameInfo.result = GameUnfinished;
4402         if (gameInfo.resultDetails != NULL) {
4403             free(gameInfo.resultDetails);
4404             gameInfo.resultDetails = NULL;
4405         }
4406     }
4407
4408     /* In pausing && IcsExamining mode, we ignore boards coming
4409        in if they are in a different variation than we are. */
4410     if (pauseExamInvalid) return;
4411     if (pausing && gameMode == IcsExamining) {
4412         if (moveNum <= pauseExamForwardMostMove) {
4413             pauseExamInvalid = TRUE;
4414             forwardMostMove = pauseExamForwardMostMove;
4415             return;
4416         }
4417     }
4418
4419   if (appData.debugMode) {
4420     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4421   }
4422     /* Parse the board */
4423     for (k = 0; k < ranks; k++) {
4424       for (j = 0; j < files; j++)
4425         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4426       if(gameInfo.holdingsWidth > 1) {
4427            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4428            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4429       }
4430     }
4431     CopyBoard(boards[moveNum], board);
4432     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4433     if (moveNum == 0) {
4434         startedFromSetupPosition =
4435           !CompareBoards(board, initialPosition);
4436         if(startedFromSetupPosition)
4437             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4438     }
4439
4440     /* [HGM] Set castling rights. Take the outermost Rooks,
4441        to make it also work for FRC opening positions. Note that board12
4442        is really defective for later FRC positions, as it has no way to
4443        indicate which Rook can castle if they are on the same side of King.
4444        For the initial position we grant rights to the outermost Rooks,
4445        and remember thos rights, and we then copy them on positions
4446        later in an FRC game. This means WB might not recognize castlings with
4447        Rooks that have moved back to their original position as illegal,
4448        but in ICS mode that is not its job anyway.
4449     */
4450     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4451     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4452
4453         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4454             if(board[0][i] == WhiteRook) j = i;
4455         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4456         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4457             if(board[0][i] == WhiteRook) j = i;
4458         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4459         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4460             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4461         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4462         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4463             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4464         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4465
4466         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4467         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4468             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4469         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4470             if(board[BOARD_HEIGHT-1][k] == bKing)
4471                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4472         if(gameInfo.variant == VariantTwoKings) {
4473             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4474             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4475             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4476         }
4477     } else { int r;
4478         r = boards[moveNum][CASTLING][0] = initialRights[0];
4479         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4480         r = boards[moveNum][CASTLING][1] = initialRights[1];
4481         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4482         r = boards[moveNum][CASTLING][3] = initialRights[3];
4483         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4484         r = boards[moveNum][CASTLING][4] = initialRights[4];
4485         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4486         /* wildcastle kludge: always assume King has rights */
4487         r = boards[moveNum][CASTLING][2] = initialRights[2];
4488         r = boards[moveNum][CASTLING][5] = initialRights[5];
4489     }
4490     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4491     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4492
4493
4494     if (ics_getting_history == H_GOT_REQ_HEADER ||
4495         ics_getting_history == H_GOT_UNREQ_HEADER) {
4496         /* This was an initial position from a move list, not
4497            the current position */
4498         return;
4499     }
4500
4501     /* Update currentMove and known move number limits */
4502     newMove = newGame || moveNum > forwardMostMove;
4503
4504     if (newGame) {
4505         forwardMostMove = backwardMostMove = currentMove = moveNum;
4506         if (gameMode == IcsExamining && moveNum == 0) {
4507           /* Workaround for ICS limitation: we are not told the wild
4508              type when starting to examine a game.  But if we ask for
4509              the move list, the move list header will tell us */
4510             ics_getting_history = H_REQUESTED;
4511             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4512             SendToICS(str);
4513         }
4514     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4515                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4516 #if ZIPPY
4517         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4518         /* [HGM] applied this also to an engine that is silently watching        */
4519         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4520             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4521             gameInfo.variant == currentlyInitializedVariant) {
4522           takeback = forwardMostMove - moveNum;
4523           for (i = 0; i < takeback; i++) {
4524             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4525             SendToProgram("undo\n", &first);
4526           }
4527         }
4528 #endif
4529
4530         forwardMostMove = moveNum;
4531         if (!pausing || currentMove > forwardMostMove)
4532           currentMove = forwardMostMove;
4533     } else {
4534         /* New part of history that is not contiguous with old part */
4535         if (pausing && gameMode == IcsExamining) {
4536             pauseExamInvalid = TRUE;
4537             forwardMostMove = pauseExamForwardMostMove;
4538             return;
4539         }
4540         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4541 #if ZIPPY
4542             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4543                 // [HGM] when we will receive the move list we now request, it will be
4544                 // fed to the engine from the first move on. So if the engine is not
4545                 // in the initial position now, bring it there.
4546                 InitChessProgram(&first, 0);
4547             }
4548 #endif
4549             ics_getting_history = H_REQUESTED;
4550             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4551             SendToICS(str);
4552         }
4553         forwardMostMove = backwardMostMove = currentMove = moveNum;
4554     }
4555
4556     /* Update the clocks */
4557     if (strchr(elapsed_time, '.')) {
4558       /* Time is in ms */
4559       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4560       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4561     } else {
4562       /* Time is in seconds */
4563       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4564       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4565     }
4566
4567
4568 #if ZIPPY
4569     if (appData.zippyPlay && newGame &&
4570         gameMode != IcsObserving && gameMode != IcsIdle &&
4571         gameMode != IcsExamining)
4572       ZippyFirstBoard(moveNum, basetime, increment);
4573 #endif
4574
4575     /* Put the move on the move list, first converting
4576        to canonical algebraic form. */
4577     if (moveNum > 0) {
4578   if (appData.debugMode) {
4579     if (appData.debugMode) { int f = forwardMostMove;
4580         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4581                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4582                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4583     }
4584     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4585     fprintf(debugFP, "moveNum = %d\n", moveNum);
4586     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4587     setbuf(debugFP, NULL);
4588   }
4589         if (moveNum <= backwardMostMove) {
4590             /* We don't know what the board looked like before
4591                this move.  Punt. */
4592           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4593             strcat(parseList[moveNum - 1], " ");
4594             strcat(parseList[moveNum - 1], elapsed_time);
4595             moveList[moveNum - 1][0] = NULLCHAR;
4596         } else if (strcmp(move_str, "none") == 0) {
4597             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4598             /* Again, we don't know what the board looked like;
4599                this is really the start of the game. */
4600             parseList[moveNum - 1][0] = NULLCHAR;
4601             moveList[moveNum - 1][0] = NULLCHAR;
4602             backwardMostMove = moveNum;
4603             startedFromSetupPosition = TRUE;
4604             fromX = fromY = toX = toY = -1;
4605         } else {
4606           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4607           //                 So we parse the long-algebraic move string in stead of the SAN move
4608           int valid; char buf[MSG_SIZ], *prom;
4609
4610           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4611                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4612           // str looks something like "Q/a1-a2"; kill the slash
4613           if(str[1] == '/')
4614             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4615           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4616           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4617                 strcat(buf, prom); // long move lacks promo specification!
4618           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4619                 if(appData.debugMode)
4620                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4621                 safeStrCpy(move_str, buf, MSG_SIZ);
4622           }
4623           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4624                                 &fromX, &fromY, &toX, &toY, &promoChar)
4625                || ParseOneMove(buf, moveNum - 1, &moveType,
4626                                 &fromX, &fromY, &toX, &toY, &promoChar);
4627           // end of long SAN patch
4628           if (valid) {
4629             (void) CoordsToAlgebraic(boards[moveNum - 1],
4630                                      PosFlags(moveNum - 1),
4631                                      fromY, fromX, toY, toX, promoChar,
4632                                      parseList[moveNum-1]);
4633             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4634               case MT_NONE:
4635               case MT_STALEMATE:
4636               default:
4637                 break;
4638               case MT_CHECK:
4639                 if(gameInfo.variant != VariantShogi)
4640                     strcat(parseList[moveNum - 1], "+");
4641                 break;
4642               case MT_CHECKMATE:
4643               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4644                 strcat(parseList[moveNum - 1], "#");
4645                 break;
4646             }
4647             strcat(parseList[moveNum - 1], " ");
4648             strcat(parseList[moveNum - 1], elapsed_time);
4649             /* currentMoveString is set as a side-effect of ParseOneMove */
4650             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4651             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4652             strcat(moveList[moveNum - 1], "\n");
4653
4654             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4655                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4656               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4657                 ChessSquare old, new = boards[moveNum][k][j];
4658                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4659                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4660                   if(old == new) continue;
4661                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4662                   else if(new == WhiteWazir || new == BlackWazir) {
4663                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4664                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4665                       else boards[moveNum][k][j] = old; // preserve type of Gold
4666                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4667                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4668               }
4669           } else {
4670             /* Move from ICS was illegal!?  Punt. */
4671             if (appData.debugMode) {
4672               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4673               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4674             }
4675             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4676             strcat(parseList[moveNum - 1], " ");
4677             strcat(parseList[moveNum - 1], elapsed_time);
4678             moveList[moveNum - 1][0] = NULLCHAR;
4679             fromX = fromY = toX = toY = -1;
4680           }
4681         }
4682   if (appData.debugMode) {
4683     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4684     setbuf(debugFP, NULL);
4685   }
4686
4687 #if ZIPPY
4688         /* Send move to chess program (BEFORE animating it). */
4689         if (appData.zippyPlay && !newGame && newMove &&
4690            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4691
4692             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4693                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4694                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4695                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4696                             move_str);
4697                     DisplayError(str, 0);
4698                 } else {
4699                     if (first.sendTime) {
4700                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4701                     }
4702                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4703                     if (firstMove && !bookHit) {
4704                         firstMove = FALSE;
4705                         if (first.useColors) {
4706                           SendToProgram(gameMode == IcsPlayingWhite ?
4707                                         "white\ngo\n" :
4708                                         "black\ngo\n", &first);
4709                         } else {
4710                           SendToProgram("go\n", &first);
4711                         }
4712                         first.maybeThinking = TRUE;
4713                     }
4714                 }
4715             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4716               if (moveList[moveNum - 1][0] == NULLCHAR) {
4717                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4718                 DisplayError(str, 0);
4719               } else {
4720                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4721                 SendMoveToProgram(moveNum - 1, &first);
4722               }
4723             }
4724         }
4725 #endif
4726     }
4727
4728     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4729         /* If move comes from a remote source, animate it.  If it
4730            isn't remote, it will have already been animated. */
4731         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4732             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4733         }
4734         if (!pausing && appData.highlightLastMove) {
4735             SetHighlights(fromX, fromY, toX, toY);
4736         }
4737     }
4738
4739     /* Start the clocks */
4740     whiteFlag = blackFlag = FALSE;
4741     appData.clockMode = !(basetime == 0 && increment == 0);
4742     if (ticking == 0) {
4743       ics_clock_paused = TRUE;
4744       StopClocks();
4745     } else if (ticking == 1) {
4746       ics_clock_paused = FALSE;
4747     }
4748     if (gameMode == IcsIdle ||
4749         relation == RELATION_OBSERVING_STATIC ||
4750         relation == RELATION_EXAMINING ||
4751         ics_clock_paused)
4752       DisplayBothClocks();
4753     else
4754       StartClocks();
4755
4756     /* Display opponents and material strengths */
4757     if (gameInfo.variant != VariantBughouse &&
4758         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4759         if (tinyLayout || smallLayout) {
4760             if(gameInfo.variant == VariantNormal)
4761               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4762                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4763                     basetime, increment);
4764             else
4765               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4766                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4767                     basetime, increment, (int) gameInfo.variant);
4768         } else {
4769             if(gameInfo.variant == VariantNormal)
4770               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4771                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4772                     basetime, increment);
4773             else
4774               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4775                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4776                     basetime, increment, VariantName(gameInfo.variant));
4777         }
4778         DisplayTitle(str);
4779   if (appData.debugMode) {
4780     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4781   }
4782     }
4783
4784
4785     /* Display the board */
4786     if (!pausing && !appData.noGUI) {
4787
4788       if (appData.premove)
4789           if (!gotPremove ||
4790              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4791              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4792               ClearPremoveHighlights();
4793
4794       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4795         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4796       DrawPosition(j, boards[currentMove]);
4797
4798       DisplayMove(moveNum - 1);
4799       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4800             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4801               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4802         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4803       }
4804     }
4805
4806     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4807 #if ZIPPY
4808     if(bookHit) { // [HGM] book: simulate book reply
4809         static char bookMove[MSG_SIZ]; // a bit generous?
4810
4811         programStats.nodes = programStats.depth = programStats.time =
4812         programStats.score = programStats.got_only_move = 0;
4813         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4814
4815         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4816         strcat(bookMove, bookHit);
4817         HandleMachineMove(bookMove, &first);
4818     }
4819 #endif
4820 }
4821
4822 void
4823 GetMoveListEvent()
4824 {
4825     char buf[MSG_SIZ];
4826     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4827         ics_getting_history = H_REQUESTED;
4828         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4829         SendToICS(buf);
4830     }
4831 }
4832
4833 void
4834 AnalysisPeriodicEvent(force)
4835      int force;
4836 {
4837     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4838          && !force) || !appData.periodicUpdates)
4839       return;
4840
4841     /* Send . command to Crafty to collect stats */
4842     SendToProgram(".\n", &first);
4843
4844     /* Don't send another until we get a response (this makes
4845        us stop sending to old Crafty's which don't understand
4846        the "." command (sending illegal cmds resets node count & time,
4847        which looks bad)) */
4848     programStats.ok_to_send = 0;
4849 }
4850
4851 void ics_update_width(new_width)
4852         int new_width;
4853 {
4854         ics_printf("set width %d\n", new_width);
4855 }
4856
4857 void
4858 SendMoveToProgram(moveNum, cps)
4859      int moveNum;
4860      ChessProgramState *cps;
4861 {
4862     char buf[MSG_SIZ];
4863
4864     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4865         // null move in variant where engine does not understand it (for analysis purposes)
4866         SendBoard(cps, moveNum + 1); // send position after move in stead.
4867         return;
4868     }
4869     if (cps->useUsermove) {
4870       SendToProgram("usermove ", cps);
4871     }
4872     if (cps->useSAN) {
4873       char *space;
4874       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4875         int len = space - parseList[moveNum];
4876         memcpy(buf, parseList[moveNum], len);
4877         buf[len++] = '\n';
4878         buf[len] = NULLCHAR;
4879       } else {
4880         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4881       }
4882       SendToProgram(buf, cps);
4883     } else {
4884       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4885         AlphaRank(moveList[moveNum], 4);
4886         SendToProgram(moveList[moveNum], cps);
4887         AlphaRank(moveList[moveNum], 4); // and back
4888       } else
4889       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4890        * the engine. It would be nice to have a better way to identify castle
4891        * moves here. */
4892       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4893                                                                          && cps->useOOCastle) {
4894         int fromX = moveList[moveNum][0] - AAA;
4895         int fromY = moveList[moveNum][1] - ONE;
4896         int toX = moveList[moveNum][2] - AAA;
4897         int toY = moveList[moveNum][3] - ONE;
4898         if((boards[moveNum][fromY][fromX] == WhiteKing
4899             && boards[moveNum][toY][toX] == WhiteRook)
4900            || (boards[moveNum][fromY][fromX] == BlackKing
4901                && boards[moveNum][toY][toX] == BlackRook)) {
4902           if(toX > fromX) SendToProgram("O-O\n", cps);
4903           else SendToProgram("O-O-O\n", cps);
4904         }
4905         else SendToProgram(moveList[moveNum], cps);
4906       } else
4907       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4908         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4909           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4910           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4911                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4912         } else
4913           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4914                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4915         SendToProgram(buf, cps);
4916       }
4917       else SendToProgram(moveList[moveNum], cps);
4918       /* End of additions by Tord */
4919     }
4920
4921     /* [HGM] setting up the opening has brought engine in force mode! */
4922     /*       Send 'go' if we are in a mode where machine should play. */
4923     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4924         (gameMode == TwoMachinesPlay   ||
4925 #if ZIPPY
4926          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4927 #endif
4928          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4929         SendToProgram("go\n", cps);
4930   if (appData.debugMode) {
4931     fprintf(debugFP, "(extra)\n");
4932   }
4933     }
4934     setboardSpoiledMachineBlack = 0;
4935 }
4936
4937 void
4938 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4939      ChessMove moveType;
4940      int fromX, fromY, toX, toY;
4941      char promoChar;
4942 {
4943     char user_move[MSG_SIZ];
4944
4945     switch (moveType) {
4946       default:
4947         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4948                 (int)moveType, fromX, fromY, toX, toY);
4949         DisplayError(user_move + strlen("say "), 0);
4950         break;
4951       case WhiteKingSideCastle:
4952       case BlackKingSideCastle:
4953       case WhiteQueenSideCastleWild:
4954       case BlackQueenSideCastleWild:
4955       /* PUSH Fabien */
4956       case WhiteHSideCastleFR:
4957       case BlackHSideCastleFR:
4958       /* POP Fabien */
4959         snprintf(user_move, MSG_SIZ, "o-o\n");
4960         break;
4961       case WhiteQueenSideCastle:
4962       case BlackQueenSideCastle:
4963       case WhiteKingSideCastleWild:
4964       case BlackKingSideCastleWild:
4965       /* PUSH Fabien */
4966       case WhiteASideCastleFR:
4967       case BlackASideCastleFR:
4968       /* POP Fabien */
4969         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4970         break;
4971       case WhiteNonPromotion:
4972       case BlackNonPromotion:
4973         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4974         break;
4975       case WhitePromotion:
4976       case BlackPromotion:
4977         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4978           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4979                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4980                 PieceToChar(WhiteFerz));
4981         else if(gameInfo.variant == VariantGreat)
4982           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4983                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4984                 PieceToChar(WhiteMan));
4985         else
4986           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4987                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4988                 promoChar);
4989         break;
4990       case WhiteDrop:
4991       case BlackDrop:
4992       drop:
4993         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4994                  ToUpper(PieceToChar((ChessSquare) fromX)),
4995                  AAA + toX, ONE + toY);
4996         break;
4997       case IllegalMove:  /* could be a variant we don't quite understand */
4998         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4999       case NormalMove:
5000       case WhiteCapturesEnPassant:
5001       case BlackCapturesEnPassant:
5002         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5003                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5004         break;
5005     }
5006     SendToICS(user_move);
5007     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5008         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5009 }
5010
5011 void
5012 UploadGameEvent()
5013 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5014     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5015     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5016     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5017         DisplayError("You cannot do this while you are playing or observing", 0);
5018         return;
5019     }
5020     if(gameMode != IcsExamining) { // is this ever not the case?
5021         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5022
5023         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5024           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5025         } else { // on FICS we must first go to general examine mode
5026           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5027         }
5028         if(gameInfo.variant != VariantNormal) {
5029             // try figure out wild number, as xboard names are not always valid on ICS
5030             for(i=1; i<=36; i++) {
5031               snprintf(buf, MSG_SIZ, "wild/%d", i);
5032                 if(StringToVariant(buf) == gameInfo.variant) break;
5033             }
5034             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5035             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5036             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5037         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5038         SendToICS(ics_prefix);
5039         SendToICS(buf);
5040         if(startedFromSetupPosition || backwardMostMove != 0) {
5041           fen = PositionToFEN(backwardMostMove, NULL);
5042           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5043             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5044             SendToICS(buf);
5045           } else { // FICS: everything has to set by separate bsetup commands
5046             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5047             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5048             SendToICS(buf);
5049             if(!WhiteOnMove(backwardMostMove)) {
5050                 SendToICS("bsetup tomove black\n");
5051             }
5052             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5053             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5054             SendToICS(buf);
5055             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5056             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5057             SendToICS(buf);
5058             i = boards[backwardMostMove][EP_STATUS];
5059             if(i >= 0) { // set e.p.
5060               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5061                 SendToICS(buf);
5062             }
5063             bsetup++;
5064           }
5065         }
5066       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5067             SendToICS("bsetup done\n"); // switch to normal examining.
5068     }
5069     for(i = backwardMostMove; i<last; i++) {
5070         char buf[20];
5071         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5072         SendToICS(buf);
5073     }
5074     SendToICS(ics_prefix);
5075     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5076 }
5077
5078 void
5079 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5080      int rf, ff, rt, ft;
5081      char promoChar;
5082      char move[7];
5083 {
5084     if (rf == DROP_RANK) {
5085       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5086       sprintf(move, "%c@%c%c\n",
5087                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5088     } else {
5089         if (promoChar == 'x' || promoChar == NULLCHAR) {
5090           sprintf(move, "%c%c%c%c\n",
5091                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5092         } else {
5093             sprintf(move, "%c%c%c%c%c\n",
5094                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5095         }
5096     }
5097 }
5098
5099 void
5100 ProcessICSInitScript(f)
5101      FILE *f;
5102 {
5103     char buf[MSG_SIZ];
5104
5105     while (fgets(buf, MSG_SIZ, f)) {
5106         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5107     }
5108
5109     fclose(f);
5110 }
5111
5112
5113 static int lastX, lastY, selectFlag, dragging;
5114
5115 void
5116 Sweep(int step)
5117 {
5118     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5119     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5120     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5121     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5122     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5123     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5124     do {
5125         promoSweep -= step;
5126         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5127         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5128         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5129         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5130         if(!step) step = 1;
5131     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5132             appData.testLegality && (promoSweep == king ||
5133             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5134     ChangeDragPiece(promoSweep);
5135 }
5136
5137 int PromoScroll(int x, int y)
5138 {
5139   int step = 0;
5140
5141   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5142   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5143   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5144   if(!step) return FALSE;
5145   lastX = x; lastY = y;
5146   if((promoSweep < BlackPawn) == flipView) step = -step;
5147   if(step > 0) selectFlag = 1;
5148   if(!selectFlag) Sweep(step);
5149   return FALSE;
5150 }
5151
5152 void
5153 NextPiece(int step)
5154 {
5155     ChessSquare piece = boards[currentMove][toY][toX];
5156     do {
5157         pieceSweep -= step;
5158         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5159         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5160         if(!step) step = -1;
5161     } while(PieceToChar(pieceSweep) == '.');
5162     boards[currentMove][toY][toX] = pieceSweep;
5163     DrawPosition(FALSE, boards[currentMove]);
5164     boards[currentMove][toY][toX] = piece;
5165 }
5166 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5167 void
5168 AlphaRank(char *move, int n)
5169 {
5170 //    char *p = move, c; int x, y;
5171
5172     if (appData.debugMode) {
5173         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5174     }
5175
5176     if(move[1]=='*' &&
5177        move[2]>='0' && move[2]<='9' &&
5178        move[3]>='a' && move[3]<='x'    ) {
5179         move[1] = '@';
5180         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5181         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5182     } else
5183     if(move[0]>='0' && move[0]<='9' &&
5184        move[1]>='a' && move[1]<='x' &&
5185        move[2]>='0' && move[2]<='9' &&
5186        move[3]>='a' && move[3]<='x'    ) {
5187         /* input move, Shogi -> normal */
5188         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5189         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5190         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5191         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5192     } else
5193     if(move[1]=='@' &&
5194        move[3]>='0' && move[3]<='9' &&
5195        move[2]>='a' && move[2]<='x'    ) {
5196         move[1] = '*';
5197         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5198         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5199     } else
5200     if(
5201        move[0]>='a' && move[0]<='x' &&
5202        move[3]>='0' && move[3]<='9' &&
5203        move[2]>='a' && move[2]<='x'    ) {
5204          /* output move, normal -> Shogi */
5205         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5206         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5207         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5208         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5209         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5210     }
5211     if (appData.debugMode) {
5212         fprintf(debugFP, "   out = '%s'\n", move);
5213     }
5214 }
5215
5216 char yy_textstr[8000];
5217
5218 /* Parser for moves from gnuchess, ICS, or user typein box */
5219 Boolean
5220 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5221      char *move;
5222      int moveNum;
5223      ChessMove *moveType;
5224      int *fromX, *fromY, *toX, *toY;
5225      char *promoChar;
5226 {
5227     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5228
5229     switch (*moveType) {
5230       case WhitePromotion:
5231       case BlackPromotion:
5232       case WhiteNonPromotion:
5233       case BlackNonPromotion:
5234       case NormalMove:
5235       case WhiteCapturesEnPassant:
5236       case BlackCapturesEnPassant:
5237       case WhiteKingSideCastle:
5238       case WhiteQueenSideCastle:
5239       case BlackKingSideCastle:
5240       case BlackQueenSideCastle:
5241       case WhiteKingSideCastleWild:
5242       case WhiteQueenSideCastleWild:
5243       case BlackKingSideCastleWild:
5244       case BlackQueenSideCastleWild:
5245       /* Code added by Tord: */
5246       case WhiteHSideCastleFR:
5247       case WhiteASideCastleFR:
5248       case BlackHSideCastleFR:
5249       case BlackASideCastleFR:
5250       /* End of code added by Tord */
5251       case IllegalMove:         /* bug or odd chess variant */
5252         *fromX = currentMoveString[0] - AAA;
5253         *fromY = currentMoveString[1] - ONE;
5254         *toX = currentMoveString[2] - AAA;
5255         *toY = currentMoveString[3] - ONE;
5256         *promoChar = currentMoveString[4];
5257         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5258             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5259     if (appData.debugMode) {
5260         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5261     }
5262             *fromX = *fromY = *toX = *toY = 0;
5263             return FALSE;
5264         }
5265         if (appData.testLegality) {
5266           return (*moveType != IllegalMove);
5267         } else {
5268           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5269                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5270         }
5271
5272       case WhiteDrop:
5273       case BlackDrop:
5274         *fromX = *moveType == WhiteDrop ?
5275           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5276           (int) CharToPiece(ToLower(currentMoveString[0]));
5277         *fromY = DROP_RANK;
5278         *toX = currentMoveString[2] - AAA;
5279         *toY = currentMoveString[3] - ONE;
5280         *promoChar = NULLCHAR;
5281         return TRUE;
5282
5283       case AmbiguousMove:
5284       case ImpossibleMove:
5285       case EndOfFile:
5286       case ElapsedTime:
5287       case Comment:
5288       case PGNTag:
5289       case NAG:
5290       case WhiteWins:
5291       case BlackWins:
5292       case GameIsDrawn:
5293       default:
5294     if (appData.debugMode) {
5295         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5296     }
5297         /* bug? */
5298         *fromX = *fromY = *toX = *toY = 0;
5299         *promoChar = NULLCHAR;
5300         return FALSE;
5301     }
5302 }
5303
5304 Boolean pushed = FALSE;
5305 char *lastParseAttempt;
5306
5307 void
5308 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5309 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5310   int fromX, fromY, toX, toY; char promoChar;
5311   ChessMove moveType;
5312   Boolean valid;
5313   int nr = 0;
5314
5315   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5316     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5317     pushed = TRUE;
5318   }
5319   endPV = forwardMostMove;
5320   do {
5321     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5322     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5323     lastParseAttempt = pv;
5324     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5325 if(appData.debugMode){
5326 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5327 }
5328     if(!valid && nr == 0 &&
5329        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5330         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5331         // Hande case where played move is different from leading PV move
5332         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5333         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5334         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5335         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5336           endPV += 2; // if position different, keep this
5337           moveList[endPV-1][0] = fromX + AAA;
5338           moveList[endPV-1][1] = fromY + ONE;
5339           moveList[endPV-1][2] = toX + AAA;
5340           moveList[endPV-1][3] = toY + ONE;
5341           parseList[endPV-1][0] = NULLCHAR;
5342           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5343         }
5344       }
5345     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5346     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5347     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5348     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5349         valid++; // allow comments in PV
5350         continue;
5351     }
5352     nr++;
5353     if(endPV+1 > framePtr) break; // no space, truncate
5354     if(!valid) break;
5355     endPV++;
5356     CopyBoard(boards[endPV], boards[endPV-1]);
5357     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5358     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5359     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5360     CoordsToAlgebraic(boards[endPV - 1],
5361                              PosFlags(endPV - 1),
5362                              fromY, fromX, toY, toX, promoChar,
5363                              parseList[endPV - 1]);
5364   } while(valid);
5365   if(atEnd == 2) return; // used hidden, for PV conversion
5366   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5367   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5368   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5369                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5370   DrawPosition(TRUE, boards[currentMove]);
5371 }
5372
5373 int
5374 MultiPV(ChessProgramState *cps)
5375 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5376         int i;
5377         for(i=0; i<cps->nrOptions; i++)
5378             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5379                 return i;
5380         return -1;
5381 }
5382
5383 Boolean
5384 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5385 {
5386         int startPV, multi, lineStart, origIndex = index;
5387         char *p, buf2[MSG_SIZ];
5388
5389         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5390         lastX = x; lastY = y;
5391         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5392         lineStart = startPV = index;
5393         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5394         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5395         index = startPV;
5396         do{ while(buf[index] && buf[index] != '\n') index++;
5397         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5398         buf[index] = 0;
5399         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5400                 int n = first.option[multi].value;
5401                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5402                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5403                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5404                 first.option[multi].value = n;
5405                 *start = *end = 0;
5406                 return FALSE;
5407         }
5408         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5409         *start = startPV; *end = index-1;
5410         return TRUE;
5411 }
5412
5413 char *
5414 PvToSAN(char *pv)
5415 {
5416         static char buf[10*MSG_SIZ];
5417         int i, k=0, savedEnd=endPV;
5418         *buf = NULLCHAR;
5419         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5420         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5421         for(i = forwardMostMove; i<endPV; i++){
5422             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5423             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5424             k += strlen(buf+k);
5425         }
5426         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5427         if(forwardMostMove < savedEnd) PopInner(0);
5428         endPV = savedEnd;
5429         return buf;
5430 }
5431
5432 Boolean
5433 LoadPV(int x, int y)
5434 { // called on right mouse click to load PV
5435   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5436   lastX = x; lastY = y;
5437   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5438   return TRUE;
5439 }
5440
5441 void
5442 UnLoadPV()
5443 {
5444   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5445   if(endPV < 0) return;
5446   endPV = -1;
5447   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5448         Boolean saveAnimate = appData.animate;
5449         if(pushed) {
5450             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5451                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5452             } else storedGames--; // abandon shelved tail of original game
5453         }
5454         pushed = FALSE;
5455         forwardMostMove = currentMove;
5456         currentMove = oldFMM;
5457         appData.animate = FALSE;
5458         ToNrEvent(forwardMostMove);
5459         appData.animate = saveAnimate;
5460   }
5461   currentMove = forwardMostMove;
5462   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5463   ClearPremoveHighlights();
5464   DrawPosition(TRUE, boards[currentMove]);
5465 }
5466
5467 void
5468 MovePV(int x, int y, int h)
5469 { // step through PV based on mouse coordinates (called on mouse move)
5470   int margin = h>>3, step = 0;
5471
5472   // we must somehow check if right button is still down (might be released off board!)
5473   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5474   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5475   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5476   if(!step) return;
5477   lastX = x; lastY = y;
5478
5479   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5480   if(endPV < 0) return;
5481   if(y < margin) step = 1; else
5482   if(y > h - margin) step = -1;
5483   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5484   currentMove += step;
5485   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5486   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5487                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5488   DrawPosition(FALSE, boards[currentMove]);
5489 }
5490
5491
5492 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5493 // All positions will have equal probability, but the current method will not provide a unique
5494 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5495 #define DARK 1
5496 #define LITE 2
5497 #define ANY 3
5498
5499 int squaresLeft[4];
5500 int piecesLeft[(int)BlackPawn];
5501 int seed, nrOfShuffles;
5502
5503 void GetPositionNumber()
5504 {       // sets global variable seed
5505         int i;
5506
5507         seed = appData.defaultFrcPosition;
5508         if(seed < 0) { // randomize based on time for negative FRC position numbers
5509                 for(i=0; i<50; i++) seed += random();
5510                 seed = random() ^ random() >> 8 ^ random() << 8;
5511                 if(seed<0) seed = -seed;
5512         }
5513 }
5514
5515 int put(Board board, int pieceType, int rank, int n, int shade)
5516 // put the piece on the (n-1)-th empty squares of the given shade
5517 {
5518         int i;
5519
5520         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5521                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5522                         board[rank][i] = (ChessSquare) pieceType;
5523                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5524                         squaresLeft[ANY]--;
5525                         piecesLeft[pieceType]--;
5526                         return i;
5527                 }
5528         }
5529         return -1;
5530 }
5531
5532
5533 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5534 // calculate where the next piece goes, (any empty square), and put it there
5535 {
5536         int i;
5537
5538         i = seed % squaresLeft[shade];
5539         nrOfShuffles *= squaresLeft[shade];
5540         seed /= squaresLeft[shade];
5541         put(board, pieceType, rank, i, shade);
5542 }
5543
5544 void AddTwoPieces(Board board, int pieceType, int rank)
5545 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5546 {
5547         int i, n=squaresLeft[ANY], j=n-1, k;
5548
5549         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5550         i = seed % k;  // pick one
5551         nrOfShuffles *= k;
5552         seed /= k;
5553         while(i >= j) i -= j--;
5554         j = n - 1 - j; i += j;
5555         put(board, pieceType, rank, j, ANY);
5556         put(board, pieceType, rank, i, ANY);
5557 }
5558
5559 void SetUpShuffle(Board board, int number)
5560 {
5561         int i, p, first=1;
5562
5563         GetPositionNumber(); nrOfShuffles = 1;
5564
5565         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5566         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5567         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5568
5569         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5570
5571         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5572             p = (int) board[0][i];
5573             if(p < (int) BlackPawn) piecesLeft[p] ++;
5574             board[0][i] = EmptySquare;
5575         }
5576
5577         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5578             // shuffles restricted to allow normal castling put KRR first
5579             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5580                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5581             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5582                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5583             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5584                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5585             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5586                 put(board, WhiteRook, 0, 0, ANY);
5587             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5588         }
5589
5590         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5591             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5592             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5593                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5594                 while(piecesLeft[p] >= 2) {
5595                     AddOnePiece(board, p, 0, LITE);
5596                     AddOnePiece(board, p, 0, DARK);
5597                 }
5598                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5599             }
5600
5601         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5602             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5603             // but we leave King and Rooks for last, to possibly obey FRC restriction
5604             if(p == (int)WhiteRook) continue;
5605             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5606             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5607         }
5608
5609         // now everything is placed, except perhaps King (Unicorn) and Rooks
5610
5611         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5612             // Last King gets castling rights
5613             while(piecesLeft[(int)WhiteUnicorn]) {
5614                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5615                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5616             }
5617
5618             while(piecesLeft[(int)WhiteKing]) {
5619                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5620                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5621             }
5622
5623
5624         } else {
5625             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5626             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5627         }
5628
5629         // Only Rooks can be left; simply place them all
5630         while(piecesLeft[(int)WhiteRook]) {
5631                 i = put(board, WhiteRook, 0, 0, ANY);
5632                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5633                         if(first) {
5634                                 first=0;
5635                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5636                         }
5637                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5638                 }
5639         }
5640         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5641             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5642         }
5643
5644         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5645 }
5646
5647 int SetCharTable( char *table, const char * map )
5648 /* [HGM] moved here from winboard.c because of its general usefulness */
5649 /*       Basically a safe strcpy that uses the last character as King */
5650 {
5651     int result = FALSE; int NrPieces;
5652
5653     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5654                     && NrPieces >= 12 && !(NrPieces&1)) {
5655         int i; /* [HGM] Accept even length from 12 to 34 */
5656
5657         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5658         for( i=0; i<NrPieces/2-1; i++ ) {
5659             table[i] = map[i];
5660             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5661         }
5662         table[(int) WhiteKing]  = map[NrPieces/2-1];
5663         table[(int) BlackKing]  = map[NrPieces-1];
5664
5665         result = TRUE;
5666     }
5667
5668     return result;
5669 }
5670
5671 void Prelude(Board board)
5672 {       // [HGM] superchess: random selection of exo-pieces
5673         int i, j, k; ChessSquare p;
5674         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5675
5676         GetPositionNumber(); // use FRC position number
5677
5678         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5679             SetCharTable(pieceToChar, appData.pieceToCharTable);
5680             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5681                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5682         }
5683
5684         j = seed%4;                 seed /= 4;
5685         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5686         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5687         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5688         j = seed%3 + (seed%3 >= j); seed /= 3;
5689         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5690         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5691         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5692         j = seed%3;                 seed /= 3;
5693         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5694         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5695         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5696         j = seed%2 + (seed%2 >= j); seed /= 2;
5697         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5698         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5699         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5700         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5701         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5702         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5703         put(board, exoPieces[0],    0, 0, ANY);
5704         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5705 }
5706
5707 void
5708 InitPosition(redraw)
5709      int redraw;
5710 {
5711     ChessSquare (* pieces)[BOARD_FILES];
5712     int i, j, pawnRow, overrule,
5713     oldx = gameInfo.boardWidth,
5714     oldy = gameInfo.boardHeight,
5715     oldh = gameInfo.holdingsWidth;
5716     static int oldv;
5717
5718     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5719
5720     /* [AS] Initialize pv info list [HGM] and game status */
5721     {
5722         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5723             pvInfoList[i].depth = 0;
5724             boards[i][EP_STATUS] = EP_NONE;
5725             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5726         }
5727
5728         initialRulePlies = 0; /* 50-move counter start */
5729
5730         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5731         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5732     }
5733
5734
5735     /* [HGM] logic here is completely changed. In stead of full positions */
5736     /* the initialized data only consist of the two backranks. The switch */
5737     /* selects which one we will use, which is than copied to the Board   */
5738     /* initialPosition, which for the rest is initialized by Pawns and    */
5739     /* empty squares. This initial position is then copied to boards[0],  */
5740     /* possibly after shuffling, so that it remains available.            */
5741
5742     gameInfo.holdingsWidth = 0; /* default board sizes */
5743     gameInfo.boardWidth    = 8;
5744     gameInfo.boardHeight   = 8;
5745     gameInfo.holdingsSize  = 0;
5746     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5747     for(i=0; i<BOARD_FILES-2; i++)
5748       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5749     initialPosition[EP_STATUS] = EP_NONE;
5750     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5751     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5752          SetCharTable(pieceNickName, appData.pieceNickNames);
5753     else SetCharTable(pieceNickName, "............");
5754     pieces = FIDEArray;
5755
5756     switch (gameInfo.variant) {
5757     case VariantFischeRandom:
5758       shuffleOpenings = TRUE;
5759     default:
5760       break;
5761     case VariantShatranj:
5762       pieces = ShatranjArray;
5763       nrCastlingRights = 0;
5764       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5765       break;
5766     case VariantMakruk:
5767       pieces = makrukArray;
5768       nrCastlingRights = 0;
5769       startedFromSetupPosition = TRUE;
5770       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5771       break;
5772     case VariantTwoKings:
5773       pieces = twoKingsArray;
5774       break;
5775     case VariantGrand:
5776       pieces = GrandArray;
5777       nrCastlingRights = 0;
5778       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5779       gameInfo.boardWidth = 10;
5780       gameInfo.boardHeight = 10;
5781       gameInfo.holdingsSize = 7;
5782       break;
5783     case VariantCapaRandom:
5784       shuffleOpenings = TRUE;
5785     case VariantCapablanca:
5786       pieces = CapablancaArray;
5787       gameInfo.boardWidth = 10;
5788       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5789       break;
5790     case VariantGothic:
5791       pieces = GothicArray;
5792       gameInfo.boardWidth = 10;
5793       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5794       break;
5795     case VariantSChess:
5796       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5797       gameInfo.holdingsSize = 7;
5798       break;
5799     case VariantJanus:
5800       pieces = JanusArray;
5801       gameInfo.boardWidth = 10;
5802       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5803       nrCastlingRights = 6;
5804         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5805         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5806         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5807         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5808         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5809         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5810       break;
5811     case VariantFalcon:
5812       pieces = FalconArray;
5813       gameInfo.boardWidth = 10;
5814       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5815       break;
5816     case VariantXiangqi:
5817       pieces = XiangqiArray;
5818       gameInfo.boardWidth  = 9;
5819       gameInfo.boardHeight = 10;
5820       nrCastlingRights = 0;
5821       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5822       break;
5823     case VariantShogi:
5824       pieces = ShogiArray;
5825       gameInfo.boardWidth  = 9;
5826       gameInfo.boardHeight = 9;
5827       gameInfo.holdingsSize = 7;
5828       nrCastlingRights = 0;
5829       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5830       break;
5831     case VariantCourier:
5832       pieces = CourierArray;
5833       gameInfo.boardWidth  = 12;
5834       nrCastlingRights = 0;
5835       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5836       break;
5837     case VariantKnightmate:
5838       pieces = KnightmateArray;
5839       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5840       break;
5841     case VariantSpartan:
5842       pieces = SpartanArray;
5843       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5844       break;
5845     case VariantFairy:
5846       pieces = fairyArray;
5847       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5848       break;
5849     case VariantGreat:
5850       pieces = GreatArray;
5851       gameInfo.boardWidth = 10;
5852       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5853       gameInfo.holdingsSize = 8;
5854       break;
5855     case VariantSuper:
5856       pieces = FIDEArray;
5857       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5858       gameInfo.holdingsSize = 8;
5859       startedFromSetupPosition = TRUE;
5860       break;
5861     case VariantCrazyhouse:
5862     case VariantBughouse:
5863       pieces = FIDEArray;
5864       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5865       gameInfo.holdingsSize = 5;
5866       break;
5867     case VariantWildCastle:
5868       pieces = FIDEArray;
5869       /* !!?shuffle with kings guaranteed to be on d or e file */
5870       shuffleOpenings = 1;
5871       break;
5872     case VariantNoCastle:
5873       pieces = FIDEArray;
5874       nrCastlingRights = 0;
5875       /* !!?unconstrained back-rank shuffle */
5876       shuffleOpenings = 1;
5877       break;
5878     }
5879
5880     overrule = 0;
5881     if(appData.NrFiles >= 0) {
5882         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5883         gameInfo.boardWidth = appData.NrFiles;
5884     }
5885     if(appData.NrRanks >= 0) {
5886         gameInfo.boardHeight = appData.NrRanks;
5887     }
5888     if(appData.holdingsSize >= 0) {
5889         i = appData.holdingsSize;
5890         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5891         gameInfo.holdingsSize = i;
5892     }
5893     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5894     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5895         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5896
5897     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5898     if(pawnRow < 1) pawnRow = 1;
5899     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5900
5901     /* User pieceToChar list overrules defaults */
5902     if(appData.pieceToCharTable != NULL)
5903         SetCharTable(pieceToChar, appData.pieceToCharTable);
5904
5905     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5906
5907         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5908             s = (ChessSquare) 0; /* account holding counts in guard band */
5909         for( i=0; i<BOARD_HEIGHT; i++ )
5910             initialPosition[i][j] = s;
5911
5912         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5913         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5914         initialPosition[pawnRow][j] = WhitePawn;
5915         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5916         if(gameInfo.variant == VariantXiangqi) {
5917             if(j&1) {
5918                 initialPosition[pawnRow][j] =
5919                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5920                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5921                    initialPosition[2][j] = WhiteCannon;
5922                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5923                 }
5924             }
5925         }
5926         if(gameInfo.variant == VariantGrand) {
5927             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5928                initialPosition[0][j] = WhiteRook;
5929                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5930             }
5931         }
5932         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5933     }
5934     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5935
5936             j=BOARD_LEFT+1;
5937             initialPosition[1][j] = WhiteBishop;
5938             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5939             j=BOARD_RGHT-2;
5940             initialPosition[1][j] = WhiteRook;
5941             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5942     }
5943
5944     if( nrCastlingRights == -1) {
5945         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5946         /*       This sets default castling rights from none to normal corners   */
5947         /* Variants with other castling rights must set them themselves above    */
5948         nrCastlingRights = 6;
5949
5950         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5951         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5952         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5953         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5954         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5955         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5956      }
5957
5958      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5959      if(gameInfo.variant == VariantGreat) { // promotion commoners
5960         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5961         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5962         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5963         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5964      }
5965      if( gameInfo.variant == VariantSChess ) {
5966       initialPosition[1][0] = BlackMarshall;
5967       initialPosition[2][0] = BlackAngel;
5968       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5969       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5970       initialPosition[1][1] = initialPosition[2][1] = 
5971       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5972      }
5973   if (appData.debugMode) {
5974     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5975   }
5976     if(shuffleOpenings) {
5977         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5978         startedFromSetupPosition = TRUE;
5979     }
5980     if(startedFromPositionFile) {
5981       /* [HGM] loadPos: use PositionFile for every new game */
5982       CopyBoard(initialPosition, filePosition);
5983       for(i=0; i<nrCastlingRights; i++)
5984           initialRights[i] = filePosition[CASTLING][i];
5985       startedFromSetupPosition = TRUE;
5986     }
5987
5988     CopyBoard(boards[0], initialPosition);
5989
5990     if(oldx != gameInfo.boardWidth ||
5991        oldy != gameInfo.boardHeight ||
5992        oldv != gameInfo.variant ||
5993        oldh != gameInfo.holdingsWidth
5994                                          )
5995             InitDrawingSizes(-2 ,0);
5996
5997     oldv = gameInfo.variant;
5998     if (redraw)
5999       DrawPosition(TRUE, boards[currentMove]);
6000 }
6001
6002 void
6003 SendBoard(cps, moveNum)
6004      ChessProgramState *cps;
6005      int moveNum;
6006 {
6007     char message[MSG_SIZ];
6008
6009     if (cps->useSetboard) {
6010       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6011       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6012       SendToProgram(message, cps);
6013       free(fen);
6014
6015     } else {
6016       ChessSquare *bp;
6017       int i, j;
6018       /* Kludge to set black to move, avoiding the troublesome and now
6019        * deprecated "black" command.
6020        */
6021       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6022         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6023
6024       SendToProgram("edit\n", cps);
6025       SendToProgram("#\n", cps);
6026       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6027         bp = &boards[moveNum][i][BOARD_LEFT];
6028         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6029           if ((int) *bp < (int) BlackPawn) {
6030             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6031                     AAA + j, ONE + i);
6032             if(message[0] == '+' || message[0] == '~') {
6033               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6034                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6035                         AAA + j, ONE + i);
6036             }
6037             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6038                 message[1] = BOARD_RGHT   - 1 - j + '1';
6039                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6040             }
6041             SendToProgram(message, cps);
6042           }
6043         }
6044       }
6045
6046       SendToProgram("c\n", cps);
6047       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6048         bp = &boards[moveNum][i][BOARD_LEFT];
6049         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6050           if (((int) *bp != (int) EmptySquare)
6051               && ((int) *bp >= (int) BlackPawn)) {
6052             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6053                     AAA + j, ONE + i);
6054             if(message[0] == '+' || message[0] == '~') {
6055               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6056                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6057                         AAA + j, ONE + i);
6058             }
6059             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6060                 message[1] = BOARD_RGHT   - 1 - j + '1';
6061                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6062             }
6063             SendToProgram(message, cps);
6064           }
6065         }
6066       }
6067
6068       SendToProgram(".\n", cps);
6069     }
6070     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6071 }
6072
6073 ChessSquare
6074 DefaultPromoChoice(int white)
6075 {
6076     ChessSquare result;
6077     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6078         result = WhiteFerz; // no choice
6079     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6080         result= WhiteKing; // in Suicide Q is the last thing we want
6081     else if(gameInfo.variant == VariantSpartan)
6082         result = white ? WhiteQueen : WhiteAngel;
6083     else result = WhiteQueen;
6084     if(!white) result = WHITE_TO_BLACK result;
6085     return result;
6086 }
6087
6088 static int autoQueen; // [HGM] oneclick
6089
6090 int
6091 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6092 {
6093     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6094     /* [HGM] add Shogi promotions */
6095     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6096     ChessSquare piece;
6097     ChessMove moveType;
6098     Boolean premove;
6099
6100     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6101     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6102
6103     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6104       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6105         return FALSE;
6106
6107     piece = boards[currentMove][fromY][fromX];
6108     if(gameInfo.variant == VariantShogi) {
6109         promotionZoneSize = BOARD_HEIGHT/3;
6110         highestPromotingPiece = (int)WhiteFerz;
6111     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6112         promotionZoneSize = 3;
6113     }
6114
6115     // Treat Lance as Pawn when it is not representing Amazon
6116     if(gameInfo.variant != VariantSuper) {
6117         if(piece == WhiteLance) piece = WhitePawn; else
6118         if(piece == BlackLance) piece = BlackPawn;
6119     }
6120
6121     // next weed out all moves that do not touch the promotion zone at all
6122     if((int)piece >= BlackPawn) {
6123         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6124              return FALSE;
6125         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6126     } else {
6127         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6128            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6129     }
6130
6131     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6132
6133     // weed out mandatory Shogi promotions
6134     if(gameInfo.variant == VariantShogi) {
6135         if(piece >= BlackPawn) {
6136             if(toY == 0 && piece == BlackPawn ||
6137                toY == 0 && piece == BlackQueen ||
6138                toY <= 1 && piece == BlackKnight) {
6139                 *promoChoice = '+';
6140                 return FALSE;
6141             }
6142         } else {
6143             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6144                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6145                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6146                 *promoChoice = '+';
6147                 return FALSE;
6148             }
6149         }
6150     }
6151
6152     // weed out obviously illegal Pawn moves
6153     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6154         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6155         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6156         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6157         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6158         // note we are not allowed to test for valid (non-)capture, due to premove
6159     }
6160
6161     // we either have a choice what to promote to, or (in Shogi) whether to promote
6162     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6163         *promoChoice = PieceToChar(BlackFerz);  // no choice
6164         return FALSE;
6165     }
6166     // no sense asking what we must promote to if it is going to explode...
6167     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6168         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6169         return FALSE;
6170     }
6171     // give caller the default choice even if we will not make it
6172     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6173     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6174     if(        sweepSelect && gameInfo.variant != VariantGreat
6175                            && gameInfo.variant != VariantGrand
6176                            && gameInfo.variant != VariantSuper) return FALSE;
6177     if(autoQueen) return FALSE; // predetermined
6178
6179     // suppress promotion popup on illegal moves that are not premoves
6180     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6181               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6182     if(appData.testLegality && !premove) {
6183         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6184                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6185         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6186             return FALSE;
6187     }
6188
6189     return TRUE;
6190 }
6191
6192 int
6193 InPalace(row, column)
6194      int row, column;
6195 {   /* [HGM] for Xiangqi */
6196     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6197          column < (BOARD_WIDTH + 4)/2 &&
6198          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6199     return FALSE;
6200 }
6201
6202 int
6203 PieceForSquare (x, y)
6204      int x;
6205      int y;
6206 {
6207   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6208      return -1;
6209   else
6210      return boards[currentMove][y][x];
6211 }
6212
6213 int
6214 OKToStartUserMove(x, y)
6215      int x, y;
6216 {
6217     ChessSquare from_piece;
6218     int white_piece;
6219
6220     if (matchMode) return FALSE;
6221     if (gameMode == EditPosition) return TRUE;
6222
6223     if (x >= 0 && y >= 0)
6224       from_piece = boards[currentMove][y][x];
6225     else
6226       from_piece = EmptySquare;
6227
6228     if (from_piece == EmptySquare) return FALSE;
6229
6230     white_piece = (int)from_piece >= (int)WhitePawn &&
6231       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6232
6233     switch (gameMode) {
6234       case AnalyzeFile:
6235       case TwoMachinesPlay:
6236       case EndOfGame:
6237         return FALSE;
6238
6239       case IcsObserving:
6240       case IcsIdle:
6241         return FALSE;
6242
6243       case MachinePlaysWhite:
6244       case IcsPlayingBlack:
6245         if (appData.zippyPlay) return FALSE;
6246         if (white_piece) {
6247             DisplayMoveError(_("You are playing Black"));
6248             return FALSE;
6249         }
6250         break;
6251
6252       case MachinePlaysBlack:
6253       case IcsPlayingWhite:
6254         if (appData.zippyPlay) return FALSE;
6255         if (!white_piece) {
6256             DisplayMoveError(_("You are playing White"));
6257             return FALSE;
6258         }
6259         break;
6260
6261       case PlayFromGameFile:
6262             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6263       case EditGame:
6264         if (!white_piece && WhiteOnMove(currentMove)) {
6265             DisplayMoveError(_("It is White's turn"));
6266             return FALSE;
6267         }
6268         if (white_piece && !WhiteOnMove(currentMove)) {
6269             DisplayMoveError(_("It is Black's turn"));
6270             return FALSE;
6271         }
6272         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6273             /* Editing correspondence game history */
6274             /* Could disallow this or prompt for confirmation */
6275             cmailOldMove = -1;
6276         }
6277         break;
6278
6279       case BeginningOfGame:
6280         if (appData.icsActive) return FALSE;
6281         if (!appData.noChessProgram) {
6282             if (!white_piece) {
6283                 DisplayMoveError(_("You are playing White"));
6284                 return FALSE;
6285             }
6286         }
6287         break;
6288
6289       case Training:
6290         if (!white_piece && WhiteOnMove(currentMove)) {
6291             DisplayMoveError(_("It is White's turn"));
6292             return FALSE;
6293         }
6294         if (white_piece && !WhiteOnMove(currentMove)) {
6295             DisplayMoveError(_("It is Black's turn"));
6296             return FALSE;
6297         }
6298         break;
6299
6300       default:
6301       case IcsExamining:
6302         break;
6303     }
6304     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6305         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6306         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6307         && gameMode != AnalyzeFile && gameMode != Training) {
6308         DisplayMoveError(_("Displayed position is not current"));
6309         return FALSE;
6310     }
6311     return TRUE;
6312 }
6313
6314 Boolean
6315 OnlyMove(int *x, int *y, Boolean captures) {
6316     DisambiguateClosure cl;
6317     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6318     switch(gameMode) {
6319       case MachinePlaysBlack:
6320       case IcsPlayingWhite:
6321       case BeginningOfGame:
6322         if(!WhiteOnMove(currentMove)) return FALSE;
6323         break;
6324       case MachinePlaysWhite:
6325       case IcsPlayingBlack:
6326         if(WhiteOnMove(currentMove)) return FALSE;
6327         break;
6328       case EditGame:
6329         break;
6330       default:
6331         return FALSE;
6332     }
6333     cl.pieceIn = EmptySquare;
6334     cl.rfIn = *y;
6335     cl.ffIn = *x;
6336     cl.rtIn = -1;
6337     cl.ftIn = -1;
6338     cl.promoCharIn = NULLCHAR;
6339     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6340     if( cl.kind == NormalMove ||
6341         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6342         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6343         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6344       fromX = cl.ff;
6345       fromY = cl.rf;
6346       *x = cl.ft;
6347       *y = cl.rt;
6348       return TRUE;
6349     }
6350     if(cl.kind != ImpossibleMove) return FALSE;
6351     cl.pieceIn = EmptySquare;
6352     cl.rfIn = -1;
6353     cl.ffIn = -1;
6354     cl.rtIn = *y;
6355     cl.ftIn = *x;
6356     cl.promoCharIn = NULLCHAR;
6357     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6358     if( cl.kind == NormalMove ||
6359         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6360         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6361         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6362       fromX = cl.ff;
6363       fromY = cl.rf;
6364       *x = cl.ft;
6365       *y = cl.rt;
6366       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6367       return TRUE;
6368     }
6369     return FALSE;
6370 }
6371
6372 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6373 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6374 int lastLoadGameUseList = FALSE;
6375 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6376 ChessMove lastLoadGameStart = EndOfFile;
6377
6378 void
6379 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6380      int fromX, fromY, toX, toY;
6381      int promoChar;
6382 {
6383     ChessMove moveType;
6384     ChessSquare pdown, pup;
6385
6386     /* Check if the user is playing in turn.  This is complicated because we
6387        let the user "pick up" a piece before it is his turn.  So the piece he
6388        tried to pick up may have been captured by the time he puts it down!
6389        Therefore we use the color the user is supposed to be playing in this
6390        test, not the color of the piece that is currently on the starting
6391        square---except in EditGame mode, where the user is playing both
6392        sides; fortunately there the capture race can't happen.  (It can
6393        now happen in IcsExamining mode, but that's just too bad.  The user
6394        will get a somewhat confusing message in that case.)
6395        */
6396
6397     switch (gameMode) {
6398       case AnalyzeFile:
6399       case TwoMachinesPlay:
6400       case EndOfGame:
6401       case IcsObserving:
6402       case IcsIdle:
6403         /* We switched into a game mode where moves are not accepted,
6404            perhaps while the mouse button was down. */
6405         return;
6406
6407       case MachinePlaysWhite:
6408         /* User is moving for Black */
6409         if (WhiteOnMove(currentMove)) {
6410             DisplayMoveError(_("It is White's turn"));
6411             return;
6412         }
6413         break;
6414
6415       case MachinePlaysBlack:
6416         /* User is moving for White */
6417         if (!WhiteOnMove(currentMove)) {
6418             DisplayMoveError(_("It is Black's turn"));
6419             return;
6420         }
6421         break;
6422
6423       case PlayFromGameFile:
6424             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6425       case EditGame:
6426       case IcsExamining:
6427       case BeginningOfGame:
6428       case AnalyzeMode:
6429       case Training:
6430         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6431         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6432             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6433             /* User is moving for Black */
6434             if (WhiteOnMove(currentMove)) {
6435                 DisplayMoveError(_("It is White's turn"));
6436                 return;
6437             }
6438         } else {
6439             /* User is moving for White */
6440             if (!WhiteOnMove(currentMove)) {
6441                 DisplayMoveError(_("It is Black's turn"));
6442                 return;
6443             }
6444         }
6445         break;
6446
6447       case IcsPlayingBlack:
6448         /* User is moving for Black */
6449         if (WhiteOnMove(currentMove)) {
6450             if (!appData.premove) {
6451                 DisplayMoveError(_("It is White's turn"));
6452             } else if (toX >= 0 && toY >= 0) {
6453                 premoveToX = toX;
6454                 premoveToY = toY;
6455                 premoveFromX = fromX;
6456                 premoveFromY = fromY;
6457                 premovePromoChar = promoChar;
6458                 gotPremove = 1;
6459                 if (appData.debugMode)
6460                     fprintf(debugFP, "Got premove: fromX %d,"
6461                             "fromY %d, toX %d, toY %d\n",
6462                             fromX, fromY, toX, toY);
6463             }
6464             return;
6465         }
6466         break;
6467
6468       case IcsPlayingWhite:
6469         /* User is moving for White */
6470         if (!WhiteOnMove(currentMove)) {
6471             if (!appData.premove) {
6472                 DisplayMoveError(_("It is Black's turn"));
6473             } else if (toX >= 0 && toY >= 0) {
6474                 premoveToX = toX;
6475                 premoveToY = toY;
6476                 premoveFromX = fromX;
6477                 premoveFromY = fromY;
6478                 premovePromoChar = promoChar;
6479                 gotPremove = 1;
6480                 if (appData.debugMode)
6481                     fprintf(debugFP, "Got premove: fromX %d,"
6482                             "fromY %d, toX %d, toY %d\n",
6483                             fromX, fromY, toX, toY);
6484             }
6485             return;
6486         }
6487         break;
6488
6489       default:
6490         break;
6491
6492       case EditPosition:
6493         /* EditPosition, empty square, or different color piece;
6494            click-click move is possible */
6495         if (toX == -2 || toY == -2) {
6496             boards[0][fromY][fromX] = EmptySquare;
6497             DrawPosition(FALSE, boards[currentMove]);
6498             return;
6499         } else if (toX >= 0 && toY >= 0) {
6500             boards[0][toY][toX] = boards[0][fromY][fromX];
6501             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6502                 if(boards[0][fromY][0] != EmptySquare) {
6503                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6504                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6505                 }
6506             } else
6507             if(fromX == BOARD_RGHT+1) {
6508                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6509                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6510                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6511                 }
6512             } else
6513             boards[0][fromY][fromX] = EmptySquare;
6514             DrawPosition(FALSE, boards[currentMove]);
6515             return;
6516         }
6517         return;
6518     }
6519
6520     if(toX < 0 || toY < 0) return;
6521     pdown = boards[currentMove][fromY][fromX];
6522     pup = boards[currentMove][toY][toX];
6523
6524     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6525     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6526          if( pup != EmptySquare ) return;
6527          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6528            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6529                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6530            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6531            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6532            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6533            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6534          fromY = DROP_RANK;
6535     }
6536
6537     /* [HGM] always test for legality, to get promotion info */
6538     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6539                                          fromY, fromX, toY, toX, promoChar);
6540
6541     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6542
6543     /* [HGM] but possibly ignore an IllegalMove result */
6544     if (appData.testLegality) {
6545         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6546             DisplayMoveError(_("Illegal move"));
6547             return;
6548         }
6549     }
6550
6551     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6552 }
6553
6554 /* Common tail of UserMoveEvent and DropMenuEvent */
6555 int
6556 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6557      ChessMove moveType;
6558      int fromX, fromY, toX, toY;
6559      /*char*/int promoChar;
6560 {
6561     char *bookHit = 0;
6562
6563     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6564         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6565         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6566         if(WhiteOnMove(currentMove)) {
6567             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6568         } else {
6569             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6570         }
6571     }
6572
6573     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6574        move type in caller when we know the move is a legal promotion */
6575     if(moveType == NormalMove && promoChar)
6576         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6577
6578     /* [HGM] <popupFix> The following if has been moved here from
6579        UserMoveEvent(). Because it seemed to belong here (why not allow
6580        piece drops in training games?), and because it can only be
6581        performed after it is known to what we promote. */
6582     if (gameMode == Training) {
6583       /* compare the move played on the board to the next move in the
6584        * game. If they match, display the move and the opponent's response.
6585        * If they don't match, display an error message.
6586        */
6587       int saveAnimate;
6588       Board testBoard;
6589       CopyBoard(testBoard, boards[currentMove]);
6590       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6591
6592       if (CompareBoards(testBoard, boards[currentMove+1])) {
6593         ForwardInner(currentMove+1);
6594
6595         /* Autoplay the opponent's response.
6596          * if appData.animate was TRUE when Training mode was entered,
6597          * the response will be animated.
6598          */
6599         saveAnimate = appData.animate;
6600         appData.animate = animateTraining;
6601         ForwardInner(currentMove+1);
6602         appData.animate = saveAnimate;
6603
6604         /* check for the end of the game */
6605         if (currentMove >= forwardMostMove) {
6606           gameMode = PlayFromGameFile;
6607           ModeHighlight();
6608           SetTrainingModeOff();
6609           DisplayInformation(_("End of game"));
6610         }
6611       } else {
6612         DisplayError(_("Incorrect move"), 0);
6613       }
6614       return 1;
6615     }
6616
6617   /* Ok, now we know that the move is good, so we can kill
6618      the previous line in Analysis Mode */
6619   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6620                                 && currentMove < forwardMostMove) {
6621     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6622     else forwardMostMove = currentMove;
6623   }
6624
6625   /* If we need the chess program but it's dead, restart it */
6626   ResurrectChessProgram();
6627
6628   /* A user move restarts a paused game*/
6629   if (pausing)
6630     PauseEvent();
6631
6632   thinkOutput[0] = NULLCHAR;
6633
6634   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6635
6636   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6637     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6638     return 1;
6639   }
6640
6641   if (gameMode == BeginningOfGame) {
6642     if (appData.noChessProgram) {
6643       gameMode = EditGame;
6644       SetGameInfo();
6645     } else {
6646       char buf[MSG_SIZ];
6647       gameMode = MachinePlaysBlack;
6648       StartClocks();
6649       SetGameInfo();
6650       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6651       DisplayTitle(buf);
6652       if (first.sendName) {
6653         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6654         SendToProgram(buf, &first);
6655       }
6656       StartClocks();
6657     }
6658     ModeHighlight();
6659   }
6660
6661   /* Relay move to ICS or chess engine */
6662   if (appData.icsActive) {
6663     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6664         gameMode == IcsExamining) {
6665       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6666         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6667         SendToICS("draw ");
6668         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6669       }
6670       // also send plain move, in case ICS does not understand atomic claims
6671       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6672       ics_user_moved = 1;
6673     }
6674   } else {
6675     if (first.sendTime && (gameMode == BeginningOfGame ||
6676                            gameMode == MachinePlaysWhite ||
6677                            gameMode == MachinePlaysBlack)) {
6678       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6679     }
6680     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6681          // [HGM] book: if program might be playing, let it use book
6682         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6683         first.maybeThinking = TRUE;
6684     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6685         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6686         SendBoard(&first, currentMove+1);
6687     } else SendMoveToProgram(forwardMostMove-1, &first);
6688     if (currentMove == cmailOldMove + 1) {
6689       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6690     }
6691   }
6692
6693   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6694
6695   switch (gameMode) {
6696   case EditGame:
6697     if(appData.testLegality)
6698     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6699     case MT_NONE:
6700     case MT_CHECK:
6701       break;
6702     case MT_CHECKMATE:
6703     case MT_STAINMATE:
6704       if (WhiteOnMove(currentMove)) {
6705         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6706       } else {
6707         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6708       }
6709       break;
6710     case MT_STALEMATE:
6711       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6712       break;
6713     }
6714     break;
6715
6716   case MachinePlaysBlack:
6717   case MachinePlaysWhite:
6718     /* disable certain menu options while machine is thinking */
6719     SetMachineThinkingEnables();
6720     break;
6721
6722   default:
6723     break;
6724   }
6725
6726   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6727   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6728
6729   if(bookHit) { // [HGM] book: simulate book reply
6730         static char bookMove[MSG_SIZ]; // a bit generous?
6731
6732         programStats.nodes = programStats.depth = programStats.time =
6733         programStats.score = programStats.got_only_move = 0;
6734         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6735
6736         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6737         strcat(bookMove, bookHit);
6738         HandleMachineMove(bookMove, &first);
6739   }
6740   return 1;
6741 }
6742
6743 void
6744 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6745      Board board;
6746      int flags;
6747      ChessMove kind;
6748      int rf, ff, rt, ft;
6749      VOIDSTAR closure;
6750 {
6751     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6752     Markers *m = (Markers *) closure;
6753     if(rf == fromY && ff == fromX)
6754         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6755                          || kind == WhiteCapturesEnPassant
6756                          || kind == BlackCapturesEnPassant);
6757     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6758 }
6759
6760 void
6761 MarkTargetSquares(int clear)
6762 {
6763   int x, y;
6764   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6765      !appData.testLegality || gameMode == EditPosition) return;
6766   if(clear) {
6767     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6768   } else {
6769     int capt = 0;
6770     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6771     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6772       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6773       if(capt)
6774       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6775     }
6776   }
6777   DrawPosition(TRUE, NULL);
6778 }
6779
6780 int
6781 Explode(Board board, int fromX, int fromY, int toX, int toY)
6782 {
6783     if(gameInfo.variant == VariantAtomic &&
6784        (board[toY][toX] != EmptySquare ||                     // capture?
6785         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6786                          board[fromY][fromX] == BlackPawn   )
6787       )) {
6788         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6789         return TRUE;
6790     }
6791     return FALSE;
6792 }
6793
6794 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6795
6796 int CanPromote(ChessSquare piece, int y)
6797 {
6798         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6799         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6800         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6801            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6802            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6803                                                   gameInfo.variant == VariantMakruk) return FALSE;
6804         return (piece == BlackPawn && y == 1 ||
6805                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6806                 piece == BlackLance && y == 1 ||
6807                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6808 }
6809
6810 void LeftClick(ClickType clickType, int xPix, int yPix)
6811 {
6812     int x, y;
6813     Boolean saveAnimate;
6814     static int second = 0, promotionChoice = 0, clearFlag = 0;
6815     char promoChoice = NULLCHAR;
6816     ChessSquare piece;
6817
6818     if(appData.seekGraph && appData.icsActive && loggedOn &&
6819         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6820         SeekGraphClick(clickType, xPix, yPix, 0);
6821         return;
6822     }
6823
6824     if (clickType == Press) ErrorPopDown();
6825
6826     x = EventToSquare(xPix, BOARD_WIDTH);
6827     y = EventToSquare(yPix, BOARD_HEIGHT);
6828     if (!flipView && y >= 0) {
6829         y = BOARD_HEIGHT - 1 - y;
6830     }
6831     if (flipView && x >= 0) {
6832         x = BOARD_WIDTH - 1 - x;
6833     }
6834
6835     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6836         defaultPromoChoice = promoSweep;
6837         promoSweep = EmptySquare;   // terminate sweep
6838         promoDefaultAltered = TRUE;
6839         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6840     }
6841
6842     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6843         if(clickType == Release) return; // ignore upclick of click-click destination
6844         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6845         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6846         if(gameInfo.holdingsWidth &&
6847                 (WhiteOnMove(currentMove)
6848                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6849                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6850             // click in right holdings, for determining promotion piece
6851             ChessSquare p = boards[currentMove][y][x];
6852             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6853             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6854             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6855                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6856                 fromX = fromY = -1;
6857                 return;
6858             }
6859         }
6860         DrawPosition(FALSE, boards[currentMove]);
6861         return;
6862     }
6863
6864     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6865     if(clickType == Press
6866             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6867               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6868               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6869         return;
6870
6871     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6872         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6873
6874     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6875         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6876                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6877         defaultPromoChoice = DefaultPromoChoice(side);
6878     }
6879
6880     autoQueen = appData.alwaysPromoteToQueen;
6881
6882     if (fromX == -1) {
6883       int originalY = y;
6884       gatingPiece = EmptySquare;
6885       if (clickType != Press) {
6886         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6887             DragPieceEnd(xPix, yPix); dragging = 0;
6888             DrawPosition(FALSE, NULL);
6889         }
6890         return;
6891       }
6892       fromX = x; fromY = y; toX = toY = -1;
6893       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6894          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6895          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6896             /* First square */
6897             if (OKToStartUserMove(fromX, fromY)) {
6898                 second = 0;
6899                 MarkTargetSquares(0);
6900                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6901                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6902                     promoSweep = defaultPromoChoice;
6903                     selectFlag = 0; lastX = xPix; lastY = yPix;
6904                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6905                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6906                 }
6907                 if (appData.highlightDragging) {
6908                     SetHighlights(fromX, fromY, -1, -1);
6909                 }
6910             } else fromX = fromY = -1;
6911             return;
6912         }
6913     }
6914
6915     /* fromX != -1 */
6916     if (clickType == Press && gameMode != EditPosition) {
6917         ChessSquare fromP;
6918         ChessSquare toP;
6919         int frc;
6920
6921         // ignore off-board to clicks
6922         if(y < 0 || x < 0) return;
6923
6924         /* Check if clicking again on the same color piece */
6925         fromP = boards[currentMove][fromY][fromX];
6926         toP = boards[currentMove][y][x];
6927         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6928         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6929              WhitePawn <= toP && toP <= WhiteKing &&
6930              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6931              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6932             (BlackPawn <= fromP && fromP <= BlackKing &&
6933              BlackPawn <= toP && toP <= BlackKing &&
6934              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6935              !(fromP == BlackKing && toP == BlackRook && frc))) {
6936             /* Clicked again on same color piece -- changed his mind */
6937             second = (x == fromX && y == fromY);
6938             promoDefaultAltered = FALSE;
6939             MarkTargetSquares(1);
6940            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6941             if (appData.highlightDragging) {
6942                 SetHighlights(x, y, -1, -1);
6943             } else {
6944                 ClearHighlights();
6945             }
6946             if (OKToStartUserMove(x, y)) {
6947                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6948                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6949                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6950                  gatingPiece = boards[currentMove][fromY][fromX];
6951                 else gatingPiece = EmptySquare;
6952                 fromX = x;
6953                 fromY = y; dragging = 1;
6954                 MarkTargetSquares(0);
6955                 DragPieceBegin(xPix, yPix, FALSE);
6956                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6957                     promoSweep = defaultPromoChoice;
6958                     selectFlag = 0; lastX = xPix; lastY = yPix;
6959                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6960                 }
6961             }
6962            }
6963            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6964            second = FALSE; 
6965         }
6966         // ignore clicks on holdings
6967         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6968     }
6969
6970     if (clickType == Release && x == fromX && y == fromY) {
6971         DragPieceEnd(xPix, yPix); dragging = 0;
6972         if(clearFlag) {
6973             // a deferred attempt to click-click move an empty square on top of a piece
6974             boards[currentMove][y][x] = EmptySquare;
6975             ClearHighlights();
6976             DrawPosition(FALSE, boards[currentMove]);
6977             fromX = fromY = -1; clearFlag = 0;
6978             return;
6979         }
6980         if (appData.animateDragging) {
6981             /* Undo animation damage if any */
6982             DrawPosition(FALSE, NULL);
6983         }
6984         if (second) {
6985             /* Second up/down in same square; just abort move */
6986             second = 0;
6987             fromX = fromY = -1;
6988             gatingPiece = EmptySquare;
6989             ClearHighlights();
6990             gotPremove = 0;
6991             ClearPremoveHighlights();
6992         } else {
6993             /* First upclick in same square; start click-click mode */
6994             SetHighlights(x, y, -1, -1);
6995         }
6996         return;
6997     }
6998
6999     clearFlag = 0;
7000
7001     /* we now have a different from- and (possibly off-board) to-square */
7002     /* Completed move */
7003     toX = x;
7004     toY = y;
7005     saveAnimate = appData.animate;
7006     MarkTargetSquares(1);
7007     if (clickType == Press) {
7008         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7009             // must be Edit Position mode with empty-square selected
7010             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7011             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7012             return;
7013         }
7014         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7015             ChessSquare piece = boards[currentMove][fromY][fromX];
7016             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7017             promoSweep = defaultPromoChoice;
7018             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7019             selectFlag = 0; lastX = xPix; lastY = yPix;
7020             Sweep(0); // Pawn that is going to promote: preview promotion piece
7021             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7022             DrawPosition(FALSE, boards[currentMove]);
7023             return;
7024         }
7025         /* Finish clickclick move */
7026         if (appData.animate || appData.highlightLastMove) {
7027             SetHighlights(fromX, fromY, toX, toY);
7028         } else {
7029             ClearHighlights();
7030         }
7031     } else {
7032         /* Finish drag move */
7033         if (appData.highlightLastMove) {
7034             SetHighlights(fromX, fromY, toX, toY);
7035         } else {
7036             ClearHighlights();
7037         }
7038         DragPieceEnd(xPix, yPix); dragging = 0;
7039         /* Don't animate move and drag both */
7040         appData.animate = FALSE;
7041     }
7042
7043     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7044     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7045         ChessSquare piece = boards[currentMove][fromY][fromX];
7046         if(gameMode == EditPosition && piece != EmptySquare &&
7047            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7048             int n;
7049
7050             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7051                 n = PieceToNumber(piece - (int)BlackPawn);
7052                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7053                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7054                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7055             } else
7056             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7057                 n = PieceToNumber(piece);
7058                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7059                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7060                 boards[currentMove][n][BOARD_WIDTH-2]++;
7061             }
7062             boards[currentMove][fromY][fromX] = EmptySquare;
7063         }
7064         ClearHighlights();
7065         fromX = fromY = -1;
7066         DrawPosition(TRUE, boards[currentMove]);
7067         return;
7068     }
7069
7070     // off-board moves should not be highlighted
7071     if(x < 0 || y < 0) ClearHighlights();
7072
7073     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7074
7075     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7076         SetHighlights(fromX, fromY, toX, toY);
7077         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7078             // [HGM] super: promotion to captured piece selected from holdings
7079             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7080             promotionChoice = TRUE;
7081             // kludge follows to temporarily execute move on display, without promoting yet
7082             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7083             boards[currentMove][toY][toX] = p;
7084             DrawPosition(FALSE, boards[currentMove]);
7085             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7086             boards[currentMove][toY][toX] = q;
7087             DisplayMessage("Click in holdings to choose piece", "");
7088             return;
7089         }
7090         PromotionPopUp();
7091     } else {
7092         int oldMove = currentMove;
7093         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7094         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7095         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7096         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7097            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7098             DrawPosition(TRUE, boards[currentMove]);
7099         fromX = fromY = -1;
7100     }
7101     appData.animate = saveAnimate;
7102     if (appData.animate || appData.animateDragging) {
7103         /* Undo animation damage if needed */
7104         DrawPosition(FALSE, NULL);
7105     }
7106 }
7107
7108 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7109 {   // front-end-free part taken out of PieceMenuPopup
7110     int whichMenu; int xSqr, ySqr;
7111
7112     if(seekGraphUp) { // [HGM] seekgraph
7113         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7114         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7115         return -2;
7116     }
7117
7118     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7119          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7120         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7121         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7122         if(action == Press)   {
7123             originalFlip = flipView;
7124             flipView = !flipView; // temporarily flip board to see game from partners perspective
7125             DrawPosition(TRUE, partnerBoard);
7126             DisplayMessage(partnerStatus, "");
7127             partnerUp = TRUE;
7128         } else if(action == Release) {
7129             flipView = originalFlip;
7130             DrawPosition(TRUE, boards[currentMove]);
7131             partnerUp = FALSE;
7132         }
7133         return -2;
7134     }
7135
7136     xSqr = EventToSquare(x, BOARD_WIDTH);
7137     ySqr = EventToSquare(y, BOARD_HEIGHT);
7138     if (action == Release) {
7139         if(pieceSweep != EmptySquare) {
7140             EditPositionMenuEvent(pieceSweep, toX, toY);
7141             pieceSweep = EmptySquare;
7142         } else UnLoadPV(); // [HGM] pv
7143     }
7144     if (action != Press) return -2; // return code to be ignored
7145     switch (gameMode) {
7146       case IcsExamining:
7147         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7148       case EditPosition:
7149         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7150         if (xSqr < 0 || ySqr < 0) return -1;
7151         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7152         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7153         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7154         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7155         NextPiece(0);
7156         return 2; // grab
7157       case IcsObserving:
7158         if(!appData.icsEngineAnalyze) return -1;
7159       case IcsPlayingWhite:
7160       case IcsPlayingBlack:
7161         if(!appData.zippyPlay) goto noZip;
7162       case AnalyzeMode:
7163       case AnalyzeFile:
7164       case MachinePlaysWhite:
7165       case MachinePlaysBlack:
7166       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7167         if (!appData.dropMenu) {
7168           LoadPV(x, y);
7169           return 2; // flag front-end to grab mouse events
7170         }
7171         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7172            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7173       case EditGame:
7174       noZip:
7175         if (xSqr < 0 || ySqr < 0) return -1;
7176         if (!appData.dropMenu || appData.testLegality &&
7177             gameInfo.variant != VariantBughouse &&
7178             gameInfo.variant != VariantCrazyhouse) return -1;
7179         whichMenu = 1; // drop menu
7180         break;
7181       default:
7182         return -1;
7183     }
7184
7185     if (((*fromX = xSqr) < 0) ||
7186         ((*fromY = ySqr) < 0)) {
7187         *fromX = *fromY = -1;
7188         return -1;
7189     }
7190     if (flipView)
7191       *fromX = BOARD_WIDTH - 1 - *fromX;
7192     else
7193       *fromY = BOARD_HEIGHT - 1 - *fromY;
7194
7195     return whichMenu;
7196 }
7197
7198 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7199 {
7200 //    char * hint = lastHint;
7201     FrontEndProgramStats stats;
7202
7203     stats.which = cps == &first ? 0 : 1;
7204     stats.depth = cpstats->depth;
7205     stats.nodes = cpstats->nodes;
7206     stats.score = cpstats->score;
7207     stats.time = cpstats->time;
7208     stats.pv = cpstats->movelist;
7209     stats.hint = lastHint;
7210     stats.an_move_index = 0;
7211     stats.an_move_count = 0;
7212
7213     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7214         stats.hint = cpstats->move_name;
7215         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7216         stats.an_move_count = cpstats->nr_moves;
7217     }
7218
7219     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7220
7221     SetProgramStats( &stats );
7222 }
7223
7224 void
7225 ClearEngineOutputPane(int which)
7226 {
7227     static FrontEndProgramStats dummyStats;
7228     dummyStats.which = which;
7229     dummyStats.pv = "#";
7230     SetProgramStats( &dummyStats );
7231 }
7232
7233 #define MAXPLAYERS 500
7234
7235 char *
7236 TourneyStandings(int display)
7237 {
7238     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7239     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7240     char result, *p, *names[MAXPLAYERS];
7241
7242     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7243         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7244     names[0] = p = strdup(appData.participants);
7245     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7246
7247     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7248
7249     while(result = appData.results[nr]) {
7250         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7251         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7252         wScore = bScore = 0;
7253         switch(result) {
7254           case '+': wScore = 2; break;
7255           case '-': bScore = 2; break;
7256           case '=': wScore = bScore = 1; break;
7257           case ' ':
7258           case '*': return strdup("busy"); // tourney not finished
7259         }
7260         score[w] += wScore;
7261         score[b] += bScore;
7262         games[w]++;
7263         games[b]++;
7264         nr++;
7265     }
7266     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7267     for(w=0; w<nPlayers; w++) {
7268         bScore = -1;
7269         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7270         ranking[w] = b; points[w] = bScore; score[b] = -2;
7271     }
7272     p = malloc(nPlayers*34+1);
7273     for(w=0; w<nPlayers && w<display; w++)
7274         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7275     free(names[0]);
7276     return p;
7277 }
7278
7279 void
7280 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7281 {       // count all piece types
7282         int p, f, r;
7283         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7284         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7285         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7286                 p = board[r][f];
7287                 pCnt[p]++;
7288                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7289                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7290                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7291                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7292                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7293                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7294         }
7295 }
7296
7297 int
7298 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7299 {
7300         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7301         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7302
7303         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7304         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7305         if(myPawns == 2 && nMine == 3) // KPP
7306             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7307         if(myPawns == 1 && nMine == 2) // KP
7308             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7309         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7310             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7311         if(myPawns) return FALSE;
7312         if(pCnt[WhiteRook+side])
7313             return pCnt[BlackRook-side] ||
7314                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7315                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7316                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7317         if(pCnt[WhiteCannon+side]) {
7318             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7319             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7320         }
7321         if(pCnt[WhiteKnight+side])
7322             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7323         return FALSE;
7324 }
7325
7326 int
7327 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7328 {
7329         VariantClass v = gameInfo.variant;
7330
7331         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7332         if(v == VariantShatranj) return TRUE; // always winnable through baring
7333         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7334         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7335
7336         if(v == VariantXiangqi) {
7337                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7338
7339                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7340                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7341                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7342                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7343                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7344                 if(stale) // we have at least one last-rank P plus perhaps C
7345                     return majors // KPKX
7346                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7347                 else // KCA*E*
7348                     return pCnt[WhiteFerz+side] // KCAK
7349                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7350                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7351                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7352
7353         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7354                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7355
7356                 if(nMine == 1) return FALSE; // bare King
7357                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
7358                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7359                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7360                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7361                 if(pCnt[WhiteKnight+side])
7362                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7363                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7364                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7365                 if(nBishops)
7366                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7367                 if(pCnt[WhiteAlfil+side])
7368                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7369                 if(pCnt[WhiteWazir+side])
7370                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7371         }
7372
7373         return TRUE;
7374 }
7375
7376 int
7377 CompareWithRights(Board b1, Board b2)
7378 {
7379     int rights = 0;
7380     if(!CompareBoards(b1, b2)) return FALSE;
7381     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7382     /* compare castling rights */
7383     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7384            rights++; /* King lost rights, while rook still had them */
7385     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7386         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7387            rights++; /* but at least one rook lost them */
7388     }
7389     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7390            rights++;
7391     if( b1[CASTLING][5] != NoRights ) {
7392         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7393            rights++;
7394     }
7395     return rights == 0;
7396 }
7397
7398 int
7399 Adjudicate(ChessProgramState *cps)
7400 {       // [HGM] some adjudications useful with buggy engines
7401         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7402         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7403         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7404         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7405         int k, count = 0; static int bare = 1;
7406         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7407         Boolean canAdjudicate = !appData.icsActive;
7408
7409         // most tests only when we understand the game, i.e. legality-checking on
7410             if( appData.testLegality )
7411             {   /* [HGM] Some more adjudications for obstinate engines */
7412                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7413                 static int moveCount = 6;
7414                 ChessMove result;
7415                 char *reason = NULL;
7416
7417                 /* Count what is on board. */
7418                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7419
7420                 /* Some material-based adjudications that have to be made before stalemate test */
7421                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7422                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7423                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7424                      if(canAdjudicate && appData.checkMates) {
7425                          if(engineOpponent)
7426                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7427                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7428                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7429                          return 1;
7430                      }
7431                 }
7432
7433                 /* Bare King in Shatranj (loses) or Losers (wins) */
7434                 if( nrW == 1 || nrB == 1) {
7435                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7436                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7437                      if(canAdjudicate && appData.checkMates) {
7438                          if(engineOpponent)
7439                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7440                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7441                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7442                          return 1;
7443                      }
7444                   } else
7445                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7446                   {    /* bare King */
7447                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7448                         if(canAdjudicate && appData.checkMates) {
7449                             /* but only adjudicate if adjudication enabled */
7450                             if(engineOpponent)
7451                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7452                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7453                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7454                             return 1;
7455                         }
7456                   }
7457                 } else bare = 1;
7458
7459
7460             // don't wait for engine to announce game end if we can judge ourselves
7461             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7462               case MT_CHECK:
7463                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7464                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7465                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7466                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7467                             checkCnt++;
7468                         if(checkCnt >= 2) {
7469                             reason = "Xboard adjudication: 3rd check";
7470                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7471                             break;
7472                         }
7473                     }
7474                 }
7475               case MT_NONE:
7476               default:
7477                 break;
7478               case MT_STALEMATE:
7479               case MT_STAINMATE:
7480                 reason = "Xboard adjudication: Stalemate";
7481                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7482                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7483                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7484                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7485                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7486                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7487                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7488                                                                         EP_CHECKMATE : EP_WINS);
7489                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7490                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7491                 }
7492                 break;
7493               case MT_CHECKMATE:
7494                 reason = "Xboard adjudication: Checkmate";
7495                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7496                 break;
7497             }
7498
7499                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7500                     case EP_STALEMATE:
7501                         result = GameIsDrawn; break;
7502                     case EP_CHECKMATE:
7503                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7504                     case EP_WINS:
7505                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7506                     default:
7507                         result = EndOfFile;
7508                 }
7509                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7510                     if(engineOpponent)
7511                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7512                     GameEnds( result, reason, GE_XBOARD );
7513                     return 1;
7514                 }
7515
7516                 /* Next absolutely insufficient mating material. */
7517                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7518                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7519                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7520
7521                      /* always flag draws, for judging claims */
7522                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7523
7524                      if(canAdjudicate && appData.materialDraws) {
7525                          /* but only adjudicate them if adjudication enabled */
7526                          if(engineOpponent) {
7527                            SendToProgram("force\n", engineOpponent); // suppress reply
7528                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7529                          }
7530                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7531                          return 1;
7532                      }
7533                 }
7534
7535                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7536                 if(gameInfo.variant == VariantXiangqi ?
7537                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7538                  : nrW + nrB == 4 &&
7539                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7540                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7541                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7542                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7543                    ) ) {
7544                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7545                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7546                           if(engineOpponent) {
7547                             SendToProgram("force\n", engineOpponent); // suppress reply
7548                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7549                           }
7550                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7551                           return 1;
7552                      }
7553                 } else moveCount = 6;
7554             }
7555         if (appData.debugMode) { int i;
7556             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7557                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7558                     appData.drawRepeats);
7559             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7560               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7561
7562         }
7563
7564         // Repetition draws and 50-move rule can be applied independently of legality testing
7565
7566                 /* Check for rep-draws */
7567                 count = 0;
7568                 for(k = forwardMostMove-2;
7569                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7570                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7571                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7572                     k-=2)
7573                 {   int rights=0;
7574                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7575                         /* compare castling rights */
7576                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7577                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7578                                 rights++; /* King lost rights, while rook still had them */
7579                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7580                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7581                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7582                                    rights++; /* but at least one rook lost them */
7583                         }
7584                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7585                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7586                                 rights++;
7587                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7588                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7589                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7590                                    rights++;
7591                         }
7592                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7593                             && appData.drawRepeats > 1) {
7594                              /* adjudicate after user-specified nr of repeats */
7595                              int result = GameIsDrawn;
7596                              char *details = "XBoard adjudication: repetition draw";
7597                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7598                                 // [HGM] xiangqi: check for forbidden perpetuals
7599                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7600                                 for(m=forwardMostMove; m>k; m-=2) {
7601                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7602                                         ourPerpetual = 0; // the current mover did not always check
7603                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7604                                         hisPerpetual = 0; // the opponent did not always check
7605                                 }
7606                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7607                                                                         ourPerpetual, hisPerpetual);
7608                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7609                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7610                                     details = "Xboard adjudication: perpetual checking";
7611                                 } else
7612                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7613                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7614                                 } else
7615                                 // Now check for perpetual chases
7616                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7617                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7618                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7619                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7620                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7621                                         details = "Xboard adjudication: perpetual chasing";
7622                                     } else
7623                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7624                                         break; // Abort repetition-checking loop.
7625                                 }
7626                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7627                              }
7628                              if(engineOpponent) {
7629                                SendToProgram("force\n", engineOpponent); // suppress reply
7630                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7631                              }
7632                              GameEnds( result, details, GE_XBOARD );
7633                              return 1;
7634                         }
7635                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7636                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7637                     }
7638                 }
7639
7640                 /* Now we test for 50-move draws. Determine ply count */
7641                 count = forwardMostMove;
7642                 /* look for last irreversble move */
7643                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7644                     count--;
7645                 /* if we hit starting position, add initial plies */
7646                 if( count == backwardMostMove )
7647                     count -= initialRulePlies;
7648                 count = forwardMostMove - count;
7649                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7650                         // adjust reversible move counter for checks in Xiangqi
7651                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7652                         if(i < backwardMostMove) i = backwardMostMove;
7653                         while(i <= forwardMostMove) {
7654                                 lastCheck = inCheck; // check evasion does not count
7655                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7656                                 if(inCheck || lastCheck) count--; // check does not count
7657                                 i++;
7658                         }
7659                 }
7660                 if( count >= 100)
7661                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7662                          /* this is used to judge if draw claims are legal */
7663                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7664                          if(engineOpponent) {
7665                            SendToProgram("force\n", engineOpponent); // suppress reply
7666                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7667                          }
7668                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7669                          return 1;
7670                 }
7671
7672                 /* if draw offer is pending, treat it as a draw claim
7673                  * when draw condition present, to allow engines a way to
7674                  * claim draws before making their move to avoid a race
7675                  * condition occurring after their move
7676                  */
7677                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7678                          char *p = NULL;
7679                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7680                              p = "Draw claim: 50-move rule";
7681                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7682                              p = "Draw claim: 3-fold repetition";
7683                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7684                              p = "Draw claim: insufficient mating material";
7685                          if( p != NULL && canAdjudicate) {
7686                              if(engineOpponent) {
7687                                SendToProgram("force\n", engineOpponent); // suppress reply
7688                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7689                              }
7690                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7691                              return 1;
7692                          }
7693                 }
7694
7695                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7696                     if(engineOpponent) {
7697                       SendToProgram("force\n", engineOpponent); // suppress reply
7698                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7699                     }
7700                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7701                     return 1;
7702                 }
7703         return 0;
7704 }
7705
7706 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7707 {   // [HGM] book: this routine intercepts moves to simulate book replies
7708     char *bookHit = NULL;
7709
7710     //first determine if the incoming move brings opponent into his book
7711     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7712         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7713     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7714     if(bookHit != NULL && !cps->bookSuspend) {
7715         // make sure opponent is not going to reply after receiving move to book position
7716         SendToProgram("force\n", cps);
7717         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7718     }
7719     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7720     // now arrange restart after book miss
7721     if(bookHit) {
7722         // after a book hit we never send 'go', and the code after the call to this routine
7723         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7724         char buf[MSG_SIZ], *move = bookHit;
7725         if(cps->useSAN) {
7726             int fromX, fromY, toX, toY;
7727             char promoChar;
7728             ChessMove moveType;
7729             move = buf + 30;
7730             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7731                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7732                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7733                                     PosFlags(forwardMostMove),
7734                                     fromY, fromX, toY, toX, promoChar, move);
7735             } else {
7736                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7737                 bookHit = NULL;
7738             }
7739         }
7740         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7741         SendToProgram(buf, cps);
7742         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7743     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7744         SendToProgram("go\n", cps);
7745         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7746     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7747         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7748             SendToProgram("go\n", cps);
7749         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7750     }
7751     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7752 }
7753
7754 char *savedMessage;
7755 ChessProgramState *savedState;
7756 void DeferredBookMove(void)
7757 {
7758         if(savedState->lastPing != savedState->lastPong)
7759                     ScheduleDelayedEvent(DeferredBookMove, 10);
7760         else
7761         HandleMachineMove(savedMessage, savedState);
7762 }
7763
7764 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7765
7766 void
7767 HandleMachineMove(message, cps)
7768      char *message;
7769      ChessProgramState *cps;
7770 {
7771     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7772     char realname[MSG_SIZ];
7773     int fromX, fromY, toX, toY;
7774     ChessMove moveType;
7775     char promoChar;
7776     char *p, *pv=buf1;
7777     int machineWhite;
7778     char *bookHit;
7779
7780     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7781         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7782         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7783             DisplayError(_("Invalid pairing from pairing engine"), 0);
7784             return;
7785         }
7786         pairingReceived = 1;
7787         NextMatchGame();
7788         return; // Skim the pairing messages here.
7789     }
7790
7791     cps->userError = 0;
7792
7793 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7794     /*
7795      * Kludge to ignore BEL characters
7796      */
7797     while (*message == '\007') message++;
7798
7799     /*
7800      * [HGM] engine debug message: ignore lines starting with '#' character
7801      */
7802     if(cps->debug && *message == '#') return;
7803
7804     /*
7805      * Look for book output
7806      */
7807     if (cps == &first && bookRequested) {
7808         if (message[0] == '\t' || message[0] == ' ') {
7809             /* Part of the book output is here; append it */
7810             strcat(bookOutput, message);
7811             strcat(bookOutput, "  \n");
7812             return;
7813         } else if (bookOutput[0] != NULLCHAR) {
7814             /* All of book output has arrived; display it */
7815             char *p = bookOutput;
7816             while (*p != NULLCHAR) {
7817                 if (*p == '\t') *p = ' ';
7818                 p++;
7819             }
7820             DisplayInformation(bookOutput);
7821             bookRequested = FALSE;
7822             /* Fall through to parse the current output */
7823         }
7824     }
7825
7826     /*
7827      * Look for machine move.
7828      */
7829     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7830         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7831     {
7832         /* This method is only useful on engines that support ping */
7833         if (cps->lastPing != cps->lastPong) {
7834           if (gameMode == BeginningOfGame) {
7835             /* Extra move from before last new; ignore */
7836             if (appData.debugMode) {
7837                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7838             }
7839           } else {
7840             if (appData.debugMode) {
7841                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7842                         cps->which, gameMode);
7843             }
7844
7845             SendToProgram("undo\n", cps);
7846           }
7847           return;
7848         }
7849
7850         switch (gameMode) {
7851           case BeginningOfGame:
7852             /* Extra move from before last reset; ignore */
7853             if (appData.debugMode) {
7854                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7855             }
7856             return;
7857
7858           case EndOfGame:
7859           case IcsIdle:
7860           default:
7861             /* Extra move after we tried to stop.  The mode test is
7862                not a reliable way of detecting this problem, but it's
7863                the best we can do on engines that don't support ping.
7864             */
7865             if (appData.debugMode) {
7866                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7867                         cps->which, gameMode);
7868             }
7869             SendToProgram("undo\n", cps);
7870             return;
7871
7872           case MachinePlaysWhite:
7873           case IcsPlayingWhite:
7874             machineWhite = TRUE;
7875             break;
7876
7877           case MachinePlaysBlack:
7878           case IcsPlayingBlack:
7879             machineWhite = FALSE;
7880             break;
7881
7882           case TwoMachinesPlay:
7883             machineWhite = (cps->twoMachinesColor[0] == 'w');
7884             break;
7885         }
7886         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7887             if (appData.debugMode) {
7888                 fprintf(debugFP,
7889                         "Ignoring move out of turn by %s, gameMode %d"
7890                         ", forwardMost %d\n",
7891                         cps->which, gameMode, forwardMostMove);
7892             }
7893             return;
7894         }
7895
7896     if (appData.debugMode) { int f = forwardMostMove;
7897         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7898                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7899                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7900     }
7901         if(cps->alphaRank) AlphaRank(machineMove, 4);
7902         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7903                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7904             /* Machine move could not be parsed; ignore it. */
7905           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7906                     machineMove, _(cps->which));
7907             DisplayError(buf1, 0);
7908             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7909                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7910             if (gameMode == TwoMachinesPlay) {
7911               GameEnds(machineWhite ? BlackWins : WhiteWins,
7912                        buf1, GE_XBOARD);
7913             }
7914             return;
7915         }
7916
7917         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7918         /* So we have to redo legality test with true e.p. status here,  */
7919         /* to make sure an illegal e.p. capture does not slip through,   */
7920         /* to cause a forfeit on a justified illegal-move complaint      */
7921         /* of the opponent.                                              */
7922         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7923            ChessMove moveType;
7924            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7925                              fromY, fromX, toY, toX, promoChar);
7926             if (appData.debugMode) {
7927                 int i;
7928                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7929                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7930                 fprintf(debugFP, "castling rights\n");
7931             }
7932             if(moveType == IllegalMove) {
7933               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7934                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7935                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7936                            buf1, GE_XBOARD);
7937                 return;
7938            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7939            /* [HGM] Kludge to handle engines that send FRC-style castling
7940               when they shouldn't (like TSCP-Gothic) */
7941            switch(moveType) {
7942              case WhiteASideCastleFR:
7943              case BlackASideCastleFR:
7944                toX+=2;
7945                currentMoveString[2]++;
7946                break;
7947              case WhiteHSideCastleFR:
7948              case BlackHSideCastleFR:
7949                toX--;
7950                currentMoveString[2]--;
7951                break;
7952              default: ; // nothing to do, but suppresses warning of pedantic compilers
7953            }
7954         }
7955         hintRequested = FALSE;
7956         lastHint[0] = NULLCHAR;
7957         bookRequested = FALSE;
7958         /* Program may be pondering now */
7959         cps->maybeThinking = TRUE;
7960         if (cps->sendTime == 2) cps->sendTime = 1;
7961         if (cps->offeredDraw) cps->offeredDraw--;
7962
7963         /* [AS] Save move info*/
7964         pvInfoList[ forwardMostMove ].score = programStats.score;
7965         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7966         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7967
7968         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7969
7970         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7971         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7972             int count = 0;
7973
7974             while( count < adjudicateLossPlies ) {
7975                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7976
7977                 if( count & 1 ) {
7978                     score = -score; /* Flip score for winning side */
7979                 }
7980
7981                 if( score > adjudicateLossThreshold ) {
7982                     break;
7983                 }
7984
7985                 count++;
7986             }
7987
7988             if( count >= adjudicateLossPlies ) {
7989                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7990
7991                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7992                     "Xboard adjudication",
7993                     GE_XBOARD );
7994
7995                 return;
7996             }
7997         }
7998
7999         if(Adjudicate(cps)) {
8000             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8001             return; // [HGM] adjudicate: for all automatic game ends
8002         }
8003
8004 #if ZIPPY
8005         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8006             first.initDone) {
8007           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8008                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8009                 SendToICS("draw ");
8010                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8011           }
8012           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8013           ics_user_moved = 1;
8014           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8015                 char buf[3*MSG_SIZ];
8016
8017                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8018                         programStats.score / 100.,
8019                         programStats.depth,
8020                         programStats.time / 100.,
8021                         (unsigned int)programStats.nodes,
8022                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8023                         programStats.movelist);
8024                 SendToICS(buf);
8025 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8026           }
8027         }
8028 #endif
8029
8030         /* [AS] Clear stats for next move */
8031         ClearProgramStats();
8032         thinkOutput[0] = NULLCHAR;
8033         hiddenThinkOutputState = 0;
8034
8035         bookHit = NULL;
8036         if (gameMode == TwoMachinesPlay) {
8037             /* [HGM] relaying draw offers moved to after reception of move */
8038             /* and interpreting offer as claim if it brings draw condition */
8039             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8040                 SendToProgram("draw\n", cps->other);
8041             }
8042             if (cps->other->sendTime) {
8043                 SendTimeRemaining(cps->other,
8044                                   cps->other->twoMachinesColor[0] == 'w');
8045             }
8046             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8047             if (firstMove && !bookHit) {
8048                 firstMove = FALSE;
8049                 if (cps->other->useColors) {
8050                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8051                 }
8052                 SendToProgram("go\n", cps->other);
8053             }
8054             cps->other->maybeThinking = TRUE;
8055         }
8056
8057         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8058
8059         if (!pausing && appData.ringBellAfterMoves) {
8060             RingBell();
8061         }
8062
8063         /*
8064          * Reenable menu items that were disabled while
8065          * machine was thinking
8066          */
8067         if (gameMode != TwoMachinesPlay)
8068             SetUserThinkingEnables();
8069
8070         // [HGM] book: after book hit opponent has received move and is now in force mode
8071         // force the book reply into it, and then fake that it outputted this move by jumping
8072         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8073         if(bookHit) {
8074                 static char bookMove[MSG_SIZ]; // a bit generous?
8075
8076                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8077                 strcat(bookMove, bookHit);
8078                 message = bookMove;
8079                 cps = cps->other;
8080                 programStats.nodes = programStats.depth = programStats.time =
8081                 programStats.score = programStats.got_only_move = 0;
8082                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8083
8084                 if(cps->lastPing != cps->lastPong) {
8085                     savedMessage = message; // args for deferred call
8086                     savedState = cps;
8087                     ScheduleDelayedEvent(DeferredBookMove, 10);
8088                     return;
8089                 }
8090                 goto FakeBookMove;
8091         }
8092
8093         return;
8094     }
8095
8096     /* Set special modes for chess engines.  Later something general
8097      *  could be added here; for now there is just one kludge feature,
8098      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8099      *  when "xboard" is given as an interactive command.
8100      */
8101     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8102         cps->useSigint = FALSE;
8103         cps->useSigterm = FALSE;
8104     }
8105     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8106       ParseFeatures(message+8, cps);
8107       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8108     }
8109
8110     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8111       int dummy, s=6; char buf[MSG_SIZ];
8112       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8113       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8114       ParseFEN(boards[0], &dummy, message+s);
8115       DrawPosition(TRUE, boards[0]);
8116       startedFromSetupPosition = TRUE;
8117       return;
8118     }
8119     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8120      * want this, I was asked to put it in, and obliged.
8121      */
8122     if (!strncmp(message, "setboard ", 9)) {
8123         Board initial_position;
8124
8125         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8126
8127         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8128             DisplayError(_("Bad FEN received from engine"), 0);
8129             return ;
8130         } else {
8131            Reset(TRUE, FALSE);
8132            CopyBoard(boards[0], initial_position);
8133            initialRulePlies = FENrulePlies;
8134            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8135            else gameMode = MachinePlaysBlack;
8136            DrawPosition(FALSE, boards[currentMove]);
8137         }
8138         return;
8139     }
8140
8141     /*
8142      * Look for communication commands
8143      */
8144     if (!strncmp(message, "telluser ", 9)) {
8145         if(message[9] == '\\' && message[10] == '\\')
8146             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8147         PlayTellSound();
8148         DisplayNote(message + 9);
8149         return;
8150     }
8151     if (!strncmp(message, "tellusererror ", 14)) {
8152         cps->userError = 1;
8153         if(message[14] == '\\' && message[15] == '\\')
8154             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8155         PlayTellSound();
8156         DisplayError(message + 14, 0);
8157         return;
8158     }
8159     if (!strncmp(message, "tellopponent ", 13)) {
8160       if (appData.icsActive) {
8161         if (loggedOn) {
8162           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8163           SendToICS(buf1);
8164         }
8165       } else {
8166         DisplayNote(message + 13);
8167       }
8168       return;
8169     }
8170     if (!strncmp(message, "tellothers ", 11)) {
8171       if (appData.icsActive) {
8172         if (loggedOn) {
8173           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8174           SendToICS(buf1);
8175         }
8176       }
8177       return;
8178     }
8179     if (!strncmp(message, "tellall ", 8)) {
8180       if (appData.icsActive) {
8181         if (loggedOn) {
8182           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8183           SendToICS(buf1);
8184         }
8185       } else {
8186         DisplayNote(message + 8);
8187       }
8188       return;
8189     }
8190     if (strncmp(message, "warning", 7) == 0) {
8191         /* Undocumented feature, use tellusererror in new code */
8192         DisplayError(message, 0);
8193         return;
8194     }
8195     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8196         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8197         strcat(realname, " query");
8198         AskQuestion(realname, buf2, buf1, cps->pr);
8199         return;
8200     }
8201     /* Commands from the engine directly to ICS.  We don't allow these to be
8202      *  sent until we are logged on. Crafty kibitzes have been known to
8203      *  interfere with the login process.
8204      */
8205     if (loggedOn) {
8206         if (!strncmp(message, "tellics ", 8)) {
8207             SendToICS(message + 8);
8208             SendToICS("\n");
8209             return;
8210         }
8211         if (!strncmp(message, "tellicsnoalias ", 15)) {
8212             SendToICS(ics_prefix);
8213             SendToICS(message + 15);
8214             SendToICS("\n");
8215             return;
8216         }
8217         /* The following are for backward compatibility only */
8218         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8219             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8220             SendToICS(ics_prefix);
8221             SendToICS(message);
8222             SendToICS("\n");
8223             return;
8224         }
8225     }
8226     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8227         return;
8228     }
8229     /*
8230      * If the move is illegal, cancel it and redraw the board.
8231      * Also deal with other error cases.  Matching is rather loose
8232      * here to accommodate engines written before the spec.
8233      */
8234     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8235         strncmp(message, "Error", 5) == 0) {
8236         if (StrStr(message, "name") ||
8237             StrStr(message, "rating") || StrStr(message, "?") ||
8238             StrStr(message, "result") || StrStr(message, "board") ||
8239             StrStr(message, "bk") || StrStr(message, "computer") ||
8240             StrStr(message, "variant") || StrStr(message, "hint") ||
8241             StrStr(message, "random") || StrStr(message, "depth") ||
8242             StrStr(message, "accepted")) {
8243             return;
8244         }
8245         if (StrStr(message, "protover")) {
8246           /* Program is responding to input, so it's apparently done
8247              initializing, and this error message indicates it is
8248              protocol version 1.  So we don't need to wait any longer
8249              for it to initialize and send feature commands. */
8250           FeatureDone(cps, 1);
8251           cps->protocolVersion = 1;
8252           return;
8253         }
8254         cps->maybeThinking = FALSE;
8255
8256         if (StrStr(message, "draw")) {
8257             /* Program doesn't have "draw" command */
8258             cps->sendDrawOffers = 0;
8259             return;
8260         }
8261         if (cps->sendTime != 1 &&
8262             (StrStr(message, "time") || StrStr(message, "otim"))) {
8263           /* Program apparently doesn't have "time" or "otim" command */
8264           cps->sendTime = 0;
8265           return;
8266         }
8267         if (StrStr(message, "analyze")) {
8268             cps->analysisSupport = FALSE;
8269             cps->analyzing = FALSE;
8270             Reset(FALSE, TRUE);
8271             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8272             DisplayError(buf2, 0);
8273             return;
8274         }
8275         if (StrStr(message, "(no matching move)st")) {
8276           /* Special kludge for GNU Chess 4 only */
8277           cps->stKludge = TRUE;
8278           SendTimeControl(cps, movesPerSession, timeControl,
8279                           timeIncrement, appData.searchDepth,
8280                           searchTime);
8281           return;
8282         }
8283         if (StrStr(message, "(no matching move)sd")) {
8284           /* Special kludge for GNU Chess 4 only */
8285           cps->sdKludge = TRUE;
8286           SendTimeControl(cps, movesPerSession, timeControl,
8287                           timeIncrement, appData.searchDepth,
8288                           searchTime);
8289           return;
8290         }
8291         if (!StrStr(message, "llegal")) {
8292             return;
8293         }
8294         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8295             gameMode == IcsIdle) return;
8296         if (forwardMostMove <= backwardMostMove) return;
8297         if (pausing) PauseEvent();
8298       if(appData.forceIllegal) {
8299             // [HGM] illegal: machine refused move; force position after move into it
8300           SendToProgram("force\n", cps);
8301           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8302                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8303                 // when black is to move, while there might be nothing on a2 or black
8304                 // might already have the move. So send the board as if white has the move.
8305                 // But first we must change the stm of the engine, as it refused the last move
8306                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8307                 if(WhiteOnMove(forwardMostMove)) {
8308                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8309                     SendBoard(cps, forwardMostMove); // kludgeless board
8310                 } else {
8311                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8312                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8313                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8314                 }
8315           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8316             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8317                  gameMode == TwoMachinesPlay)
8318               SendToProgram("go\n", cps);
8319             return;
8320       } else
8321         if (gameMode == PlayFromGameFile) {
8322             /* Stop reading this game file */
8323             gameMode = EditGame;
8324             ModeHighlight();
8325         }
8326         /* [HGM] illegal-move claim should forfeit game when Xboard */
8327         /* only passes fully legal moves                            */
8328         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8329             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8330                                 "False illegal-move claim", GE_XBOARD );
8331             return; // do not take back move we tested as valid
8332         }
8333         currentMove = forwardMostMove-1;
8334         DisplayMove(currentMove-1); /* before DisplayMoveError */
8335         SwitchClocks(forwardMostMove-1); // [HGM] race
8336         DisplayBothClocks();
8337         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8338                 parseList[currentMove], _(cps->which));
8339         DisplayMoveError(buf1);
8340         DrawPosition(FALSE, boards[currentMove]);
8341         return;
8342     }
8343     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8344         /* Program has a broken "time" command that
8345            outputs a string not ending in newline.
8346            Don't use it. */
8347         cps->sendTime = 0;
8348     }
8349
8350     /*
8351      * If chess program startup fails, exit with an error message.
8352      * Attempts to recover here are futile.
8353      */
8354     if ((StrStr(message, "unknown host") != NULL)
8355         || (StrStr(message, "No remote directory") != NULL)
8356         || (StrStr(message, "not found") != NULL)
8357         || (StrStr(message, "No such file") != NULL)
8358         || (StrStr(message, "can't alloc") != NULL)
8359         || (StrStr(message, "Permission denied") != NULL)) {
8360
8361         cps->maybeThinking = FALSE;
8362         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8363                 _(cps->which), cps->program, cps->host, message);
8364         RemoveInputSource(cps->isr);
8365         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8366             if(cps == &first) appData.noChessProgram = TRUE;
8367             DisplayError(buf1, 0);
8368         }
8369         return;
8370     }
8371
8372     /*
8373      * Look for hint output
8374      */
8375     if (sscanf(message, "Hint: %s", buf1) == 1) {
8376         if (cps == &first && hintRequested) {
8377             hintRequested = FALSE;
8378             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8379                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8380                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8381                                     PosFlags(forwardMostMove),
8382                                     fromY, fromX, toY, toX, promoChar, buf1);
8383                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8384                 DisplayInformation(buf2);
8385             } else {
8386                 /* Hint move could not be parsed!? */
8387               snprintf(buf2, sizeof(buf2),
8388                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8389                         buf1, _(cps->which));
8390                 DisplayError(buf2, 0);
8391             }
8392         } else {
8393           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8394         }
8395         return;
8396     }
8397
8398     /*
8399      * Ignore other messages if game is not in progress
8400      */
8401     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8402         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8403
8404     /*
8405      * look for win, lose, draw, or draw offer
8406      */
8407     if (strncmp(message, "1-0", 3) == 0) {
8408         char *p, *q, *r = "";
8409         p = strchr(message, '{');
8410         if (p) {
8411             q = strchr(p, '}');
8412             if (q) {
8413                 *q = NULLCHAR;
8414                 r = p + 1;
8415             }
8416         }
8417         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8418         return;
8419     } else if (strncmp(message, "0-1", 3) == 0) {
8420         char *p, *q, *r = "";
8421         p = strchr(message, '{');
8422         if (p) {
8423             q = strchr(p, '}');
8424             if (q) {
8425                 *q = NULLCHAR;
8426                 r = p + 1;
8427             }
8428         }
8429         /* Kludge for Arasan 4.1 bug */
8430         if (strcmp(r, "Black resigns") == 0) {
8431             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8432             return;
8433         }
8434         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8435         return;
8436     } else if (strncmp(message, "1/2", 3) == 0) {
8437         char *p, *q, *r = "";
8438         p = strchr(message, '{');
8439         if (p) {
8440             q = strchr(p, '}');
8441             if (q) {
8442                 *q = NULLCHAR;
8443                 r = p + 1;
8444             }
8445         }
8446
8447         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8448         return;
8449
8450     } else if (strncmp(message, "White resign", 12) == 0) {
8451         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8452         return;
8453     } else if (strncmp(message, "Black resign", 12) == 0) {
8454         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8455         return;
8456     } else if (strncmp(message, "White matches", 13) == 0 ||
8457                strncmp(message, "Black matches", 13) == 0   ) {
8458         /* [HGM] ignore GNUShogi noises */
8459         return;
8460     } else if (strncmp(message, "White", 5) == 0 &&
8461                message[5] != '(' &&
8462                StrStr(message, "Black") == NULL) {
8463         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8464         return;
8465     } else if (strncmp(message, "Black", 5) == 0 &&
8466                message[5] != '(') {
8467         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8468         return;
8469     } else if (strcmp(message, "resign") == 0 ||
8470                strcmp(message, "computer resigns") == 0) {
8471         switch (gameMode) {
8472           case MachinePlaysBlack:
8473           case IcsPlayingBlack:
8474             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8475             break;
8476           case MachinePlaysWhite:
8477           case IcsPlayingWhite:
8478             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8479             break;
8480           case TwoMachinesPlay:
8481             if (cps->twoMachinesColor[0] == 'w')
8482               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8483             else
8484               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8485             break;
8486           default:
8487             /* can't happen */
8488             break;
8489         }
8490         return;
8491     } else if (strncmp(message, "opponent mates", 14) == 0) {
8492         switch (gameMode) {
8493           case MachinePlaysBlack:
8494           case IcsPlayingBlack:
8495             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8496             break;
8497           case MachinePlaysWhite:
8498           case IcsPlayingWhite:
8499             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8500             break;
8501           case TwoMachinesPlay:
8502             if (cps->twoMachinesColor[0] == 'w')
8503               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8504             else
8505               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8506             break;
8507           default:
8508             /* can't happen */
8509             break;
8510         }
8511         return;
8512     } else if (strncmp(message, "computer mates", 14) == 0) {
8513         switch (gameMode) {
8514           case MachinePlaysBlack:
8515           case IcsPlayingBlack:
8516             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8517             break;
8518           case MachinePlaysWhite:
8519           case IcsPlayingWhite:
8520             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8521             break;
8522           case TwoMachinesPlay:
8523             if (cps->twoMachinesColor[0] == 'w')
8524               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8525             else
8526               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8527             break;
8528           default:
8529             /* can't happen */
8530             break;
8531         }
8532         return;
8533     } else if (strncmp(message, "checkmate", 9) == 0) {
8534         if (WhiteOnMove(forwardMostMove)) {
8535             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8536         } else {
8537             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8538         }
8539         return;
8540     } else if (strstr(message, "Draw") != NULL ||
8541                strstr(message, "game is a draw") != NULL) {
8542         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8543         return;
8544     } else if (strstr(message, "offer") != NULL &&
8545                strstr(message, "draw") != NULL) {
8546 #if ZIPPY
8547         if (appData.zippyPlay && first.initDone) {
8548             /* Relay offer to ICS */
8549             SendToICS(ics_prefix);
8550             SendToICS("draw\n");
8551         }
8552 #endif
8553         cps->offeredDraw = 2; /* valid until this engine moves twice */
8554         if (gameMode == TwoMachinesPlay) {
8555             if (cps->other->offeredDraw) {
8556                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8557             /* [HGM] in two-machine mode we delay relaying draw offer      */
8558             /* until after we also have move, to see if it is really claim */
8559             }
8560         } else if (gameMode == MachinePlaysWhite ||
8561                    gameMode == MachinePlaysBlack) {
8562           if (userOfferedDraw) {
8563             DisplayInformation(_("Machine accepts your draw offer"));
8564             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8565           } else {
8566             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8567           }
8568         }
8569     }
8570
8571
8572     /*
8573      * Look for thinking output
8574      */
8575     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8576           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8577                                 ) {
8578         int plylev, mvleft, mvtot, curscore, time;
8579         char mvname[MOVE_LEN];
8580         u64 nodes; // [DM]
8581         char plyext;
8582         int ignore = FALSE;
8583         int prefixHint = FALSE;
8584         mvname[0] = NULLCHAR;
8585
8586         switch (gameMode) {
8587           case MachinePlaysBlack:
8588           case IcsPlayingBlack:
8589             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8590             break;
8591           case MachinePlaysWhite:
8592           case IcsPlayingWhite:
8593             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8594             break;
8595           case AnalyzeMode:
8596           case AnalyzeFile:
8597             break;
8598           case IcsObserving: /* [DM] icsEngineAnalyze */
8599             if (!appData.icsEngineAnalyze) ignore = TRUE;
8600             break;
8601           case TwoMachinesPlay:
8602             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8603                 ignore = TRUE;
8604             }
8605             break;
8606           default:
8607             ignore = TRUE;
8608             break;
8609         }
8610
8611         if (!ignore) {
8612             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8613             buf1[0] = NULLCHAR;
8614             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8615                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8616
8617                 if (plyext != ' ' && plyext != '\t') {
8618                     time *= 100;
8619                 }
8620
8621                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8622                 if( cps->scoreIsAbsolute &&
8623                     ( gameMode == MachinePlaysBlack ||
8624                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8625                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8626                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8627                      !WhiteOnMove(currentMove)
8628                     ) )
8629                 {
8630                     curscore = -curscore;
8631                 }
8632
8633                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8634
8635                 tempStats.depth = plylev;
8636                 tempStats.nodes = nodes;
8637                 tempStats.time = time;
8638                 tempStats.score = curscore;
8639                 tempStats.got_only_move = 0;
8640
8641                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8642                         int ticklen;
8643
8644                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8645                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8646                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8647                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8648                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8649                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8650                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8651                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8652                 }
8653
8654                 /* Buffer overflow protection */
8655                 if (pv[0] != NULLCHAR) {
8656                     if (strlen(pv) >= sizeof(tempStats.movelist)
8657                         && appData.debugMode) {
8658                         fprintf(debugFP,
8659                                 "PV is too long; using the first %u bytes.\n",
8660                                 (unsigned) sizeof(tempStats.movelist) - 1);
8661                     }
8662
8663                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8664                 } else {
8665                     sprintf(tempStats.movelist, " no PV\n");
8666                 }
8667
8668                 if (tempStats.seen_stat) {
8669                     tempStats.ok_to_send = 1;
8670                 }
8671
8672                 if (strchr(tempStats.movelist, '(') != NULL) {
8673                     tempStats.line_is_book = 1;
8674                     tempStats.nr_moves = 0;
8675                     tempStats.moves_left = 0;
8676                 } else {
8677                     tempStats.line_is_book = 0;
8678                 }
8679
8680                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8681                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8682
8683                 SendProgramStatsToFrontend( cps, &tempStats );
8684
8685                 /*
8686                     [AS] Protect the thinkOutput buffer from overflow... this
8687                     is only useful if buf1 hasn't overflowed first!
8688                 */
8689                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8690                          plylev,
8691                          (gameMode == TwoMachinesPlay ?
8692                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8693                          ((double) curscore) / 100.0,
8694                          prefixHint ? lastHint : "",
8695                          prefixHint ? " " : "" );
8696
8697                 if( buf1[0] != NULLCHAR ) {
8698                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8699
8700                     if( strlen(pv) > max_len ) {
8701                         if( appData.debugMode) {
8702                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8703                         }
8704                         pv[max_len+1] = '\0';
8705                     }
8706
8707                     strcat( thinkOutput, pv);
8708                 }
8709
8710                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8711                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8712                     DisplayMove(currentMove - 1);
8713                 }
8714                 return;
8715
8716             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8717                 /* crafty (9.25+) says "(only move) <move>"
8718                  * if there is only 1 legal move
8719                  */
8720                 sscanf(p, "(only move) %s", buf1);
8721                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8722                 sprintf(programStats.movelist, "%s (only move)", buf1);
8723                 programStats.depth = 1;
8724                 programStats.nr_moves = 1;
8725                 programStats.moves_left = 1;
8726                 programStats.nodes = 1;
8727                 programStats.time = 1;
8728                 programStats.got_only_move = 1;
8729
8730                 /* Not really, but we also use this member to
8731                    mean "line isn't going to change" (Crafty
8732                    isn't searching, so stats won't change) */
8733                 programStats.line_is_book = 1;
8734
8735                 SendProgramStatsToFrontend( cps, &programStats );
8736
8737                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8738                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8739                     DisplayMove(currentMove - 1);
8740                 }
8741                 return;
8742             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8743                               &time, &nodes, &plylev, &mvleft,
8744                               &mvtot, mvname) >= 5) {
8745                 /* The stat01: line is from Crafty (9.29+) in response
8746                    to the "." command */
8747                 programStats.seen_stat = 1;
8748                 cps->maybeThinking = TRUE;
8749
8750                 if (programStats.got_only_move || !appData.periodicUpdates)
8751                   return;
8752
8753                 programStats.depth = plylev;
8754                 programStats.time = time;
8755                 programStats.nodes = nodes;
8756                 programStats.moves_left = mvleft;
8757                 programStats.nr_moves = mvtot;
8758                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8759                 programStats.ok_to_send = 1;
8760                 programStats.movelist[0] = '\0';
8761
8762                 SendProgramStatsToFrontend( cps, &programStats );
8763
8764                 return;
8765
8766             } else if (strncmp(message,"++",2) == 0) {
8767                 /* Crafty 9.29+ outputs this */
8768                 programStats.got_fail = 2;
8769                 return;
8770
8771             } else if (strncmp(message,"--",2) == 0) {
8772                 /* Crafty 9.29+ outputs this */
8773                 programStats.got_fail = 1;
8774                 return;
8775
8776             } else if (thinkOutput[0] != NULLCHAR &&
8777                        strncmp(message, "    ", 4) == 0) {
8778                 unsigned message_len;
8779
8780                 p = message;
8781                 while (*p && *p == ' ') p++;
8782
8783                 message_len = strlen( p );
8784
8785                 /* [AS] Avoid buffer overflow */
8786                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8787                     strcat(thinkOutput, " ");
8788                     strcat(thinkOutput, p);
8789                 }
8790
8791                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8792                     strcat(programStats.movelist, " ");
8793                     strcat(programStats.movelist, p);
8794                 }
8795
8796                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8797                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8798                     DisplayMove(currentMove - 1);
8799                 }
8800                 return;
8801             }
8802         }
8803         else {
8804             buf1[0] = NULLCHAR;
8805
8806             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8807                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8808             {
8809                 ChessProgramStats cpstats;
8810
8811                 if (plyext != ' ' && plyext != '\t') {
8812                     time *= 100;
8813                 }
8814
8815                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8816                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8817                     curscore = -curscore;
8818                 }
8819
8820                 cpstats.depth = plylev;
8821                 cpstats.nodes = nodes;
8822                 cpstats.time = time;
8823                 cpstats.score = curscore;
8824                 cpstats.got_only_move = 0;
8825                 cpstats.movelist[0] = '\0';
8826
8827                 if (buf1[0] != NULLCHAR) {
8828                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8829                 }
8830
8831                 cpstats.ok_to_send = 0;
8832                 cpstats.line_is_book = 0;
8833                 cpstats.nr_moves = 0;
8834                 cpstats.moves_left = 0;
8835
8836                 SendProgramStatsToFrontend( cps, &cpstats );
8837             }
8838         }
8839     }
8840 }
8841
8842
8843 /* Parse a game score from the character string "game", and
8844    record it as the history of the current game.  The game
8845    score is NOT assumed to start from the standard position.
8846    The display is not updated in any way.
8847    */
8848 void
8849 ParseGameHistory(game)
8850      char *game;
8851 {
8852     ChessMove moveType;
8853     int fromX, fromY, toX, toY, boardIndex;
8854     char promoChar;
8855     char *p, *q;
8856     char buf[MSG_SIZ];
8857
8858     if (appData.debugMode)
8859       fprintf(debugFP, "Parsing game history: %s\n", game);
8860
8861     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8862     gameInfo.site = StrSave(appData.icsHost);
8863     gameInfo.date = PGNDate();
8864     gameInfo.round = StrSave("-");
8865
8866     /* Parse out names of players */
8867     while (*game == ' ') game++;
8868     p = buf;
8869     while (*game != ' ') *p++ = *game++;
8870     *p = NULLCHAR;
8871     gameInfo.white = StrSave(buf);
8872     while (*game == ' ') game++;
8873     p = buf;
8874     while (*game != ' ' && *game != '\n') *p++ = *game++;
8875     *p = NULLCHAR;
8876     gameInfo.black = StrSave(buf);
8877
8878     /* Parse moves */
8879     boardIndex = blackPlaysFirst ? 1 : 0;
8880     yynewstr(game);
8881     for (;;) {
8882         yyboardindex = boardIndex;
8883         moveType = (ChessMove) Myylex();
8884         switch (moveType) {
8885           case IllegalMove:             /* maybe suicide chess, etc. */
8886   if (appData.debugMode) {
8887     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8888     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8889     setbuf(debugFP, NULL);
8890   }
8891           case WhitePromotion:
8892           case BlackPromotion:
8893           case WhiteNonPromotion:
8894           case BlackNonPromotion:
8895           case NormalMove:
8896           case WhiteCapturesEnPassant:
8897           case BlackCapturesEnPassant:
8898           case WhiteKingSideCastle:
8899           case WhiteQueenSideCastle:
8900           case BlackKingSideCastle:
8901           case BlackQueenSideCastle:
8902           case WhiteKingSideCastleWild:
8903           case WhiteQueenSideCastleWild:
8904           case BlackKingSideCastleWild:
8905           case BlackQueenSideCastleWild:
8906           /* PUSH Fabien */
8907           case WhiteHSideCastleFR:
8908           case WhiteASideCastleFR:
8909           case BlackHSideCastleFR:
8910           case BlackASideCastleFR:
8911           /* POP Fabien */
8912             fromX = currentMoveString[0] - AAA;
8913             fromY = currentMoveString[1] - ONE;
8914             toX = currentMoveString[2] - AAA;
8915             toY = currentMoveString[3] - ONE;
8916             promoChar = currentMoveString[4];
8917             break;
8918           case WhiteDrop:
8919           case BlackDrop:
8920             fromX = moveType == WhiteDrop ?
8921               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8922             (int) CharToPiece(ToLower(currentMoveString[0]));
8923             fromY = DROP_RANK;
8924             toX = currentMoveString[2] - AAA;
8925             toY = currentMoveString[3] - ONE;
8926             promoChar = NULLCHAR;
8927             break;
8928           case AmbiguousMove:
8929             /* bug? */
8930             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8931   if (appData.debugMode) {
8932     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8933     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8934     setbuf(debugFP, NULL);
8935   }
8936             DisplayError(buf, 0);
8937             return;
8938           case ImpossibleMove:
8939             /* bug? */
8940             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8941   if (appData.debugMode) {
8942     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8943     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8944     setbuf(debugFP, NULL);
8945   }
8946             DisplayError(buf, 0);
8947             return;
8948           case EndOfFile:
8949             if (boardIndex < backwardMostMove) {
8950                 /* Oops, gap.  How did that happen? */
8951                 DisplayError(_("Gap in move list"), 0);
8952                 return;
8953             }
8954             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8955             if (boardIndex > forwardMostMove) {
8956                 forwardMostMove = boardIndex;
8957             }
8958             return;
8959           case ElapsedTime:
8960             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8961                 strcat(parseList[boardIndex-1], " ");
8962                 strcat(parseList[boardIndex-1], yy_text);
8963             }
8964             continue;
8965           case Comment:
8966           case PGNTag:
8967           case NAG:
8968           default:
8969             /* ignore */
8970             continue;
8971           case WhiteWins:
8972           case BlackWins:
8973           case GameIsDrawn:
8974           case GameUnfinished:
8975             if (gameMode == IcsExamining) {
8976                 if (boardIndex < backwardMostMove) {
8977                     /* Oops, gap.  How did that happen? */
8978                     return;
8979                 }
8980                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8981                 return;
8982             }
8983             gameInfo.result = moveType;
8984             p = strchr(yy_text, '{');
8985             if (p == NULL) p = strchr(yy_text, '(');
8986             if (p == NULL) {
8987                 p = yy_text;
8988                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8989             } else {
8990                 q = strchr(p, *p == '{' ? '}' : ')');
8991                 if (q != NULL) *q = NULLCHAR;
8992                 p++;
8993             }
8994             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8995             gameInfo.resultDetails = StrSave(p);
8996             continue;
8997         }
8998         if (boardIndex >= forwardMostMove &&
8999             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9000             backwardMostMove = blackPlaysFirst ? 1 : 0;
9001             return;
9002         }
9003         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9004                                  fromY, fromX, toY, toX, promoChar,
9005                                  parseList[boardIndex]);
9006         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9007         /* currentMoveString is set as a side-effect of yylex */
9008         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9009         strcat(moveList[boardIndex], "\n");
9010         boardIndex++;
9011         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9012         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9013           case MT_NONE:
9014           case MT_STALEMATE:
9015           default:
9016             break;
9017           case MT_CHECK:
9018             if(gameInfo.variant != VariantShogi)
9019                 strcat(parseList[boardIndex - 1], "+");
9020             break;
9021           case MT_CHECKMATE:
9022           case MT_STAINMATE:
9023             strcat(parseList[boardIndex - 1], "#");
9024             break;
9025         }
9026     }
9027 }
9028
9029
9030 /* Apply a move to the given board  */
9031 void
9032 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9033      int fromX, fromY, toX, toY;
9034      int promoChar;
9035      Board board;
9036 {
9037   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9038   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9039
9040     /* [HGM] compute & store e.p. status and castling rights for new position */
9041     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9042
9043       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9044       oldEP = (signed char)board[EP_STATUS];
9045       board[EP_STATUS] = EP_NONE;
9046
9047   if (fromY == DROP_RANK) {
9048         /* must be first */
9049         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9050             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9051             return;
9052         }
9053         piece = board[toY][toX] = (ChessSquare) fromX;
9054   } else {
9055       int i;
9056
9057       if( board[toY][toX] != EmptySquare )
9058            board[EP_STATUS] = EP_CAPTURE;
9059
9060       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9061            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9062                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9063       } else
9064       if( board[fromY][fromX] == WhitePawn ) {
9065            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9066                board[EP_STATUS] = EP_PAWN_MOVE;
9067            if( toY-fromY==2) {
9068                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9069                         gameInfo.variant != VariantBerolina || toX < fromX)
9070                       board[EP_STATUS] = toX | berolina;
9071                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9072                         gameInfo.variant != VariantBerolina || toX > fromX)
9073                       board[EP_STATUS] = toX;
9074            }
9075       } else
9076       if( board[fromY][fromX] == BlackPawn ) {
9077            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9078                board[EP_STATUS] = EP_PAWN_MOVE;
9079            if( toY-fromY== -2) {
9080                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9081                         gameInfo.variant != VariantBerolina || toX < fromX)
9082                       board[EP_STATUS] = toX | berolina;
9083                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9084                         gameInfo.variant != VariantBerolina || toX > fromX)
9085                       board[EP_STATUS] = toX;
9086            }
9087        }
9088
9089        for(i=0; i<nrCastlingRights; i++) {
9090            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9091               board[CASTLING][i] == toX   && castlingRank[i] == toY
9092              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9093        }
9094
9095      if (fromX == toX && fromY == toY) return;
9096
9097      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9098      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9099      if(gameInfo.variant == VariantKnightmate)
9100          king += (int) WhiteUnicorn - (int) WhiteKing;
9101
9102     /* Code added by Tord: */
9103     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9104     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9105         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9106       board[fromY][fromX] = EmptySquare;
9107       board[toY][toX] = EmptySquare;
9108       if((toX > fromX) != (piece == WhiteRook)) {
9109         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9110       } else {
9111         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9112       }
9113     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9114                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9115       board[fromY][fromX] = EmptySquare;
9116       board[toY][toX] = EmptySquare;
9117       if((toX > fromX) != (piece == BlackRook)) {
9118         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9119       } else {
9120         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9121       }
9122     /* End of code added by Tord */
9123
9124     } else if (board[fromY][fromX] == king
9125         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9126         && toY == fromY && toX > fromX+1) {
9127         board[fromY][fromX] = EmptySquare;
9128         board[toY][toX] = king;
9129         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9130         board[fromY][BOARD_RGHT-1] = EmptySquare;
9131     } else if (board[fromY][fromX] == king
9132         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9133                && toY == fromY && toX < fromX-1) {
9134         board[fromY][fromX] = EmptySquare;
9135         board[toY][toX] = king;
9136         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9137         board[fromY][BOARD_LEFT] = EmptySquare;
9138     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9139                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9140                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9141                ) {
9142         /* white pawn promotion */
9143         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9144         if(gameInfo.variant==VariantBughouse ||
9145            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9146             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9147         board[fromY][fromX] = EmptySquare;
9148     } else if ((fromY >= BOARD_HEIGHT>>1)
9149                && (toX != fromX)
9150                && gameInfo.variant != VariantXiangqi
9151                && gameInfo.variant != VariantBerolina
9152                && (board[fromY][fromX] == WhitePawn)
9153                && (board[toY][toX] == EmptySquare)) {
9154         board[fromY][fromX] = EmptySquare;
9155         board[toY][toX] = WhitePawn;
9156         captured = board[toY - 1][toX];
9157         board[toY - 1][toX] = EmptySquare;
9158     } else if ((fromY == BOARD_HEIGHT-4)
9159                && (toX == fromX)
9160                && gameInfo.variant == VariantBerolina
9161                && (board[fromY][fromX] == WhitePawn)
9162                && (board[toY][toX] == EmptySquare)) {
9163         board[fromY][fromX] = EmptySquare;
9164         board[toY][toX] = WhitePawn;
9165         if(oldEP & EP_BEROLIN_A) {
9166                 captured = board[fromY][fromX-1];
9167                 board[fromY][fromX-1] = EmptySquare;
9168         }else{  captured = board[fromY][fromX+1];
9169                 board[fromY][fromX+1] = EmptySquare;
9170         }
9171     } else if (board[fromY][fromX] == king
9172         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9173                && toY == fromY && toX > fromX+1) {
9174         board[fromY][fromX] = EmptySquare;
9175         board[toY][toX] = king;
9176         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9177         board[fromY][BOARD_RGHT-1] = EmptySquare;
9178     } else if (board[fromY][fromX] == king
9179         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9180                && toY == fromY && toX < fromX-1) {
9181         board[fromY][fromX] = EmptySquare;
9182         board[toY][toX] = king;
9183         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9184         board[fromY][BOARD_LEFT] = EmptySquare;
9185     } else if (fromY == 7 && fromX == 3
9186                && board[fromY][fromX] == BlackKing
9187                && toY == 7 && toX == 5) {
9188         board[fromY][fromX] = EmptySquare;
9189         board[toY][toX] = BlackKing;
9190         board[fromY][7] = EmptySquare;
9191         board[toY][4] = BlackRook;
9192     } else if (fromY == 7 && fromX == 3
9193                && board[fromY][fromX] == BlackKing
9194                && toY == 7 && toX == 1) {
9195         board[fromY][fromX] = EmptySquare;
9196         board[toY][toX] = BlackKing;
9197         board[fromY][0] = EmptySquare;
9198         board[toY][2] = BlackRook;
9199     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9200                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9201                && toY < promoRank && promoChar
9202                ) {
9203         /* black pawn promotion */
9204         board[toY][toX] = CharToPiece(ToLower(promoChar));
9205         if(gameInfo.variant==VariantBughouse ||
9206            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9207             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9208         board[fromY][fromX] = EmptySquare;
9209     } else if ((fromY < BOARD_HEIGHT>>1)
9210                && (toX != fromX)
9211                && gameInfo.variant != VariantXiangqi
9212                && gameInfo.variant != VariantBerolina
9213                && (board[fromY][fromX] == BlackPawn)
9214                && (board[toY][toX] == EmptySquare)) {
9215         board[fromY][fromX] = EmptySquare;
9216         board[toY][toX] = BlackPawn;
9217         captured = board[toY + 1][toX];
9218         board[toY + 1][toX] = EmptySquare;
9219     } else if ((fromY == 3)
9220                && (toX == fromX)
9221                && gameInfo.variant == VariantBerolina
9222                && (board[fromY][fromX] == BlackPawn)
9223                && (board[toY][toX] == EmptySquare)) {
9224         board[fromY][fromX] = EmptySquare;
9225         board[toY][toX] = BlackPawn;
9226         if(oldEP & EP_BEROLIN_A) {
9227                 captured = board[fromY][fromX-1];
9228                 board[fromY][fromX-1] = EmptySquare;
9229         }else{  captured = board[fromY][fromX+1];
9230                 board[fromY][fromX+1] = EmptySquare;
9231         }
9232     } else {
9233         board[toY][toX] = board[fromY][fromX];
9234         board[fromY][fromX] = EmptySquare;
9235     }
9236   }
9237
9238     if (gameInfo.holdingsWidth != 0) {
9239
9240       /* !!A lot more code needs to be written to support holdings  */
9241       /* [HGM] OK, so I have written it. Holdings are stored in the */
9242       /* penultimate board files, so they are automaticlly stored   */
9243       /* in the game history.                                       */
9244       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9245                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9246         /* Delete from holdings, by decreasing count */
9247         /* and erasing image if necessary            */
9248         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9249         if(p < (int) BlackPawn) { /* white drop */
9250              p -= (int)WhitePawn;
9251                  p = PieceToNumber((ChessSquare)p);
9252              if(p >= gameInfo.holdingsSize) p = 0;
9253              if(--board[p][BOARD_WIDTH-2] <= 0)
9254                   board[p][BOARD_WIDTH-1] = EmptySquare;
9255              if((int)board[p][BOARD_WIDTH-2] < 0)
9256                         board[p][BOARD_WIDTH-2] = 0;
9257         } else {                  /* black drop */
9258              p -= (int)BlackPawn;
9259                  p = PieceToNumber((ChessSquare)p);
9260              if(p >= gameInfo.holdingsSize) p = 0;
9261              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9262                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9263              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9264                         board[BOARD_HEIGHT-1-p][1] = 0;
9265         }
9266       }
9267       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9268           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9269         /* [HGM] holdings: Add to holdings, if holdings exist */
9270         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9271                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9272                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9273         }
9274         p = (int) captured;
9275         if (p >= (int) BlackPawn) {
9276           p -= (int)BlackPawn;
9277           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9278                   /* in Shogi restore piece to its original  first */
9279                   captured = (ChessSquare) (DEMOTED captured);
9280                   p = DEMOTED p;
9281           }
9282           p = PieceToNumber((ChessSquare)p);
9283           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9284           board[p][BOARD_WIDTH-2]++;
9285           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9286         } else {
9287           p -= (int)WhitePawn;
9288           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9289                   captured = (ChessSquare) (DEMOTED captured);
9290                   p = DEMOTED p;
9291           }
9292           p = PieceToNumber((ChessSquare)p);
9293           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9294           board[BOARD_HEIGHT-1-p][1]++;
9295           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9296         }
9297       }
9298     } else if (gameInfo.variant == VariantAtomic) {
9299       if (captured != EmptySquare) {
9300         int y, x;
9301         for (y = toY-1; y <= toY+1; y++) {
9302           for (x = toX-1; x <= toX+1; x++) {
9303             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9304                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9305               board[y][x] = EmptySquare;
9306             }
9307           }
9308         }
9309         board[toY][toX] = EmptySquare;
9310       }
9311     }
9312     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9313         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9314     } else
9315     if(promoChar == '+') {
9316         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9317         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9318     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9319         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9320     }
9321     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9322                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9323         // [HGM] superchess: take promotion piece out of holdings
9324         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9325         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9326             if(!--board[k][BOARD_WIDTH-2])
9327                 board[k][BOARD_WIDTH-1] = EmptySquare;
9328         } else {
9329             if(!--board[BOARD_HEIGHT-1-k][1])
9330                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9331         }
9332     }
9333
9334 }
9335
9336 /* Updates forwardMostMove */
9337 void
9338 MakeMove(fromX, fromY, toX, toY, promoChar)
9339      int fromX, fromY, toX, toY;
9340      int promoChar;
9341 {
9342 //    forwardMostMove++; // [HGM] bare: moved downstream
9343
9344     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9345         int timeLeft; static int lastLoadFlag=0; int king, piece;
9346         piece = boards[forwardMostMove][fromY][fromX];
9347         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9348         if(gameInfo.variant == VariantKnightmate)
9349             king += (int) WhiteUnicorn - (int) WhiteKing;
9350         if(forwardMostMove == 0) {
9351             if(blackPlaysFirst)
9352                 fprintf(serverMoves, "%s;", second.tidy);
9353             fprintf(serverMoves, "%s;", first.tidy);
9354             if(!blackPlaysFirst)
9355                 fprintf(serverMoves, "%s;", second.tidy);
9356         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9357         lastLoadFlag = loadFlag;
9358         // print base move
9359         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9360         // print castling suffix
9361         if( toY == fromY && piece == king ) {
9362             if(toX-fromX > 1)
9363                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9364             if(fromX-toX >1)
9365                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9366         }
9367         // e.p. suffix
9368         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9369              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9370              boards[forwardMostMove][toY][toX] == EmptySquare
9371              && fromX != toX && fromY != toY)
9372                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9373         // promotion suffix
9374         if(promoChar != NULLCHAR)
9375                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9376         if(!loadFlag) {
9377             fprintf(serverMoves, "/%d/%d",
9378                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9379             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9380             else                      timeLeft = blackTimeRemaining/1000;
9381             fprintf(serverMoves, "/%d", timeLeft);
9382         }
9383         fflush(serverMoves);
9384     }
9385
9386     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9387       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9388                         0, 1);
9389       return;
9390     }
9391     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9392     if (commentList[forwardMostMove+1] != NULL) {
9393         free(commentList[forwardMostMove+1]);
9394         commentList[forwardMostMove+1] = NULL;
9395     }
9396     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9397     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9398     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9399     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9400     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9401     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9402     gameInfo.result = GameUnfinished;
9403     if (gameInfo.resultDetails != NULL) {
9404         free(gameInfo.resultDetails);
9405         gameInfo.resultDetails = NULL;
9406     }
9407     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9408                               moveList[forwardMostMove - 1]);
9409     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9410                              PosFlags(forwardMostMove - 1),
9411                              fromY, fromX, toY, toX, promoChar,
9412                              parseList[forwardMostMove - 1]);
9413     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9414       case MT_NONE:
9415       case MT_STALEMATE:
9416       default:
9417         break;
9418       case MT_CHECK:
9419         if(gameInfo.variant != VariantShogi)
9420             strcat(parseList[forwardMostMove - 1], "+");
9421         break;
9422       case MT_CHECKMATE:
9423       case MT_STAINMATE:
9424         strcat(parseList[forwardMostMove - 1], "#");
9425         break;
9426     }
9427     if (appData.debugMode) {
9428         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9429     }
9430
9431 }
9432
9433 /* Updates currentMove if not pausing */
9434 void
9435 ShowMove(fromX, fromY, toX, toY)
9436 {
9437     int instant = (gameMode == PlayFromGameFile) ?
9438         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9439     if(appData.noGUI) return;
9440     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9441         if (!instant) {
9442             if (forwardMostMove == currentMove + 1) {
9443                 AnimateMove(boards[forwardMostMove - 1],
9444                             fromX, fromY, toX, toY);
9445             }
9446             if (appData.highlightLastMove) {
9447                 SetHighlights(fromX, fromY, toX, toY);
9448             }
9449         }
9450         currentMove = forwardMostMove;
9451     }
9452
9453     if (instant) return;
9454
9455     DisplayMove(currentMove - 1);
9456     DrawPosition(FALSE, boards[currentMove]);
9457     DisplayBothClocks();
9458     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9459     DisplayBook(currentMove);
9460 }
9461
9462 void SendEgtPath(ChessProgramState *cps)
9463 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9464         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9465
9466         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9467
9468         while(*p) {
9469             char c, *q = name+1, *r, *s;
9470
9471             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9472             while(*p && *p != ',') *q++ = *p++;
9473             *q++ = ':'; *q = 0;
9474             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9475                 strcmp(name, ",nalimov:") == 0 ) {
9476                 // take nalimov path from the menu-changeable option first, if it is defined
9477               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9478                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9479             } else
9480             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9481                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9482                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9483                 s = r = StrStr(s, ":") + 1; // beginning of path info
9484                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9485                 c = *r; *r = 0;             // temporarily null-terminate path info
9486                     *--q = 0;               // strip of trailig ':' from name
9487                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9488                 *r = c;
9489                 SendToProgram(buf,cps);     // send egtbpath command for this format
9490             }
9491             if(*p == ',') p++; // read away comma to position for next format name
9492         }
9493 }
9494
9495 void
9496 InitChessProgram(cps, setup)
9497      ChessProgramState *cps;
9498      int setup; /* [HGM] needed to setup FRC opening position */
9499 {
9500     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9501     if (appData.noChessProgram) return;
9502     hintRequested = FALSE;
9503     bookRequested = FALSE;
9504
9505     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9506     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9507     if(cps->memSize) { /* [HGM] memory */
9508       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9509         SendToProgram(buf, cps);
9510     }
9511     SendEgtPath(cps); /* [HGM] EGT */
9512     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9513       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9514         SendToProgram(buf, cps);
9515     }
9516
9517     SendToProgram(cps->initString, cps);
9518     if (gameInfo.variant != VariantNormal &&
9519         gameInfo.variant != VariantLoadable
9520         /* [HGM] also send variant if board size non-standard */
9521         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9522                                             ) {
9523       char *v = VariantName(gameInfo.variant);
9524       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9525         /* [HGM] in protocol 1 we have to assume all variants valid */
9526         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9527         DisplayFatalError(buf, 0, 1);
9528         return;
9529       }
9530
9531       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9532       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9533       if( gameInfo.variant == VariantXiangqi )
9534            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9535       if( gameInfo.variant == VariantShogi )
9536            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9537       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9538            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9539       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9540           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9541            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9542       if( gameInfo.variant == VariantCourier )
9543            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9544       if( gameInfo.variant == VariantSuper )
9545            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9546       if( gameInfo.variant == VariantGreat )
9547            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9548       if( gameInfo.variant == VariantSChess )
9549            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9550       if( gameInfo.variant == VariantGrand )
9551            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9552
9553       if(overruled) {
9554         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9555                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9556            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9557            if(StrStr(cps->variants, b) == NULL) {
9558                // specific sized variant not known, check if general sizing allowed
9559                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9560                    if(StrStr(cps->variants, "boardsize") == NULL) {
9561                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9562                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9563                        DisplayFatalError(buf, 0, 1);
9564                        return;
9565                    }
9566                    /* [HGM] here we really should compare with the maximum supported board size */
9567                }
9568            }
9569       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9570       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9571       SendToProgram(buf, cps);
9572     }
9573     currentlyInitializedVariant = gameInfo.variant;
9574
9575     /* [HGM] send opening position in FRC to first engine */
9576     if(setup) {
9577           SendToProgram("force\n", cps);
9578           SendBoard(cps, 0);
9579           /* engine is now in force mode! Set flag to wake it up after first move. */
9580           setboardSpoiledMachineBlack = 1;
9581     }
9582
9583     if (cps->sendICS) {
9584       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9585       SendToProgram(buf, cps);
9586     }
9587     cps->maybeThinking = FALSE;
9588     cps->offeredDraw = 0;
9589     if (!appData.icsActive) {
9590         SendTimeControl(cps, movesPerSession, timeControl,
9591                         timeIncrement, appData.searchDepth,
9592                         searchTime);
9593     }
9594     if (appData.showThinking
9595         // [HGM] thinking: four options require thinking output to be sent
9596         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9597                                 ) {
9598         SendToProgram("post\n", cps);
9599     }
9600     SendToProgram("hard\n", cps);
9601     if (!appData.ponderNextMove) {
9602         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9603            it without being sure what state we are in first.  "hard"
9604            is not a toggle, so that one is OK.
9605          */
9606         SendToProgram("easy\n", cps);
9607     }
9608     if (cps->usePing) {
9609       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9610       SendToProgram(buf, cps);
9611     }
9612     cps->initDone = TRUE;
9613     ClearEngineOutputPane(cps == &second);
9614 }
9615
9616
9617 void
9618 StartChessProgram(cps)
9619      ChessProgramState *cps;
9620 {
9621     char buf[MSG_SIZ];
9622     int err;
9623
9624     if (appData.noChessProgram) return;
9625     cps->initDone = FALSE;
9626
9627     if (strcmp(cps->host, "localhost") == 0) {
9628         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9629     } else if (*appData.remoteShell == NULLCHAR) {
9630         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9631     } else {
9632         if (*appData.remoteUser == NULLCHAR) {
9633           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9634                     cps->program);
9635         } else {
9636           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9637                     cps->host, appData.remoteUser, cps->program);
9638         }
9639         err = StartChildProcess(buf, "", &cps->pr);
9640     }
9641
9642     if (err != 0) {
9643       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9644         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9645         if(cps != &first) return;
9646         appData.noChessProgram = TRUE;
9647         ThawUI();
9648         SetNCPMode();
9649 //      DisplayFatalError(buf, err, 1);
9650 //      cps->pr = NoProc;
9651 //      cps->isr = NULL;
9652         return;
9653     }
9654
9655     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9656     if (cps->protocolVersion > 1) {
9657       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9658       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9659       cps->comboCnt = 0;  //                and values of combo boxes
9660       SendToProgram(buf, cps);
9661     } else {
9662       SendToProgram("xboard\n", cps);
9663     }
9664 }
9665
9666 void
9667 TwoMachinesEventIfReady P((void))
9668 {
9669   static int curMess = 0;
9670   if (first.lastPing != first.lastPong) {
9671     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9672     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9673     return;
9674   }
9675   if (second.lastPing != second.lastPong) {
9676     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9677     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9678     return;
9679   }
9680   DisplayMessage("", ""); curMess = 0;
9681   ThawUI();
9682   TwoMachinesEvent();
9683 }
9684
9685 char *
9686 MakeName(char *template)
9687 {
9688     time_t clock;
9689     struct tm *tm;
9690     static char buf[MSG_SIZ];
9691     char *p = buf;
9692     int i;
9693
9694     clock = time((time_t *)NULL);
9695     tm = localtime(&clock);
9696
9697     while(*p++ = *template++) if(p[-1] == '%') {
9698         switch(*template++) {
9699           case 0:   *p = 0; return buf;
9700           case 'Y': i = tm->tm_year+1900; break;
9701           case 'y': i = tm->tm_year-100; break;
9702           case 'M': i = tm->tm_mon+1; break;
9703           case 'd': i = tm->tm_mday; break;
9704           case 'h': i = tm->tm_hour; break;
9705           case 'm': i = tm->tm_min; break;
9706           case 's': i = tm->tm_sec; break;
9707           default:  i = 0;
9708         }
9709         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9710     }
9711     return buf;
9712 }
9713
9714 int
9715 CountPlayers(char *p)
9716 {
9717     int n = 0;
9718     while(p = strchr(p, '\n')) p++, n++; // count participants
9719     return n;
9720 }
9721
9722 FILE *
9723 WriteTourneyFile(char *results, FILE *f)
9724 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9725     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9726     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9727         // create a file with tournament description
9728         fprintf(f, "-participants {%s}\n", appData.participants);
9729         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9730         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9731         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9732         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9733         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9734         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9735         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9736         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9737         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9738         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9739         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9740         if(searchTime > 0)
9741                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9742         else {
9743                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9744                 fprintf(f, "-tc %s\n", appData.timeControl);
9745                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9746         }
9747         fprintf(f, "-results \"%s\"\n", results);
9748     }
9749     return f;
9750 }
9751
9752 #define MAXENGINES 1000
9753 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9754
9755 void Substitute(char *participants, int expunge)
9756 {
9757     int i, changed, changes=0, nPlayers=0;
9758     char *p, *q, *r, buf[MSG_SIZ];
9759     if(participants == NULL) return;
9760     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9761     r = p = participants; q = appData.participants;
9762     while(*p && *p == *q) {
9763         if(*p == '\n') r = p+1, nPlayers++;
9764         p++; q++;
9765     }
9766     if(*p) { // difference
9767         while(*p && *p++ != '\n');
9768         while(*q && *q++ != '\n');
9769       changed = nPlayers;
9770         changes = 1 + (strcmp(p, q) != 0);
9771     }
9772     if(changes == 1) { // a single engine mnemonic was changed
9773         q = r; while(*q) nPlayers += (*q++ == '\n');
9774         p = buf; while(*r && (*p = *r++) != '\n') p++;
9775         *p = NULLCHAR;
9776         NamesToList(firstChessProgramNames, command, mnemonic);
9777         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9778         if(mnemonic[i]) { // The substitute is valid
9779             FILE *f;
9780             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9781                 flock(fileno(f), LOCK_EX);
9782                 ParseArgsFromFile(f);
9783                 fseek(f, 0, SEEK_SET);
9784                 FREE(appData.participants); appData.participants = participants;
9785                 if(expunge) { // erase results of replaced engine
9786                     int len = strlen(appData.results), w, b, dummy;
9787                     for(i=0; i<len; i++) {
9788                         Pairing(i, nPlayers, &w, &b, &dummy);
9789                         if((w == changed || b == changed) && appData.results[i] == '*') {
9790                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9791                             fclose(f);
9792                             return;
9793                         }
9794                     }
9795                     for(i=0; i<len; i++) {
9796                         Pairing(i, nPlayers, &w, &b, &dummy);
9797                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9798                     }
9799                 }
9800                 WriteTourneyFile(appData.results, f);
9801                 fclose(f); // release lock
9802                 return;
9803             }
9804         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9805     }
9806     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9807     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9808     free(participants);
9809     return;
9810 }
9811
9812 int
9813 CreateTourney(char *name)
9814 {
9815         FILE *f;
9816         if(matchMode && strcmp(name, appData.tourneyFile)) {
9817              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9818         }
9819         if(name[0] == NULLCHAR) {
9820             if(appData.participants[0])
9821                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9822             return 0;
9823         }
9824         f = fopen(name, "r");
9825         if(f) { // file exists
9826             ASSIGN(appData.tourneyFile, name);
9827             ParseArgsFromFile(f); // parse it
9828         } else {
9829             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9830             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9831                 DisplayError(_("Not enough participants"), 0);
9832                 return 0;
9833             }
9834             ASSIGN(appData.tourneyFile, name);
9835             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9836             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9837         }
9838         fclose(f);
9839         appData.noChessProgram = FALSE;
9840         appData.clockMode = TRUE;
9841         SetGNUMode();
9842         return 1;
9843 }
9844
9845 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9846 {
9847     char buf[MSG_SIZ], *p, *q;
9848     int i=1;
9849     while(*names) {
9850         p = names; q = buf;
9851         while(*p && *p != '\n') *q++ = *p++;
9852         *q = 0;
9853         if(engineList[i]) free(engineList[i]);
9854         engineList[i] = strdup(buf);
9855         if(*p == '\n') p++;
9856         TidyProgramName(engineList[i], "localhost", buf);
9857         if(engineMnemonic[i]) free(engineMnemonic[i]);
9858         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9859             strcat(buf, " (");
9860             sscanf(q + 8, "%s", buf + strlen(buf));
9861             strcat(buf, ")");
9862         }
9863         engineMnemonic[i] = strdup(buf);
9864         names = p; i++;
9865       if(i > MAXENGINES - 2) break;
9866     }
9867     engineList[i] = engineMnemonic[i] = NULL;
9868 }
9869
9870 // following implemented as macro to avoid type limitations
9871 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9872
9873 void SwapEngines(int n)
9874 {   // swap settings for first engine and other engine (so far only some selected options)
9875     int h;
9876     char *p;
9877     if(n == 0) return;
9878     SWAP(directory, p)
9879     SWAP(chessProgram, p)
9880     SWAP(isUCI, h)
9881     SWAP(hasOwnBookUCI, h)
9882     SWAP(protocolVersion, h)
9883     SWAP(reuse, h)
9884     SWAP(scoreIsAbsolute, h)
9885     SWAP(timeOdds, h)
9886     SWAP(logo, p)
9887     SWAP(pgnName, p)
9888     SWAP(pvSAN, h)
9889 }
9890
9891 void
9892 SetPlayer(int player)
9893 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9894     int i;
9895     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9896     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9897     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9898     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9899     if(mnemonic[i]) {
9900         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9901         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9902         ParseArgsFromString(buf);
9903     }
9904     free(engineName);
9905 }
9906
9907 int
9908 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9909 {   // determine players from game number
9910     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9911
9912     if(appData.tourneyType == 0) {
9913         roundsPerCycle = (nPlayers - 1) | 1;
9914         pairingsPerRound = nPlayers / 2;
9915     } else if(appData.tourneyType > 0) {
9916         roundsPerCycle = nPlayers - appData.tourneyType;
9917         pairingsPerRound = appData.tourneyType;
9918     }
9919     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9920     gamesPerCycle = gamesPerRound * roundsPerCycle;
9921     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9922     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9923     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9924     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9925     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9926     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9927
9928     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9929     if(appData.roundSync) *syncInterval = gamesPerRound;
9930
9931     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9932
9933     if(appData.tourneyType == 0) {
9934         if(curPairing == (nPlayers-1)/2 ) {
9935             *whitePlayer = curRound;
9936             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9937         } else {
9938             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9939             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9940             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9941             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9942         }
9943     } else if(appData.tourneyType > 0) {
9944         *whitePlayer = curPairing;
9945         *blackPlayer = curRound + appData.tourneyType;
9946     }
9947
9948     // take care of white/black alternation per round. 
9949     // For cycles and games this is already taken care of by default, derived from matchGame!
9950     return curRound & 1;
9951 }
9952
9953 int
9954 NextTourneyGame(int nr, int *swapColors)
9955 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9956     char *p, *q;
9957     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9958     FILE *tf;
9959     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9960     tf = fopen(appData.tourneyFile, "r");
9961     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9962     ParseArgsFromFile(tf); fclose(tf);
9963     InitTimeControls(); // TC might be altered from tourney file
9964
9965     nPlayers = CountPlayers(appData.participants); // count participants
9966     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9967     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9968
9969     if(syncInterval) {
9970         p = q = appData.results;
9971         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9972         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9973             DisplayMessage(_("Waiting for other game(s)"),"");
9974             waitingForGame = TRUE;
9975             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9976             return 0;
9977         }
9978         waitingForGame = FALSE;
9979     }
9980
9981     if(appData.tourneyType < 0) {
9982         if(nr>=0 && !pairingReceived) {
9983             char buf[1<<16];
9984             if(pairing.pr == NoProc) {
9985                 if(!appData.pairingEngine[0]) {
9986                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9987                     return 0;
9988                 }
9989                 StartChessProgram(&pairing); // starts the pairing engine
9990             }
9991             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9992             SendToProgram(buf, &pairing);
9993             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9994             SendToProgram(buf, &pairing);
9995             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9996         }
9997         pairingReceived = 0;                              // ... so we continue here 
9998         *swapColors = 0;
9999         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10000         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10001         matchGame = 1; roundNr = nr / syncInterval + 1;
10002     }
10003
10004     if(first.pr != NoProc) return 1; // engines already loaded
10005
10006     // redefine engines, engine dir, etc.
10007     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10008     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10009     SwapEngines(1);
10010     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10011     SwapEngines(1);         // and make that valid for second engine by swapping
10012     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10013     InitEngine(&second, 1);
10014     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10015     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10016     return 1;
10017 }
10018
10019 void
10020 NextMatchGame()
10021 {   // performs game initialization that does not invoke engines, and then tries to start the game
10022     int firstWhite, swapColors = 0;
10023     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10024     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10025     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10026     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10027     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10028     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10029     Reset(FALSE, first.pr != NoProc);
10030     appData.noChessProgram = FALSE;
10031     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
10032     TwoMachinesEvent();
10033 }
10034
10035 void UserAdjudicationEvent( int result )
10036 {
10037     ChessMove gameResult = GameIsDrawn;
10038
10039     if( result > 0 ) {
10040         gameResult = WhiteWins;
10041     }
10042     else if( result < 0 ) {
10043         gameResult = BlackWins;
10044     }
10045
10046     if( gameMode == TwoMachinesPlay ) {
10047         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10048     }
10049 }
10050
10051
10052 // [HGM] save: calculate checksum of game to make games easily identifiable
10053 int StringCheckSum(char *s)
10054 {
10055         int i = 0;
10056         if(s==NULL) return 0;
10057         while(*s) i = i*259 + *s++;
10058         return i;
10059 }
10060
10061 int GameCheckSum()
10062 {
10063         int i, sum=0;
10064         for(i=backwardMostMove; i<forwardMostMove; i++) {
10065                 sum += pvInfoList[i].depth;
10066                 sum += StringCheckSum(parseList[i]);
10067                 sum += StringCheckSum(commentList[i]);
10068                 sum *= 261;
10069         }
10070         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10071         return sum + StringCheckSum(commentList[i]);
10072 } // end of save patch
10073
10074 void
10075 GameEnds(result, resultDetails, whosays)
10076      ChessMove result;
10077      char *resultDetails;
10078      int whosays;
10079 {
10080     GameMode nextGameMode;
10081     int isIcsGame;
10082     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10083
10084     if(endingGame) return; /* [HGM] crash: forbid recursion */
10085     endingGame = 1;
10086     if(twoBoards) { // [HGM] dual: switch back to one board
10087         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10088         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10089     }
10090     if (appData.debugMode) {
10091       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10092               result, resultDetails ? resultDetails : "(null)", whosays);
10093     }
10094
10095     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10096
10097     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10098         /* If we are playing on ICS, the server decides when the
10099            game is over, but the engine can offer to draw, claim
10100            a draw, or resign.
10101          */
10102 #if ZIPPY
10103         if (appData.zippyPlay && first.initDone) {
10104             if (result == GameIsDrawn) {
10105                 /* In case draw still needs to be claimed */
10106                 SendToICS(ics_prefix);
10107                 SendToICS("draw\n");
10108             } else if (StrCaseStr(resultDetails, "resign")) {
10109                 SendToICS(ics_prefix);
10110                 SendToICS("resign\n");
10111             }
10112         }
10113 #endif
10114         endingGame = 0; /* [HGM] crash */
10115         return;
10116     }
10117
10118     /* If we're loading the game from a file, stop */
10119     if (whosays == GE_FILE) {
10120       (void) StopLoadGameTimer();
10121       gameFileFP = NULL;
10122     }
10123
10124     /* Cancel draw offers */
10125     first.offeredDraw = second.offeredDraw = 0;
10126
10127     /* If this is an ICS game, only ICS can really say it's done;
10128        if not, anyone can. */
10129     isIcsGame = (gameMode == IcsPlayingWhite ||
10130                  gameMode == IcsPlayingBlack ||
10131                  gameMode == IcsObserving    ||
10132                  gameMode == IcsExamining);
10133
10134     if (!isIcsGame || whosays == GE_ICS) {
10135         /* OK -- not an ICS game, or ICS said it was done */
10136         StopClocks();
10137         if (!isIcsGame && !appData.noChessProgram)
10138           SetUserThinkingEnables();
10139
10140         /* [HGM] if a machine claims the game end we verify this claim */
10141         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10142             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10143                 char claimer;
10144                 ChessMove trueResult = (ChessMove) -1;
10145
10146                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10147                                             first.twoMachinesColor[0] :
10148                                             second.twoMachinesColor[0] ;
10149
10150                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10151                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10152                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10153                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10154                 } else
10155                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10156                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10157                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10158                 } else
10159                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10160                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10161                 }
10162
10163                 // now verify win claims, but not in drop games, as we don't understand those yet
10164                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10165                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10166                     (result == WhiteWins && claimer == 'w' ||
10167                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10168                       if (appData.debugMode) {
10169                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10170                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10171                       }
10172                       if(result != trueResult) {
10173                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10174                               result = claimer == 'w' ? BlackWins : WhiteWins;
10175                               resultDetails = buf;
10176                       }
10177                 } else
10178                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10179                     && (forwardMostMove <= backwardMostMove ||
10180                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10181                         (claimer=='b')==(forwardMostMove&1))
10182                                                                                   ) {
10183                       /* [HGM] verify: draws that were not flagged are false claims */
10184                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10185                       result = claimer == 'w' ? BlackWins : WhiteWins;
10186                       resultDetails = buf;
10187                 }
10188                 /* (Claiming a loss is accepted no questions asked!) */
10189             }
10190             /* [HGM] bare: don't allow bare King to win */
10191             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10192                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10193                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10194                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10195                && result != GameIsDrawn)
10196             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10197                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10198                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10199                         if(p >= 0 && p <= (int)WhiteKing) k++;
10200                 }
10201                 if (appData.debugMode) {
10202                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10203                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10204                 }
10205                 if(k <= 1) {
10206                         result = GameIsDrawn;
10207                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10208                         resultDetails = buf;
10209                 }
10210             }
10211         }
10212
10213
10214         if(serverMoves != NULL && !loadFlag) { char c = '=';
10215             if(result==WhiteWins) c = '+';
10216             if(result==BlackWins) c = '-';
10217             if(resultDetails != NULL)
10218                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10219         }
10220         if (resultDetails != NULL) {
10221             gameInfo.result = result;
10222             gameInfo.resultDetails = StrSave(resultDetails);
10223
10224             /* display last move only if game was not loaded from file */
10225             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10226                 DisplayMove(currentMove - 1);
10227
10228             if (forwardMostMove != 0) {
10229                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10230                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10231                                                                 ) {
10232                     if (*appData.saveGameFile != NULLCHAR) {
10233                         SaveGameToFile(appData.saveGameFile, TRUE);
10234                     } else if (appData.autoSaveGames) {
10235                         AutoSaveGame();
10236                     }
10237                     if (*appData.savePositionFile != NULLCHAR) {
10238                         SavePositionToFile(appData.savePositionFile);
10239                     }
10240                 }
10241             }
10242
10243             /* Tell program how game ended in case it is learning */
10244             /* [HGM] Moved this to after saving the PGN, just in case */
10245             /* engine died and we got here through time loss. In that */
10246             /* case we will get a fatal error writing the pipe, which */
10247             /* would otherwise lose us the PGN.                       */
10248             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10249             /* output during GameEnds should never be fatal anymore   */
10250             if (gameMode == MachinePlaysWhite ||
10251                 gameMode == MachinePlaysBlack ||
10252                 gameMode == TwoMachinesPlay ||
10253                 gameMode == IcsPlayingWhite ||
10254                 gameMode == IcsPlayingBlack ||
10255                 gameMode == BeginningOfGame) {
10256                 char buf[MSG_SIZ];
10257                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10258                         resultDetails);
10259                 if (first.pr != NoProc) {
10260                     SendToProgram(buf, &first);
10261                 }
10262                 if (second.pr != NoProc &&
10263                     gameMode == TwoMachinesPlay) {
10264                     SendToProgram(buf, &second);
10265                 }
10266             }
10267         }
10268
10269         if (appData.icsActive) {
10270             if (appData.quietPlay &&
10271                 (gameMode == IcsPlayingWhite ||
10272                  gameMode == IcsPlayingBlack)) {
10273                 SendToICS(ics_prefix);
10274                 SendToICS("set shout 1\n");
10275             }
10276             nextGameMode = IcsIdle;
10277             ics_user_moved = FALSE;
10278             /* clean up premove.  It's ugly when the game has ended and the
10279              * premove highlights are still on the board.
10280              */
10281             if (gotPremove) {
10282               gotPremove = FALSE;
10283               ClearPremoveHighlights();
10284               DrawPosition(FALSE, boards[currentMove]);
10285             }
10286             if (whosays == GE_ICS) {
10287                 switch (result) {
10288                 case WhiteWins:
10289                     if (gameMode == IcsPlayingWhite)
10290                         PlayIcsWinSound();
10291                     else if(gameMode == IcsPlayingBlack)
10292                         PlayIcsLossSound();
10293                     break;
10294                 case BlackWins:
10295                     if (gameMode == IcsPlayingBlack)
10296                         PlayIcsWinSound();
10297                     else if(gameMode == IcsPlayingWhite)
10298                         PlayIcsLossSound();
10299                     break;
10300                 case GameIsDrawn:
10301                     PlayIcsDrawSound();
10302                     break;
10303                 default:
10304                     PlayIcsUnfinishedSound();
10305                 }
10306             }
10307         } else if (gameMode == EditGame ||
10308                    gameMode == PlayFromGameFile ||
10309                    gameMode == AnalyzeMode ||
10310                    gameMode == AnalyzeFile) {
10311             nextGameMode = gameMode;
10312         } else {
10313             nextGameMode = EndOfGame;
10314         }
10315         pausing = FALSE;
10316         ModeHighlight();
10317     } else {
10318         nextGameMode = gameMode;
10319     }
10320
10321     if (appData.noChessProgram) {
10322         gameMode = nextGameMode;
10323         ModeHighlight();
10324         endingGame = 0; /* [HGM] crash */
10325         return;
10326     }
10327
10328     if (first.reuse) {
10329         /* Put first chess program into idle state */
10330         if (first.pr != NoProc &&
10331             (gameMode == MachinePlaysWhite ||
10332              gameMode == MachinePlaysBlack ||
10333              gameMode == TwoMachinesPlay ||
10334              gameMode == IcsPlayingWhite ||
10335              gameMode == IcsPlayingBlack ||
10336              gameMode == BeginningOfGame)) {
10337             SendToProgram("force\n", &first);
10338             if (first.usePing) {
10339               char buf[MSG_SIZ];
10340               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10341               SendToProgram(buf, &first);
10342             }
10343         }
10344     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10345         /* Kill off first chess program */
10346         if (first.isr != NULL)
10347           RemoveInputSource(first.isr);
10348         first.isr = NULL;
10349
10350         if (first.pr != NoProc) {
10351             ExitAnalyzeMode();
10352             DoSleep( appData.delayBeforeQuit );
10353             SendToProgram("quit\n", &first);
10354             DoSleep( appData.delayAfterQuit );
10355             DestroyChildProcess(first.pr, first.useSigterm);
10356         }
10357         first.pr = NoProc;
10358     }
10359     if (second.reuse) {
10360         /* Put second chess program into idle state */
10361         if (second.pr != NoProc &&
10362             gameMode == TwoMachinesPlay) {
10363             SendToProgram("force\n", &second);
10364             if (second.usePing) {
10365               char buf[MSG_SIZ];
10366               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10367               SendToProgram(buf, &second);
10368             }
10369         }
10370     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10371         /* Kill off second chess program */
10372         if (second.isr != NULL)
10373           RemoveInputSource(second.isr);
10374         second.isr = NULL;
10375
10376         if (second.pr != NoProc) {
10377             DoSleep( appData.delayBeforeQuit );
10378             SendToProgram("quit\n", &second);
10379             DoSleep( appData.delayAfterQuit );
10380             DestroyChildProcess(second.pr, second.useSigterm);
10381         }
10382         second.pr = NoProc;
10383     }
10384
10385     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10386         char resChar = '=';
10387         switch (result) {
10388         case WhiteWins:
10389           resChar = '+';
10390           if (first.twoMachinesColor[0] == 'w') {
10391             first.matchWins++;
10392           } else {
10393             second.matchWins++;
10394           }
10395           break;
10396         case BlackWins:
10397           resChar = '-';
10398           if (first.twoMachinesColor[0] == 'b') {
10399             first.matchWins++;
10400           } else {
10401             second.matchWins++;
10402           }
10403           break;
10404         case GameUnfinished:
10405           resChar = ' ';
10406         default:
10407           break;
10408         }
10409
10410         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10411         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10412             ReserveGame(nextGame, resChar); // sets nextGame
10413             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10414             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10415         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10416
10417         if (nextGame <= appData.matchGames && !abortMatch) {
10418             gameMode = nextGameMode;
10419             matchGame = nextGame; // this will be overruled in tourney mode!
10420             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10421             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10422             endingGame = 0; /* [HGM] crash */
10423             return;
10424         } else {
10425             gameMode = nextGameMode;
10426             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10427                      first.tidy, second.tidy,
10428                      first.matchWins, second.matchWins,
10429                      appData.matchGames - (first.matchWins + second.matchWins));
10430             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10431             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10432             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10433                 first.twoMachinesColor = "black\n";
10434                 second.twoMachinesColor = "white\n";
10435             } else {
10436                 first.twoMachinesColor = "white\n";
10437                 second.twoMachinesColor = "black\n";
10438             }
10439         }
10440     }
10441     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10442         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10443       ExitAnalyzeMode();
10444     gameMode = nextGameMode;
10445     ModeHighlight();
10446     endingGame = 0;  /* [HGM] crash */
10447     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10448         if(matchMode == TRUE) { // match through command line: exit with or without popup
10449             if(ranking) {
10450                 ToNrEvent(forwardMostMove);
10451                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10452                 else ExitEvent(0);
10453             } else DisplayFatalError(buf, 0, 0);
10454         } else { // match through menu; just stop, with or without popup
10455             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10456             ModeHighlight();
10457             if(ranking){
10458                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10459             } else DisplayNote(buf);
10460       }
10461       if(ranking) free(ranking);
10462     }
10463 }
10464
10465 /* Assumes program was just initialized (initString sent).
10466    Leaves program in force mode. */
10467 void
10468 FeedMovesToProgram(cps, upto)
10469      ChessProgramState *cps;
10470      int upto;
10471 {
10472     int i;
10473
10474     if (appData.debugMode)
10475       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10476               startedFromSetupPosition ? "position and " : "",
10477               backwardMostMove, upto, cps->which);
10478     if(currentlyInitializedVariant != gameInfo.variant) {
10479       char buf[MSG_SIZ];
10480         // [HGM] variantswitch: make engine aware of new variant
10481         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10482                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10483         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10484         SendToProgram(buf, cps);
10485         currentlyInitializedVariant = gameInfo.variant;
10486     }
10487     SendToProgram("force\n", cps);
10488     if (startedFromSetupPosition) {
10489         SendBoard(cps, backwardMostMove);
10490     if (appData.debugMode) {
10491         fprintf(debugFP, "feedMoves\n");
10492     }
10493     }
10494     for (i = backwardMostMove; i < upto; i++) {
10495         SendMoveToProgram(i, cps);
10496     }
10497 }
10498
10499
10500 int
10501 ResurrectChessProgram()
10502 {
10503      /* The chess program may have exited.
10504         If so, restart it and feed it all the moves made so far. */
10505     static int doInit = 0;
10506
10507     if (appData.noChessProgram) return 1;
10508
10509     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10510         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10511         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10512         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10513     } else {
10514         if (first.pr != NoProc) return 1;
10515         StartChessProgram(&first);
10516     }
10517     InitChessProgram(&first, FALSE);
10518     FeedMovesToProgram(&first, currentMove);
10519
10520     if (!first.sendTime) {
10521         /* can't tell gnuchess what its clock should read,
10522            so we bow to its notion. */
10523         ResetClocks();
10524         timeRemaining[0][currentMove] = whiteTimeRemaining;
10525         timeRemaining[1][currentMove] = blackTimeRemaining;
10526     }
10527
10528     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10529                 appData.icsEngineAnalyze) && first.analysisSupport) {
10530       SendToProgram("analyze\n", &first);
10531       first.analyzing = TRUE;
10532     }
10533     return 1;
10534 }
10535
10536 /*
10537  * Button procedures
10538  */
10539 void
10540 Reset(redraw, init)
10541      int redraw, init;
10542 {
10543     int i;
10544
10545     if (appData.debugMode) {
10546         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10547                 redraw, init, gameMode);
10548     }
10549     CleanupTail(); // [HGM] vari: delete any stored variations
10550     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10551     pausing = pauseExamInvalid = FALSE;
10552     startedFromSetupPosition = blackPlaysFirst = FALSE;
10553     firstMove = TRUE;
10554     whiteFlag = blackFlag = FALSE;
10555     userOfferedDraw = FALSE;
10556     hintRequested = bookRequested = FALSE;
10557     first.maybeThinking = FALSE;
10558     second.maybeThinking = FALSE;
10559     first.bookSuspend = FALSE; // [HGM] book
10560     second.bookSuspend = FALSE;
10561     thinkOutput[0] = NULLCHAR;
10562     lastHint[0] = NULLCHAR;
10563     ClearGameInfo(&gameInfo);
10564     gameInfo.variant = StringToVariant(appData.variant);
10565     ics_user_moved = ics_clock_paused = FALSE;
10566     ics_getting_history = H_FALSE;
10567     ics_gamenum = -1;
10568     white_holding[0] = black_holding[0] = NULLCHAR;
10569     ClearProgramStats();
10570     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10571
10572     ResetFrontEnd();
10573     ClearHighlights();
10574     flipView = appData.flipView;
10575     ClearPremoveHighlights();
10576     gotPremove = FALSE;
10577     alarmSounded = FALSE;
10578
10579     GameEnds(EndOfFile, NULL, GE_PLAYER);
10580     if(appData.serverMovesName != NULL) {
10581         /* [HGM] prepare to make moves file for broadcasting */
10582         clock_t t = clock();
10583         if(serverMoves != NULL) fclose(serverMoves);
10584         serverMoves = fopen(appData.serverMovesName, "r");
10585         if(serverMoves != NULL) {
10586             fclose(serverMoves);
10587             /* delay 15 sec before overwriting, so all clients can see end */
10588             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10589         }
10590         serverMoves = fopen(appData.serverMovesName, "w");
10591     }
10592
10593     ExitAnalyzeMode();
10594     gameMode = BeginningOfGame;
10595     ModeHighlight();
10596     if(appData.icsActive) gameInfo.variant = VariantNormal;
10597     currentMove = forwardMostMove = backwardMostMove = 0;
10598     InitPosition(redraw);
10599     for (i = 0; i < MAX_MOVES; i++) {
10600         if (commentList[i] != NULL) {
10601             free(commentList[i]);
10602             commentList[i] = NULL;
10603         }
10604     }
10605     ResetClocks();
10606     timeRemaining[0][0] = whiteTimeRemaining;
10607     timeRemaining[1][0] = blackTimeRemaining;
10608
10609     if (first.pr == NULL) {
10610         StartChessProgram(&first);
10611     }
10612     if (init) {
10613             InitChessProgram(&first, startedFromSetupPosition);
10614     }
10615     DisplayTitle("");
10616     DisplayMessage("", "");
10617     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10618     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10619 }
10620
10621 void
10622 AutoPlayGameLoop()
10623 {
10624     for (;;) {
10625         if (!AutoPlayOneMove())
10626           return;
10627         if (matchMode || appData.timeDelay == 0)
10628           continue;
10629         if (appData.timeDelay < 0)
10630           return;
10631         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10632         break;
10633     }
10634 }
10635
10636
10637 int
10638 AutoPlayOneMove()
10639 {
10640     int fromX, fromY, toX, toY;
10641
10642     if (appData.debugMode) {
10643       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10644     }
10645
10646     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10647       return FALSE;
10648
10649     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10650       pvInfoList[currentMove].depth = programStats.depth;
10651       pvInfoList[currentMove].score = programStats.score;
10652       pvInfoList[currentMove].time  = 0;
10653       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10654     }
10655
10656     if (currentMove >= forwardMostMove) {
10657       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10658 //      gameMode = EndOfGame;
10659 //      ModeHighlight();
10660
10661       /* [AS] Clear current move marker at the end of a game */
10662       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10663
10664       return FALSE;
10665     }
10666
10667     toX = moveList[currentMove][2] - AAA;
10668     toY = moveList[currentMove][3] - ONE;
10669
10670     if (moveList[currentMove][1] == '@') {
10671         if (appData.highlightLastMove) {
10672             SetHighlights(-1, -1, toX, toY);
10673         }
10674     } else {
10675         fromX = moveList[currentMove][0] - AAA;
10676         fromY = moveList[currentMove][1] - ONE;
10677
10678         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10679
10680         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10681
10682         if (appData.highlightLastMove) {
10683             SetHighlights(fromX, fromY, toX, toY);
10684         }
10685     }
10686     DisplayMove(currentMove);
10687     SendMoveToProgram(currentMove++, &first);
10688     DisplayBothClocks();
10689     DrawPosition(FALSE, boards[currentMove]);
10690     // [HGM] PV info: always display, routine tests if empty
10691     DisplayComment(currentMove - 1, commentList[currentMove]);
10692     return TRUE;
10693 }
10694
10695
10696 int
10697 LoadGameOneMove(readAhead)
10698      ChessMove readAhead;
10699 {
10700     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10701     char promoChar = NULLCHAR;
10702     ChessMove moveType;
10703     char move[MSG_SIZ];
10704     char *p, *q;
10705
10706     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10707         gameMode != AnalyzeMode && gameMode != Training) {
10708         gameFileFP = NULL;
10709         return FALSE;
10710     }
10711
10712     yyboardindex = forwardMostMove;
10713     if (readAhead != EndOfFile) {
10714       moveType = readAhead;
10715     } else {
10716       if (gameFileFP == NULL)
10717           return FALSE;
10718       moveType = (ChessMove) Myylex();
10719     }
10720
10721     done = FALSE;
10722     switch (moveType) {
10723       case Comment:
10724         if (appData.debugMode)
10725           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10726         p = yy_text;
10727
10728         /* append the comment but don't display it */
10729         AppendComment(currentMove, p, FALSE);
10730         return TRUE;
10731
10732       case WhiteCapturesEnPassant:
10733       case BlackCapturesEnPassant:
10734       case WhitePromotion:
10735       case BlackPromotion:
10736       case WhiteNonPromotion:
10737       case BlackNonPromotion:
10738       case NormalMove:
10739       case WhiteKingSideCastle:
10740       case WhiteQueenSideCastle:
10741       case BlackKingSideCastle:
10742       case BlackQueenSideCastle:
10743       case WhiteKingSideCastleWild:
10744       case WhiteQueenSideCastleWild:
10745       case BlackKingSideCastleWild:
10746       case BlackQueenSideCastleWild:
10747       /* PUSH Fabien */
10748       case WhiteHSideCastleFR:
10749       case WhiteASideCastleFR:
10750       case BlackHSideCastleFR:
10751       case BlackASideCastleFR:
10752       /* POP Fabien */
10753         if (appData.debugMode)
10754           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10755         fromX = currentMoveString[0] - AAA;
10756         fromY = currentMoveString[1] - ONE;
10757         toX = currentMoveString[2] - AAA;
10758         toY = currentMoveString[3] - ONE;
10759         promoChar = currentMoveString[4];
10760         break;
10761
10762       case WhiteDrop:
10763       case BlackDrop:
10764         if (appData.debugMode)
10765           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10766         fromX = moveType == WhiteDrop ?
10767           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10768         (int) CharToPiece(ToLower(currentMoveString[0]));
10769         fromY = DROP_RANK;
10770         toX = currentMoveString[2] - AAA;
10771         toY = currentMoveString[3] - ONE;
10772         break;
10773
10774       case WhiteWins:
10775       case BlackWins:
10776       case GameIsDrawn:
10777       case GameUnfinished:
10778         if (appData.debugMode)
10779           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10780         p = strchr(yy_text, '{');
10781         if (p == NULL) p = strchr(yy_text, '(');
10782         if (p == NULL) {
10783             p = yy_text;
10784             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10785         } else {
10786             q = strchr(p, *p == '{' ? '}' : ')');
10787             if (q != NULL) *q = NULLCHAR;
10788             p++;
10789         }
10790         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10791         GameEnds(moveType, p, GE_FILE);
10792         done = TRUE;
10793         if (cmailMsgLoaded) {
10794             ClearHighlights();
10795             flipView = WhiteOnMove(currentMove);
10796             if (moveType == GameUnfinished) flipView = !flipView;
10797             if (appData.debugMode)
10798               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10799         }
10800         break;
10801
10802       case EndOfFile:
10803         if (appData.debugMode)
10804           fprintf(debugFP, "Parser hit end of file\n");
10805         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10806           case MT_NONE:
10807           case MT_CHECK:
10808             break;
10809           case MT_CHECKMATE:
10810           case MT_STAINMATE:
10811             if (WhiteOnMove(currentMove)) {
10812                 GameEnds(BlackWins, "Black mates", GE_FILE);
10813             } else {
10814                 GameEnds(WhiteWins, "White mates", GE_FILE);
10815             }
10816             break;
10817           case MT_STALEMATE:
10818             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10819             break;
10820         }
10821         done = TRUE;
10822         break;
10823
10824       case MoveNumberOne:
10825         if (lastLoadGameStart == GNUChessGame) {
10826             /* GNUChessGames have numbers, but they aren't move numbers */
10827             if (appData.debugMode)
10828               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10829                       yy_text, (int) moveType);
10830             return LoadGameOneMove(EndOfFile); /* tail recursion */
10831         }
10832         /* else fall thru */
10833
10834       case XBoardGame:
10835       case GNUChessGame:
10836       case PGNTag:
10837         /* Reached start of next game in file */
10838         if (appData.debugMode)
10839           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10840         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10841           case MT_NONE:
10842           case MT_CHECK:
10843             break;
10844           case MT_CHECKMATE:
10845           case MT_STAINMATE:
10846             if (WhiteOnMove(currentMove)) {
10847                 GameEnds(BlackWins, "Black mates", GE_FILE);
10848             } else {
10849                 GameEnds(WhiteWins, "White mates", GE_FILE);
10850             }
10851             break;
10852           case MT_STALEMATE:
10853             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10854             break;
10855         }
10856         done = TRUE;
10857         break;
10858
10859       case PositionDiagram:     /* should not happen; ignore */
10860       case ElapsedTime:         /* ignore */
10861       case NAG:                 /* ignore */
10862         if (appData.debugMode)
10863           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10864                   yy_text, (int) moveType);
10865         return LoadGameOneMove(EndOfFile); /* tail recursion */
10866
10867       case IllegalMove:
10868         if (appData.testLegality) {
10869             if (appData.debugMode)
10870               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10871             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10872                     (forwardMostMove / 2) + 1,
10873                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10874             DisplayError(move, 0);
10875             done = TRUE;
10876         } else {
10877             if (appData.debugMode)
10878               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10879                       yy_text, currentMoveString);
10880             fromX = currentMoveString[0] - AAA;
10881             fromY = currentMoveString[1] - ONE;
10882             toX = currentMoveString[2] - AAA;
10883             toY = currentMoveString[3] - ONE;
10884             promoChar = currentMoveString[4];
10885         }
10886         break;
10887
10888       case AmbiguousMove:
10889         if (appData.debugMode)
10890           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10891         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10892                 (forwardMostMove / 2) + 1,
10893                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10894         DisplayError(move, 0);
10895         done = TRUE;
10896         break;
10897
10898       default:
10899       case ImpossibleMove:
10900         if (appData.debugMode)
10901           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10902         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10903                 (forwardMostMove / 2) + 1,
10904                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10905         DisplayError(move, 0);
10906         done = TRUE;
10907         break;
10908     }
10909
10910     if (done) {
10911         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10912             DrawPosition(FALSE, boards[currentMove]);
10913             DisplayBothClocks();
10914             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10915               DisplayComment(currentMove - 1, commentList[currentMove]);
10916         }
10917         (void) StopLoadGameTimer();
10918         gameFileFP = NULL;
10919         cmailOldMove = forwardMostMove;
10920         return FALSE;
10921     } else {
10922         /* currentMoveString is set as a side-effect of yylex */
10923
10924         thinkOutput[0] = NULLCHAR;
10925         MakeMove(fromX, fromY, toX, toY, promoChar);
10926         currentMove = forwardMostMove;
10927         return TRUE;
10928     }
10929 }
10930
10931 /* Load the nth game from the given file */
10932 int
10933 LoadGameFromFile(filename, n, title, useList)
10934      char *filename;
10935      int n;
10936      char *title;
10937      /*Boolean*/ int useList;
10938 {
10939     FILE *f;
10940     char buf[MSG_SIZ];
10941
10942     if (strcmp(filename, "-") == 0) {
10943         f = stdin;
10944         title = "stdin";
10945     } else {
10946         f = fopen(filename, "rb");
10947         if (f == NULL) {
10948           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10949             DisplayError(buf, errno);
10950             return FALSE;
10951         }
10952     }
10953     if (fseek(f, 0, 0) == -1) {
10954         /* f is not seekable; probably a pipe */
10955         useList = FALSE;
10956     }
10957     if (useList && n == 0) {
10958         int error = GameListBuild(f);
10959         if (error) {
10960             DisplayError(_("Cannot build game list"), error);
10961         } else if (!ListEmpty(&gameList) &&
10962                    ((ListGame *) gameList.tailPred)->number > 1) {
10963             GameListPopUp(f, title);
10964             return TRUE;
10965         }
10966         GameListDestroy();
10967         n = 1;
10968     }
10969     if (n == 0) n = 1;
10970     return LoadGame(f, n, title, FALSE);
10971 }
10972
10973
10974 void
10975 MakeRegisteredMove()
10976 {
10977     int fromX, fromY, toX, toY;
10978     char promoChar;
10979     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10980         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10981           case CMAIL_MOVE:
10982           case CMAIL_DRAW:
10983             if (appData.debugMode)
10984               fprintf(debugFP, "Restoring %s for game %d\n",
10985                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10986
10987             thinkOutput[0] = NULLCHAR;
10988             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10989             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10990             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10991             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10992             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10993             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10994             MakeMove(fromX, fromY, toX, toY, promoChar);
10995             ShowMove(fromX, fromY, toX, toY);
10996
10997             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10998               case MT_NONE:
10999               case MT_CHECK:
11000                 break;
11001
11002               case MT_CHECKMATE:
11003               case MT_STAINMATE:
11004                 if (WhiteOnMove(currentMove)) {
11005                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11006                 } else {
11007                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11008                 }
11009                 break;
11010
11011               case MT_STALEMATE:
11012                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11013                 break;
11014             }
11015
11016             break;
11017
11018           case CMAIL_RESIGN:
11019             if (WhiteOnMove(currentMove)) {
11020                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11021             } else {
11022                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11023             }
11024             break;
11025
11026           case CMAIL_ACCEPT:
11027             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11028             break;
11029
11030           default:
11031             break;
11032         }
11033     }
11034
11035     return;
11036 }
11037
11038 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11039 int
11040 CmailLoadGame(f, gameNumber, title, useList)
11041      FILE *f;
11042      int gameNumber;
11043      char *title;
11044      int useList;
11045 {
11046     int retVal;
11047
11048     if (gameNumber > nCmailGames) {
11049         DisplayError(_("No more games in this message"), 0);
11050         return FALSE;
11051     }
11052     if (f == lastLoadGameFP) {
11053         int offset = gameNumber - lastLoadGameNumber;
11054         if (offset == 0) {
11055             cmailMsg[0] = NULLCHAR;
11056             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11057                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11058                 nCmailMovesRegistered--;
11059             }
11060             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11061             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11062                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11063             }
11064         } else {
11065             if (! RegisterMove()) return FALSE;
11066         }
11067     }
11068
11069     retVal = LoadGame(f, gameNumber, title, useList);
11070
11071     /* Make move registered during previous look at this game, if any */
11072     MakeRegisteredMove();
11073
11074     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11075         commentList[currentMove]
11076           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11077         DisplayComment(currentMove - 1, commentList[currentMove]);
11078     }
11079
11080     return retVal;
11081 }
11082
11083 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11084 int
11085 ReloadGame(offset)
11086      int offset;
11087 {
11088     int gameNumber = lastLoadGameNumber + offset;
11089     if (lastLoadGameFP == NULL) {
11090         DisplayError(_("No game has been loaded yet"), 0);
11091         return FALSE;
11092     }
11093     if (gameNumber <= 0) {
11094         DisplayError(_("Can't back up any further"), 0);
11095         return FALSE;
11096     }
11097     if (cmailMsgLoaded) {
11098         return CmailLoadGame(lastLoadGameFP, gameNumber,
11099                              lastLoadGameTitle, lastLoadGameUseList);
11100     } else {
11101         return LoadGame(lastLoadGameFP, gameNumber,
11102                         lastLoadGameTitle, lastLoadGameUseList);
11103     }
11104 }
11105
11106 int keys[EmptySquare+1];
11107
11108 int
11109 PositionMatches(Board b1, Board b2)
11110 {
11111     int r, f, sum=0;
11112     switch(appData.searchMode) {
11113         case 1: return CompareWithRights(b1, b2);
11114         case 2:
11115             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11116                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11117             }
11118             return TRUE;
11119         case 3:
11120             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11121               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11122                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11123             }
11124             return sum==0;
11125         case 4:
11126             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11127                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11128             }
11129             return sum==0;
11130     }
11131     return TRUE;
11132 }
11133
11134 GameInfo dummyInfo;
11135
11136 int GameContainsPosition(FILE *f, ListGame *lg)
11137 {
11138     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11139     int fromX, fromY, toX, toY;
11140     char promoChar;
11141     static int initDone=FALSE;
11142
11143     if(!initDone) {
11144         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11145         initDone = TRUE;
11146     }
11147     dummyInfo.variant = VariantNormal;
11148     FREE(dummyInfo.fen); dummyInfo.fen = NULL;
11149     dummyInfo.whiteRating = 0;
11150     dummyInfo.blackRating = 0;
11151     FREE(dummyInfo.date); dummyInfo.date = NULL;
11152     fseek(f, lg->offset, 0);
11153     yynewfile(f);
11154     CopyBoard(boards[scratch], initialPosition); // default start position
11155     while(1) {
11156         yyboardindex = scratch + (plyNr&1);
11157       quickFlag = 1;
11158         next = Myylex();
11159       quickFlag = 0;
11160         switch(next) {
11161             case PGNTag:
11162                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11163 #if 0
11164                 ParsePGNTag(yy_text, &dummyInfo); // this has a bad memory leak...
11165                 if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL;
11166 #else
11167                 // do it ourselves avoiding malloc
11168                 { char *p = yy_text+1, *q;
11169                   while(!isdigit(*p) && !isalpha(*p)) p++;
11170                   q  = p; while(*p != ' ' && *p != '\t' && *p != '\n') p++;
11171                   *p = NULLCHAR;
11172                   if(!StrCaseCmp(q, "Date") && (p = strchr(p+1, '"'))) { if(atoi(p+1) < appData.dateThreshold) return -1; } else
11173                   if(!StrCaseCmp(q, "Variant")  &&  (p = strchr(p+1, '"'))) dummyInfo.variant = StringToVariant(p+1); else
11174                   if(!StrCaseCmp(q, "WhiteElo")  && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11175                   if(!StrCaseCmp(q, "BlackElo")  && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11176                   if(!StrCaseCmp(q, "WhiteUSCF") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11177                   if(!StrCaseCmp(q, "BlackUSCF") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11178                   if(!StrCaseCmp(q, "FEN")  && (p = strchr(p+1, '"'))) ParseFEN(boards[scratch], &btm, p+1);
11179                 }
11180 #endif
11181             default:
11182                 continue;
11183
11184             case XBoardGame:
11185             case GNUChessGame:
11186                 if(plyNr) return -1; // after we have seen moves, this is for new game
11187               continue;
11188
11189             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11190             case ImpossibleMove:
11191             case WhiteWins: // game ends here with these four
11192             case BlackWins:
11193             case GameIsDrawn:
11194             case GameUnfinished:
11195                 return -1;
11196
11197             case IllegalMove:
11198                 if(appData.testLegality) return -1;
11199             case WhiteCapturesEnPassant:
11200             case BlackCapturesEnPassant:
11201             case WhitePromotion:
11202             case BlackPromotion:
11203             case WhiteNonPromotion:
11204             case BlackNonPromotion:
11205             case NormalMove:
11206             case WhiteKingSideCastle:
11207             case WhiteQueenSideCastle:
11208             case BlackKingSideCastle:
11209             case BlackQueenSideCastle:
11210             case WhiteKingSideCastleWild:
11211             case WhiteQueenSideCastleWild:
11212             case BlackKingSideCastleWild:
11213             case BlackQueenSideCastleWild:
11214             case WhiteHSideCastleFR:
11215             case WhiteASideCastleFR:
11216             case BlackHSideCastleFR:
11217             case BlackASideCastleFR:
11218                 fromX = currentMoveString[0] - AAA;
11219                 fromY = currentMoveString[1] - ONE;
11220                 toX = currentMoveString[2] - AAA;
11221                 toY = currentMoveString[3] - ONE;
11222                 promoChar = currentMoveString[4];
11223                 break;
11224             case WhiteDrop:
11225             case BlackDrop:
11226                 fromX = next == WhiteDrop ?
11227                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11228                   (int) CharToPiece(ToLower(currentMoveString[0]));
11229                 fromY = DROP_RANK;
11230                 toX = currentMoveString[2] - AAA;
11231                 toY = currentMoveString[3] - ONE;
11232                 break;
11233         }
11234         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11235         if(plyNr == 0) { // but first figure out variant and initial position
11236             if(dummyInfo.variant != gameInfo.variant) return -1; // wrong variant
11237             if(appData.eloThreshold1 && (dummyInfo.whiteRating < appData.eloThreshold1 && dummyInfo.blackRating < appData.eloThreshold1)) return -1;
11238             if(appData.eloThreshold2 && (dummyInfo.whiteRating < appData.eloThreshold2 || dummyInfo.blackRating < appData.eloThreshold2)) return -1;
11239             if(appData.dateThreshold && (!dummyInfo.date || atoi(dummyInfo.date) < appData.dateThreshold)) return -1;
11240             if(btm) CopyBoard(boards[scratch+1], boards[scratch]), plyNr++;
11241             if(PositionMatches(boards[scratch + plyNr], boards[currentMove])) return plyNr;
11242         }
11243         CopyBoard(boards[scratch + (plyNr+1&1)], boards[scratch + (plyNr&1)]);
11244         plyNr++;
11245         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch + (plyNr&1)]);
11246         if(PositionMatches(boards[scratch + (plyNr&1)], boards[currentMove])) return plyNr;
11247     }
11248 }
11249
11250 /* Load the nth game from open file f */
11251 int
11252 LoadGame(f, gameNumber, title, useList)
11253      FILE *f;
11254      int gameNumber;
11255      char *title;
11256      int useList;
11257 {
11258     ChessMove cm;
11259     char buf[MSG_SIZ];
11260     int gn = gameNumber;
11261     ListGame *lg = NULL;
11262     int numPGNTags = 0;
11263     int err, pos = -1;
11264     GameMode oldGameMode;
11265     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11266
11267     if (appData.debugMode)
11268         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11269
11270     if (gameMode == Training )
11271         SetTrainingModeOff();
11272
11273     oldGameMode = gameMode;
11274     if (gameMode != BeginningOfGame) {
11275       Reset(FALSE, TRUE);
11276     }
11277
11278     gameFileFP = f;
11279     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11280         fclose(lastLoadGameFP);
11281     }
11282
11283     if (useList) {
11284         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11285
11286         if (lg) {
11287             fseek(f, lg->offset, 0);
11288             GameListHighlight(gameNumber);
11289             pos = lg->position;
11290             gn = 1;
11291         }
11292         else {
11293             DisplayError(_("Game number out of range"), 0);
11294             return FALSE;
11295         }
11296     } else {
11297         GameListDestroy();
11298         if (fseek(f, 0, 0) == -1) {
11299             if (f == lastLoadGameFP ?
11300                 gameNumber == lastLoadGameNumber + 1 :
11301                 gameNumber == 1) {
11302                 gn = 1;
11303             } else {
11304                 DisplayError(_("Can't seek on game file"), 0);
11305                 return FALSE;
11306             }
11307         }
11308     }
11309     lastLoadGameFP = f;
11310     lastLoadGameNumber = gameNumber;
11311     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11312     lastLoadGameUseList = useList;
11313
11314     yynewfile(f);
11315
11316     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11317       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11318                 lg->gameInfo.black);
11319             DisplayTitle(buf);
11320     } else if (*title != NULLCHAR) {
11321         if (gameNumber > 1) {
11322           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11323             DisplayTitle(buf);
11324         } else {
11325             DisplayTitle(title);
11326         }
11327     }
11328
11329     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11330         gameMode = PlayFromGameFile;
11331         ModeHighlight();
11332     }
11333
11334     currentMove = forwardMostMove = backwardMostMove = 0;
11335     CopyBoard(boards[0], initialPosition);
11336     StopClocks();
11337
11338     /*
11339      * Skip the first gn-1 games in the file.
11340      * Also skip over anything that precedes an identifiable
11341      * start of game marker, to avoid being confused by
11342      * garbage at the start of the file.  Currently
11343      * recognized start of game markers are the move number "1",
11344      * the pattern "gnuchess .* game", the pattern
11345      * "^[#;%] [^ ]* game file", and a PGN tag block.
11346      * A game that starts with one of the latter two patterns
11347      * will also have a move number 1, possibly
11348      * following a position diagram.
11349      * 5-4-02: Let's try being more lenient and allowing a game to
11350      * start with an unnumbered move.  Does that break anything?
11351      */
11352     cm = lastLoadGameStart = EndOfFile;
11353     while (gn > 0) {
11354         yyboardindex = forwardMostMove;
11355         cm = (ChessMove) Myylex();
11356         switch (cm) {
11357           case EndOfFile:
11358             if (cmailMsgLoaded) {
11359                 nCmailGames = CMAIL_MAX_GAMES - gn;
11360             } else {
11361                 Reset(TRUE, TRUE);
11362                 DisplayError(_("Game not found in file"), 0);
11363             }
11364             return FALSE;
11365
11366           case GNUChessGame:
11367           case XBoardGame:
11368             gn--;
11369             lastLoadGameStart = cm;
11370             break;
11371
11372           case MoveNumberOne:
11373             switch (lastLoadGameStart) {
11374               case GNUChessGame:
11375               case XBoardGame:
11376               case PGNTag:
11377                 break;
11378               case MoveNumberOne:
11379               case EndOfFile:
11380                 gn--;           /* count this game */
11381                 lastLoadGameStart = cm;
11382                 break;
11383               default:
11384                 /* impossible */
11385                 break;
11386             }
11387             break;
11388
11389           case PGNTag:
11390             switch (lastLoadGameStart) {
11391               case GNUChessGame:
11392               case PGNTag:
11393               case MoveNumberOne:
11394               case EndOfFile:
11395                 gn--;           /* count this game */
11396                 lastLoadGameStart = cm;
11397                 break;
11398               case XBoardGame:
11399                 lastLoadGameStart = cm; /* game counted already */
11400                 break;
11401               default:
11402                 /* impossible */
11403                 break;
11404             }
11405             if (gn > 0) {
11406                 do {
11407                     yyboardindex = forwardMostMove;
11408                     cm = (ChessMove) Myylex();
11409                 } while (cm == PGNTag || cm == Comment);
11410             }
11411             break;
11412
11413           case WhiteWins:
11414           case BlackWins:
11415           case GameIsDrawn:
11416             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11417                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11418                     != CMAIL_OLD_RESULT) {
11419                     nCmailResults ++ ;
11420                     cmailResult[  CMAIL_MAX_GAMES
11421                                 - gn - 1] = CMAIL_OLD_RESULT;
11422                 }
11423             }
11424             break;
11425
11426           case NormalMove:
11427             /* Only a NormalMove can be at the start of a game
11428              * without a position diagram. */
11429             if (lastLoadGameStart == EndOfFile ) {
11430               gn--;
11431               lastLoadGameStart = MoveNumberOne;
11432             }
11433             break;
11434
11435           default:
11436             break;
11437         }
11438     }
11439
11440     if (appData.debugMode)
11441       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11442
11443     if (cm == XBoardGame) {
11444         /* Skip any header junk before position diagram and/or move 1 */
11445         for (;;) {
11446             yyboardindex = forwardMostMove;
11447             cm = (ChessMove) Myylex();
11448
11449             if (cm == EndOfFile ||
11450                 cm == GNUChessGame || cm == XBoardGame) {
11451                 /* Empty game; pretend end-of-file and handle later */
11452                 cm = EndOfFile;
11453                 break;
11454             }
11455
11456             if (cm == MoveNumberOne || cm == PositionDiagram ||
11457                 cm == PGNTag || cm == Comment)
11458               break;
11459         }
11460     } else if (cm == GNUChessGame) {
11461         if (gameInfo.event != NULL) {
11462             free(gameInfo.event);
11463         }
11464         gameInfo.event = StrSave(yy_text);
11465     }
11466
11467     startedFromSetupPosition = FALSE;
11468     while (cm == PGNTag) {
11469         if (appData.debugMode)
11470           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11471         err = ParsePGNTag(yy_text, &gameInfo);
11472         if (!err) numPGNTags++;
11473
11474         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11475         if(gameInfo.variant != oldVariant) {
11476             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11477             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11478             InitPosition(TRUE);
11479             oldVariant = gameInfo.variant;
11480             if (appData.debugMode)
11481               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11482         }
11483
11484
11485         if (gameInfo.fen != NULL) {
11486           Board initial_position;
11487           startedFromSetupPosition = TRUE;
11488           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11489             Reset(TRUE, TRUE);
11490             DisplayError(_("Bad FEN position in file"), 0);
11491             return FALSE;
11492           }
11493           CopyBoard(boards[0], initial_position);
11494           if (blackPlaysFirst) {
11495             currentMove = forwardMostMove = backwardMostMove = 1;
11496             CopyBoard(boards[1], initial_position);
11497             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11498             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11499             timeRemaining[0][1] = whiteTimeRemaining;
11500             timeRemaining[1][1] = blackTimeRemaining;
11501             if (commentList[0] != NULL) {
11502               commentList[1] = commentList[0];
11503               commentList[0] = NULL;
11504             }
11505           } else {
11506             currentMove = forwardMostMove = backwardMostMove = 0;
11507           }
11508           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11509           {   int i;
11510               initialRulePlies = FENrulePlies;
11511               for( i=0; i< nrCastlingRights; i++ )
11512                   initialRights[i] = initial_position[CASTLING][i];
11513           }
11514           yyboardindex = forwardMostMove;
11515           free(gameInfo.fen);
11516           gameInfo.fen = NULL;
11517         }
11518
11519         yyboardindex = forwardMostMove;
11520         cm = (ChessMove) Myylex();
11521
11522         /* Handle comments interspersed among the tags */
11523         while (cm == Comment) {
11524             char *p;
11525             if (appData.debugMode)
11526               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11527             p = yy_text;
11528             AppendComment(currentMove, p, FALSE);
11529             yyboardindex = forwardMostMove;
11530             cm = (ChessMove) Myylex();
11531         }
11532     }
11533
11534     /* don't rely on existence of Event tag since if game was
11535      * pasted from clipboard the Event tag may not exist
11536      */
11537     if (numPGNTags > 0){
11538         char *tags;
11539         if (gameInfo.variant == VariantNormal) {
11540           VariantClass v = StringToVariant(gameInfo.event);
11541           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11542           if(v < VariantShogi) gameInfo.variant = v;
11543         }
11544         if (!matchMode) {
11545           if( appData.autoDisplayTags ) {
11546             tags = PGNTags(&gameInfo);
11547             TagsPopUp(tags, CmailMsg());
11548             free(tags);
11549           }
11550         }
11551     } else {
11552         /* Make something up, but don't display it now */
11553         SetGameInfo();
11554         TagsPopDown();
11555     }
11556
11557     if (cm == PositionDiagram) {
11558         int i, j;
11559         char *p;
11560         Board initial_position;
11561
11562         if (appData.debugMode)
11563           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11564
11565         if (!startedFromSetupPosition) {
11566             p = yy_text;
11567             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11568               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11569                 switch (*p) {
11570                   case '{':
11571                   case '[':
11572                   case '-':
11573                   case ' ':
11574                   case '\t':
11575                   case '\n':
11576                   case '\r':
11577                     break;
11578                   default:
11579                     initial_position[i][j++] = CharToPiece(*p);
11580                     break;
11581                 }
11582             while (*p == ' ' || *p == '\t' ||
11583                    *p == '\n' || *p == '\r') p++;
11584
11585             if (strncmp(p, "black", strlen("black"))==0)
11586               blackPlaysFirst = TRUE;
11587             else
11588               blackPlaysFirst = FALSE;
11589             startedFromSetupPosition = TRUE;
11590
11591             CopyBoard(boards[0], initial_position);
11592             if (blackPlaysFirst) {
11593                 currentMove = forwardMostMove = backwardMostMove = 1;
11594                 CopyBoard(boards[1], initial_position);
11595                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11596                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11597                 timeRemaining[0][1] = whiteTimeRemaining;
11598                 timeRemaining[1][1] = blackTimeRemaining;
11599                 if (commentList[0] != NULL) {
11600                     commentList[1] = commentList[0];
11601                     commentList[0] = NULL;
11602                 }
11603             } else {
11604                 currentMove = forwardMostMove = backwardMostMove = 0;
11605             }
11606         }
11607         yyboardindex = forwardMostMove;
11608         cm = (ChessMove) Myylex();
11609     }
11610
11611     if (first.pr == NoProc) {
11612         StartChessProgram(&first);
11613     }
11614     InitChessProgram(&first, FALSE);
11615     SendToProgram("force\n", &first);
11616     if (startedFromSetupPosition) {
11617         SendBoard(&first, forwardMostMove);
11618     if (appData.debugMode) {
11619         fprintf(debugFP, "Load Game\n");
11620     }
11621         DisplayBothClocks();
11622     }
11623
11624     /* [HGM] server: flag to write setup moves in broadcast file as one */
11625     loadFlag = appData.suppressLoadMoves;
11626
11627     while (cm == Comment) {
11628         char *p;
11629         if (appData.debugMode)
11630           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11631         p = yy_text;
11632         AppendComment(currentMove, p, FALSE);
11633         yyboardindex = forwardMostMove;
11634         cm = (ChessMove) Myylex();
11635     }
11636
11637     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11638         cm == WhiteWins || cm == BlackWins ||
11639         cm == GameIsDrawn || cm == GameUnfinished) {
11640         DisplayMessage("", _("No moves in game"));
11641         if (cmailMsgLoaded) {
11642             if (appData.debugMode)
11643               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11644             ClearHighlights();
11645             flipView = FALSE;
11646         }
11647         DrawPosition(FALSE, boards[currentMove]);
11648         DisplayBothClocks();
11649         gameMode = EditGame;
11650         ModeHighlight();
11651         gameFileFP = NULL;
11652         cmailOldMove = 0;
11653         return TRUE;
11654     }
11655
11656     // [HGM] PV info: routine tests if comment empty
11657     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11658         DisplayComment(currentMove - 1, commentList[currentMove]);
11659     }
11660     if (!matchMode && appData.timeDelay != 0)
11661       DrawPosition(FALSE, boards[currentMove]);
11662
11663     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11664       programStats.ok_to_send = 1;
11665     }
11666
11667     /* if the first token after the PGN tags is a move
11668      * and not move number 1, retrieve it from the parser
11669      */
11670     if (cm != MoveNumberOne)
11671         LoadGameOneMove(cm);
11672
11673     /* load the remaining moves from the file */
11674     while (LoadGameOneMove(EndOfFile)) {
11675       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11676       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11677     }
11678
11679     /* rewind to the start of the game */
11680     currentMove = backwardMostMove;
11681
11682     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11683
11684     if (oldGameMode == AnalyzeFile ||
11685         oldGameMode == AnalyzeMode) {
11686       AnalyzeFileEvent();
11687     }
11688
11689     if (!matchMode && pos >= 0) {
11690         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11691     } else
11692     if (matchMode || appData.timeDelay == 0) {
11693       ToEndEvent();
11694     } else if (appData.timeDelay > 0) {
11695       AutoPlayGameLoop();
11696     }
11697
11698     if (appData.debugMode)
11699         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11700
11701     loadFlag = 0; /* [HGM] true game starts */
11702     return TRUE;
11703 }
11704
11705 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11706 int
11707 ReloadPosition(offset)
11708      int offset;
11709 {
11710     int positionNumber = lastLoadPositionNumber + offset;
11711     if (lastLoadPositionFP == NULL) {
11712         DisplayError(_("No position has been loaded yet"), 0);
11713         return FALSE;
11714     }
11715     if (positionNumber <= 0) {
11716         DisplayError(_("Can't back up any further"), 0);
11717         return FALSE;
11718     }
11719     return LoadPosition(lastLoadPositionFP, positionNumber,
11720                         lastLoadPositionTitle);
11721 }
11722
11723 /* Load the nth position from the given file */
11724 int
11725 LoadPositionFromFile(filename, n, title)
11726      char *filename;
11727      int n;
11728      char *title;
11729 {
11730     FILE *f;
11731     char buf[MSG_SIZ];
11732
11733     if (strcmp(filename, "-") == 0) {
11734         return LoadPosition(stdin, n, "stdin");
11735     } else {
11736         f = fopen(filename, "rb");
11737         if (f == NULL) {
11738             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11739             DisplayError(buf, errno);
11740             return FALSE;
11741         } else {
11742             return LoadPosition(f, n, title);
11743         }
11744     }
11745 }
11746
11747 /* Load the nth position from the given open file, and close it */
11748 int
11749 LoadPosition(f, positionNumber, title)
11750      FILE *f;
11751      int positionNumber;
11752      char *title;
11753 {
11754     char *p, line[MSG_SIZ];
11755     Board initial_position;
11756     int i, j, fenMode, pn;
11757
11758     if (gameMode == Training )
11759         SetTrainingModeOff();
11760
11761     if (gameMode != BeginningOfGame) {
11762         Reset(FALSE, TRUE);
11763     }
11764     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11765         fclose(lastLoadPositionFP);
11766     }
11767     if (positionNumber == 0) positionNumber = 1;
11768     lastLoadPositionFP = f;
11769     lastLoadPositionNumber = positionNumber;
11770     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11771     if (first.pr == NoProc) {
11772       StartChessProgram(&first);
11773       InitChessProgram(&first, FALSE);
11774     }
11775     pn = positionNumber;
11776     if (positionNumber < 0) {
11777         /* Negative position number means to seek to that byte offset */
11778         if (fseek(f, -positionNumber, 0) == -1) {
11779             DisplayError(_("Can't seek on position file"), 0);
11780             return FALSE;
11781         };
11782         pn = 1;
11783     } else {
11784         if (fseek(f, 0, 0) == -1) {
11785             if (f == lastLoadPositionFP ?
11786                 positionNumber == lastLoadPositionNumber + 1 :
11787                 positionNumber == 1) {
11788                 pn = 1;
11789             } else {
11790                 DisplayError(_("Can't seek on position file"), 0);
11791                 return FALSE;
11792             }
11793         }
11794     }
11795     /* See if this file is FEN or old-style xboard */
11796     if (fgets(line, MSG_SIZ, f) == NULL) {
11797         DisplayError(_("Position not found in file"), 0);
11798         return FALSE;
11799     }
11800     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11801     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11802
11803     if (pn >= 2) {
11804         if (fenMode || line[0] == '#') pn--;
11805         while (pn > 0) {
11806             /* skip positions before number pn */
11807             if (fgets(line, MSG_SIZ, f) == NULL) {
11808                 Reset(TRUE, TRUE);
11809                 DisplayError(_("Position not found in file"), 0);
11810                 return FALSE;
11811             }
11812             if (fenMode || line[0] == '#') pn--;
11813         }
11814     }
11815
11816     if (fenMode) {
11817         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11818             DisplayError(_("Bad FEN position in file"), 0);
11819             return FALSE;
11820         }
11821     } else {
11822         (void) fgets(line, MSG_SIZ, f);
11823         (void) fgets(line, MSG_SIZ, f);
11824
11825         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11826             (void) fgets(line, MSG_SIZ, f);
11827             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11828                 if (*p == ' ')
11829                   continue;
11830                 initial_position[i][j++] = CharToPiece(*p);
11831             }
11832         }
11833
11834         blackPlaysFirst = FALSE;
11835         if (!feof(f)) {
11836             (void) fgets(line, MSG_SIZ, f);
11837             if (strncmp(line, "black", strlen("black"))==0)
11838               blackPlaysFirst = TRUE;
11839         }
11840     }
11841     startedFromSetupPosition = TRUE;
11842
11843     SendToProgram("force\n", &first);
11844     CopyBoard(boards[0], initial_position);
11845     if (blackPlaysFirst) {
11846         currentMove = forwardMostMove = backwardMostMove = 1;
11847         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11848         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11849         CopyBoard(boards[1], initial_position);
11850         DisplayMessage("", _("Black to play"));
11851     } else {
11852         currentMove = forwardMostMove = backwardMostMove = 0;
11853         DisplayMessage("", _("White to play"));
11854     }
11855     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11856     SendBoard(&first, forwardMostMove);
11857     if (appData.debugMode) {
11858 int i, j;
11859   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11860   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11861         fprintf(debugFP, "Load Position\n");
11862     }
11863
11864     if (positionNumber > 1) {
11865       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11866         DisplayTitle(line);
11867     } else {
11868         DisplayTitle(title);
11869     }
11870     gameMode = EditGame;
11871     ModeHighlight();
11872     ResetClocks();
11873     timeRemaining[0][1] = whiteTimeRemaining;
11874     timeRemaining[1][1] = blackTimeRemaining;
11875     DrawPosition(FALSE, boards[currentMove]);
11876
11877     return TRUE;
11878 }
11879
11880
11881 void
11882 CopyPlayerNameIntoFileName(dest, src)
11883      char **dest, *src;
11884 {
11885     while (*src != NULLCHAR && *src != ',') {
11886         if (*src == ' ') {
11887             *(*dest)++ = '_';
11888             src++;
11889         } else {
11890             *(*dest)++ = *src++;
11891         }
11892     }
11893 }
11894
11895 char *DefaultFileName(ext)
11896      char *ext;
11897 {
11898     static char def[MSG_SIZ];
11899     char *p;
11900
11901     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11902         p = def;
11903         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11904         *p++ = '-';
11905         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11906         *p++ = '.';
11907         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11908     } else {
11909         def[0] = NULLCHAR;
11910     }
11911     return def;
11912 }
11913
11914 /* Save the current game to the given file */
11915 int
11916 SaveGameToFile(filename, append)
11917      char *filename;
11918      int append;
11919 {
11920     FILE *f;
11921     char buf[MSG_SIZ];
11922     int result;
11923
11924     if (strcmp(filename, "-") == 0) {
11925         return SaveGame(stdout, 0, NULL);
11926     } else {
11927         f = fopen(filename, append ? "a" : "w");
11928         if (f == NULL) {
11929             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11930             DisplayError(buf, errno);
11931             return FALSE;
11932         } else {
11933             safeStrCpy(buf, lastMsg, MSG_SIZ);
11934             DisplayMessage(_("Waiting for access to save file"), "");
11935             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11936             DisplayMessage(_("Saving game"), "");
11937             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11938             result = SaveGame(f, 0, NULL);
11939             DisplayMessage(buf, "");
11940             return result;
11941         }
11942     }
11943 }
11944
11945 char *
11946 SavePart(str)
11947      char *str;
11948 {
11949     static char buf[MSG_SIZ];
11950     char *p;
11951
11952     p = strchr(str, ' ');
11953     if (p == NULL) return str;
11954     strncpy(buf, str, p - str);
11955     buf[p - str] = NULLCHAR;
11956     return buf;
11957 }
11958
11959 #define PGN_MAX_LINE 75
11960
11961 #define PGN_SIDE_WHITE  0
11962 #define PGN_SIDE_BLACK  1
11963
11964 /* [AS] */
11965 static int FindFirstMoveOutOfBook( int side )
11966 {
11967     int result = -1;
11968
11969     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11970         int index = backwardMostMove;
11971         int has_book_hit = 0;
11972
11973         if( (index % 2) != side ) {
11974             index++;
11975         }
11976
11977         while( index < forwardMostMove ) {
11978             /* Check to see if engine is in book */
11979             int depth = pvInfoList[index].depth;
11980             int score = pvInfoList[index].score;
11981             int in_book = 0;
11982
11983             if( depth <= 2 ) {
11984                 in_book = 1;
11985             }
11986             else if( score == 0 && depth == 63 ) {
11987                 in_book = 1; /* Zappa */
11988             }
11989             else if( score == 2 && depth == 99 ) {
11990                 in_book = 1; /* Abrok */
11991             }
11992
11993             has_book_hit += in_book;
11994
11995             if( ! in_book ) {
11996                 result = index;
11997
11998                 break;
11999             }
12000
12001             index += 2;
12002         }
12003     }
12004
12005     return result;
12006 }
12007
12008 /* [AS] */
12009 void GetOutOfBookInfo( char * buf )
12010 {
12011     int oob[2];
12012     int i;
12013     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12014
12015     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12016     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12017
12018     *buf = '\0';
12019
12020     if( oob[0] >= 0 || oob[1] >= 0 ) {
12021         for( i=0; i<2; i++ ) {
12022             int idx = oob[i];
12023
12024             if( idx >= 0 ) {
12025                 if( i > 0 && oob[0] >= 0 ) {
12026                     strcat( buf, "   " );
12027                 }
12028
12029                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12030                 sprintf( buf+strlen(buf), "%s%.2f",
12031                     pvInfoList[idx].score >= 0 ? "+" : "",
12032                     pvInfoList[idx].score / 100.0 );
12033             }
12034         }
12035     }
12036 }
12037
12038 /* Save game in PGN style and close the file */
12039 int
12040 SaveGamePGN(f)
12041      FILE *f;
12042 {
12043     int i, offset, linelen, newblock;
12044     time_t tm;
12045 //    char *movetext;
12046     char numtext[32];
12047     int movelen, numlen, blank;
12048     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12049
12050     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12051
12052     tm = time((time_t *) NULL);
12053
12054     PrintPGNTags(f, &gameInfo);
12055
12056     if (backwardMostMove > 0 || startedFromSetupPosition) {
12057         char *fen = PositionToFEN(backwardMostMove, NULL);
12058         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12059         fprintf(f, "\n{--------------\n");
12060         PrintPosition(f, backwardMostMove);
12061         fprintf(f, "--------------}\n");
12062         free(fen);
12063     }
12064     else {
12065         /* [AS] Out of book annotation */
12066         if( appData.saveOutOfBookInfo ) {
12067             char buf[64];
12068
12069             GetOutOfBookInfo( buf );
12070
12071             if( buf[0] != '\0' ) {
12072                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12073             }
12074         }
12075
12076         fprintf(f, "\n");
12077     }
12078
12079     i = backwardMostMove;
12080     linelen = 0;
12081     newblock = TRUE;
12082
12083     while (i < forwardMostMove) {
12084         /* Print comments preceding this move */
12085         if (commentList[i] != NULL) {
12086             if (linelen > 0) fprintf(f, "\n");
12087             fprintf(f, "%s", commentList[i]);
12088             linelen = 0;
12089             newblock = TRUE;
12090         }
12091
12092         /* Format move number */
12093         if ((i % 2) == 0)
12094           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12095         else
12096           if (newblock)
12097             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12098           else
12099             numtext[0] = NULLCHAR;
12100
12101         numlen = strlen(numtext);
12102         newblock = FALSE;
12103
12104         /* Print move number */
12105         blank = linelen > 0 && numlen > 0;
12106         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12107             fprintf(f, "\n");
12108             linelen = 0;
12109             blank = 0;
12110         }
12111         if (blank) {
12112             fprintf(f, " ");
12113             linelen++;
12114         }
12115         fprintf(f, "%s", numtext);
12116         linelen += numlen;
12117
12118         /* Get move */
12119         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12120         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12121
12122         /* Print move */
12123         blank = linelen > 0 && movelen > 0;
12124         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12125             fprintf(f, "\n");
12126             linelen = 0;
12127             blank = 0;
12128         }
12129         if (blank) {
12130             fprintf(f, " ");
12131             linelen++;
12132         }
12133         fprintf(f, "%s", move_buffer);
12134         linelen += movelen;
12135
12136         /* [AS] Add PV info if present */
12137         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12138             /* [HGM] add time */
12139             char buf[MSG_SIZ]; int seconds;
12140
12141             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12142
12143             if( seconds <= 0)
12144               buf[0] = 0;
12145             else
12146               if( seconds < 30 )
12147                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12148               else
12149                 {
12150                   seconds = (seconds + 4)/10; // round to full seconds
12151                   if( seconds < 60 )
12152                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12153                   else
12154                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12155                 }
12156
12157             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12158                       pvInfoList[i].score >= 0 ? "+" : "",
12159                       pvInfoList[i].score / 100.0,
12160                       pvInfoList[i].depth,
12161                       buf );
12162
12163             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12164
12165             /* Print score/depth */
12166             blank = linelen > 0 && movelen > 0;
12167             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12168                 fprintf(f, "\n");
12169                 linelen = 0;
12170                 blank = 0;
12171             }
12172             if (blank) {
12173                 fprintf(f, " ");
12174                 linelen++;
12175             }
12176             fprintf(f, "%s", move_buffer);
12177             linelen += movelen;
12178         }
12179
12180         i++;
12181     }
12182
12183     /* Start a new line */
12184     if (linelen > 0) fprintf(f, "\n");
12185
12186     /* Print comments after last move */
12187     if (commentList[i] != NULL) {
12188         fprintf(f, "%s\n", commentList[i]);
12189     }
12190
12191     /* Print result */
12192     if (gameInfo.resultDetails != NULL &&
12193         gameInfo.resultDetails[0] != NULLCHAR) {
12194         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12195                 PGNResult(gameInfo.result));
12196     } else {
12197         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12198     }
12199
12200     fclose(f);
12201     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12202     return TRUE;
12203 }
12204
12205 /* Save game in old style and close the file */
12206 int
12207 SaveGameOldStyle(f)
12208      FILE *f;
12209 {
12210     int i, offset;
12211     time_t tm;
12212
12213     tm = time((time_t *) NULL);
12214
12215     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12216     PrintOpponents(f);
12217
12218     if (backwardMostMove > 0 || startedFromSetupPosition) {
12219         fprintf(f, "\n[--------------\n");
12220         PrintPosition(f, backwardMostMove);
12221         fprintf(f, "--------------]\n");
12222     } else {
12223         fprintf(f, "\n");
12224     }
12225
12226     i = backwardMostMove;
12227     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12228
12229     while (i < forwardMostMove) {
12230         if (commentList[i] != NULL) {
12231             fprintf(f, "[%s]\n", commentList[i]);
12232         }
12233
12234         if ((i % 2) == 1) {
12235             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12236             i++;
12237         } else {
12238             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12239             i++;
12240             if (commentList[i] != NULL) {
12241                 fprintf(f, "\n");
12242                 continue;
12243             }
12244             if (i >= forwardMostMove) {
12245                 fprintf(f, "\n");
12246                 break;
12247             }
12248             fprintf(f, "%s\n", parseList[i]);
12249             i++;
12250         }
12251     }
12252
12253     if (commentList[i] != NULL) {
12254         fprintf(f, "[%s]\n", commentList[i]);
12255     }
12256
12257     /* This isn't really the old style, but it's close enough */
12258     if (gameInfo.resultDetails != NULL &&
12259         gameInfo.resultDetails[0] != NULLCHAR) {
12260         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12261                 gameInfo.resultDetails);
12262     } else {
12263         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12264     }
12265
12266     fclose(f);
12267     return TRUE;
12268 }
12269
12270 /* Save the current game to open file f and close the file */
12271 int
12272 SaveGame(f, dummy, dummy2)
12273      FILE *f;
12274      int dummy;
12275      char *dummy2;
12276 {
12277     if (gameMode == EditPosition) EditPositionDone(TRUE);
12278     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12279     if (appData.oldSaveStyle)
12280       return SaveGameOldStyle(f);
12281     else
12282       return SaveGamePGN(f);
12283 }
12284
12285 /* Save the current position to the given file */
12286 int
12287 SavePositionToFile(filename)
12288      char *filename;
12289 {
12290     FILE *f;
12291     char buf[MSG_SIZ];
12292
12293     if (strcmp(filename, "-") == 0) {
12294         return SavePosition(stdout, 0, NULL);
12295     } else {
12296         f = fopen(filename, "a");
12297         if (f == NULL) {
12298             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12299             DisplayError(buf, errno);
12300             return FALSE;
12301         } else {
12302             safeStrCpy(buf, lastMsg, MSG_SIZ);
12303             DisplayMessage(_("Waiting for access to save file"), "");
12304             flock(fileno(f), LOCK_EX); // [HGM] lock
12305             DisplayMessage(_("Saving position"), "");
12306             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12307             SavePosition(f, 0, NULL);
12308             DisplayMessage(buf, "");
12309             return TRUE;
12310         }
12311     }
12312 }
12313
12314 /* Save the current position to the given open file and close the file */
12315 int
12316 SavePosition(f, dummy, dummy2)
12317      FILE *f;
12318      int dummy;
12319      char *dummy2;
12320 {
12321     time_t tm;
12322     char *fen;
12323
12324     if (gameMode == EditPosition) EditPositionDone(TRUE);
12325     if (appData.oldSaveStyle) {
12326         tm = time((time_t *) NULL);
12327
12328         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12329         PrintOpponents(f);
12330         fprintf(f, "[--------------\n");
12331         PrintPosition(f, currentMove);
12332         fprintf(f, "--------------]\n");
12333     } else {
12334         fen = PositionToFEN(currentMove, NULL);
12335         fprintf(f, "%s\n", fen);
12336         free(fen);
12337     }
12338     fclose(f);
12339     return TRUE;
12340 }
12341
12342 void
12343 ReloadCmailMsgEvent(unregister)
12344      int unregister;
12345 {
12346 #if !WIN32
12347     static char *inFilename = NULL;
12348     static char *outFilename;
12349     int i;
12350     struct stat inbuf, outbuf;
12351     int status;
12352
12353     /* Any registered moves are unregistered if unregister is set, */
12354     /* i.e. invoked by the signal handler */
12355     if (unregister) {
12356         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12357             cmailMoveRegistered[i] = FALSE;
12358             if (cmailCommentList[i] != NULL) {
12359                 free(cmailCommentList[i]);
12360                 cmailCommentList[i] = NULL;
12361             }
12362         }
12363         nCmailMovesRegistered = 0;
12364     }
12365
12366     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12367         cmailResult[i] = CMAIL_NOT_RESULT;
12368     }
12369     nCmailResults = 0;
12370
12371     if (inFilename == NULL) {
12372         /* Because the filenames are static they only get malloced once  */
12373         /* and they never get freed                                      */
12374         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12375         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12376
12377         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12378         sprintf(outFilename, "%s.out", appData.cmailGameName);
12379     }
12380
12381     status = stat(outFilename, &outbuf);
12382     if (status < 0) {
12383         cmailMailedMove = FALSE;
12384     } else {
12385         status = stat(inFilename, &inbuf);
12386         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12387     }
12388
12389     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12390        counts the games, notes how each one terminated, etc.
12391
12392        It would be nice to remove this kludge and instead gather all
12393        the information while building the game list.  (And to keep it
12394        in the game list nodes instead of having a bunch of fixed-size
12395        parallel arrays.)  Note this will require getting each game's
12396        termination from the PGN tags, as the game list builder does
12397        not process the game moves.  --mann
12398        */
12399     cmailMsgLoaded = TRUE;
12400     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12401
12402     /* Load first game in the file or popup game menu */
12403     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12404
12405 #endif /* !WIN32 */
12406     return;
12407 }
12408
12409 int
12410 RegisterMove()
12411 {
12412     FILE *f;
12413     char string[MSG_SIZ];
12414
12415     if (   cmailMailedMove
12416         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12417         return TRUE;            /* Allow free viewing  */
12418     }
12419
12420     /* Unregister move to ensure that we don't leave RegisterMove        */
12421     /* with the move registered when the conditions for registering no   */
12422     /* longer hold                                                       */
12423     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12424         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12425         nCmailMovesRegistered --;
12426
12427         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12428           {
12429               free(cmailCommentList[lastLoadGameNumber - 1]);
12430               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12431           }
12432     }
12433
12434     if (cmailOldMove == -1) {
12435         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12436         return FALSE;
12437     }
12438
12439     if (currentMove > cmailOldMove + 1) {
12440         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12441         return FALSE;
12442     }
12443
12444     if (currentMove < cmailOldMove) {
12445         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12446         return FALSE;
12447     }
12448
12449     if (forwardMostMove > currentMove) {
12450         /* Silently truncate extra moves */
12451         TruncateGame();
12452     }
12453
12454     if (   (currentMove == cmailOldMove + 1)
12455         || (   (currentMove == cmailOldMove)
12456             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12457                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12458         if (gameInfo.result != GameUnfinished) {
12459             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12460         }
12461
12462         if (commentList[currentMove] != NULL) {
12463             cmailCommentList[lastLoadGameNumber - 1]
12464               = StrSave(commentList[currentMove]);
12465         }
12466         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12467
12468         if (appData.debugMode)
12469           fprintf(debugFP, "Saving %s for game %d\n",
12470                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12471
12472         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12473
12474         f = fopen(string, "w");
12475         if (appData.oldSaveStyle) {
12476             SaveGameOldStyle(f); /* also closes the file */
12477
12478             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12479             f = fopen(string, "w");
12480             SavePosition(f, 0, NULL); /* also closes the file */
12481         } else {
12482             fprintf(f, "{--------------\n");
12483             PrintPosition(f, currentMove);
12484             fprintf(f, "--------------}\n\n");
12485
12486             SaveGame(f, 0, NULL); /* also closes the file*/
12487         }
12488
12489         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12490         nCmailMovesRegistered ++;
12491     } else if (nCmailGames == 1) {
12492         DisplayError(_("You have not made a move yet"), 0);
12493         return FALSE;
12494     }
12495
12496     return TRUE;
12497 }
12498
12499 void
12500 MailMoveEvent()
12501 {
12502 #if !WIN32
12503     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12504     FILE *commandOutput;
12505     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12506     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12507     int nBuffers;
12508     int i;
12509     int archived;
12510     char *arcDir;
12511
12512     if (! cmailMsgLoaded) {
12513         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12514         return;
12515     }
12516
12517     if (nCmailGames == nCmailResults) {
12518         DisplayError(_("No unfinished games"), 0);
12519         return;
12520     }
12521
12522 #if CMAIL_PROHIBIT_REMAIL
12523     if (cmailMailedMove) {
12524       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);
12525         DisplayError(msg, 0);
12526         return;
12527     }
12528 #endif
12529
12530     if (! (cmailMailedMove || RegisterMove())) return;
12531
12532     if (   cmailMailedMove
12533         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12534       snprintf(string, MSG_SIZ, partCommandString,
12535                appData.debugMode ? " -v" : "", appData.cmailGameName);
12536         commandOutput = popen(string, "r");
12537
12538         if (commandOutput == NULL) {
12539             DisplayError(_("Failed to invoke cmail"), 0);
12540         } else {
12541             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12542                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12543             }
12544             if (nBuffers > 1) {
12545                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12546                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12547                 nBytes = MSG_SIZ - 1;
12548             } else {
12549                 (void) memcpy(msg, buffer, nBytes);
12550             }
12551             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12552
12553             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12554                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12555
12556                 archived = TRUE;
12557                 for (i = 0; i < nCmailGames; i ++) {
12558                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12559                         archived = FALSE;
12560                     }
12561                 }
12562                 if (   archived
12563                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12564                         != NULL)) {
12565                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12566                            arcDir,
12567                            appData.cmailGameName,
12568                            gameInfo.date);
12569                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12570                     cmailMsgLoaded = FALSE;
12571                 }
12572             }
12573
12574             DisplayInformation(msg);
12575             pclose(commandOutput);
12576         }
12577     } else {
12578         if ((*cmailMsg) != '\0') {
12579             DisplayInformation(cmailMsg);
12580         }
12581     }
12582
12583     return;
12584 #endif /* !WIN32 */
12585 }
12586
12587 char *
12588 CmailMsg()
12589 {
12590 #if WIN32
12591     return NULL;
12592 #else
12593     int  prependComma = 0;
12594     char number[5];
12595     char string[MSG_SIZ];       /* Space for game-list */
12596     int  i;
12597
12598     if (!cmailMsgLoaded) return "";
12599
12600     if (cmailMailedMove) {
12601       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12602     } else {
12603         /* Create a list of games left */
12604       snprintf(string, MSG_SIZ, "[");
12605         for (i = 0; i < nCmailGames; i ++) {
12606             if (! (   cmailMoveRegistered[i]
12607                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12608                 if (prependComma) {
12609                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12610                 } else {
12611                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12612                     prependComma = 1;
12613                 }
12614
12615                 strcat(string, number);
12616             }
12617         }
12618         strcat(string, "]");
12619
12620         if (nCmailMovesRegistered + nCmailResults == 0) {
12621             switch (nCmailGames) {
12622               case 1:
12623                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12624                 break;
12625
12626               case 2:
12627                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12628                 break;
12629
12630               default:
12631                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12632                          nCmailGames);
12633                 break;
12634             }
12635         } else {
12636             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12637               case 1:
12638                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12639                          string);
12640                 break;
12641
12642               case 0:
12643                 if (nCmailResults == nCmailGames) {
12644                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12645                 } else {
12646                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12647                 }
12648                 break;
12649
12650               default:
12651                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12652                          string);
12653             }
12654         }
12655     }
12656     return cmailMsg;
12657 #endif /* WIN32 */
12658 }
12659
12660 void
12661 ResetGameEvent()
12662 {
12663     if (gameMode == Training)
12664       SetTrainingModeOff();
12665
12666     Reset(TRUE, TRUE);
12667     cmailMsgLoaded = FALSE;
12668     if (appData.icsActive) {
12669       SendToICS(ics_prefix);
12670       SendToICS("refresh\n");
12671     }
12672 }
12673
12674 void
12675 ExitEvent(status)
12676      int status;
12677 {
12678     exiting++;
12679     if (exiting > 2) {
12680       /* Give up on clean exit */
12681       exit(status);
12682     }
12683     if (exiting > 1) {
12684       /* Keep trying for clean exit */
12685       return;
12686     }
12687
12688     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12689
12690     if (telnetISR != NULL) {
12691       RemoveInputSource(telnetISR);
12692     }
12693     if (icsPR != NoProc) {
12694       DestroyChildProcess(icsPR, TRUE);
12695     }
12696
12697     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12698     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12699
12700     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12701     /* make sure this other one finishes before killing it!                  */
12702     if(endingGame) { int count = 0;
12703         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12704         while(endingGame && count++ < 10) DoSleep(1);
12705         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12706     }
12707
12708     /* Kill off chess programs */
12709     if (first.pr != NoProc) {
12710         ExitAnalyzeMode();
12711
12712         DoSleep( appData.delayBeforeQuit );
12713         SendToProgram("quit\n", &first);
12714         DoSleep( appData.delayAfterQuit );
12715         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12716     }
12717     if (second.pr != NoProc) {
12718         DoSleep( appData.delayBeforeQuit );
12719         SendToProgram("quit\n", &second);
12720         DoSleep( appData.delayAfterQuit );
12721         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12722     }
12723     if (first.isr != NULL) {
12724         RemoveInputSource(first.isr);
12725     }
12726     if (second.isr != NULL) {
12727         RemoveInputSource(second.isr);
12728     }
12729
12730     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12731     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12732
12733     ShutDownFrontEnd();
12734     exit(status);
12735 }
12736
12737 void
12738 PauseEvent()
12739 {
12740     if (appData.debugMode)
12741         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12742     if (pausing) {
12743         pausing = FALSE;
12744         ModeHighlight();
12745         if (gameMode == MachinePlaysWhite ||
12746             gameMode == MachinePlaysBlack) {
12747             StartClocks();
12748         } else {
12749             DisplayBothClocks();
12750         }
12751         if (gameMode == PlayFromGameFile) {
12752             if (appData.timeDelay >= 0)
12753                 AutoPlayGameLoop();
12754         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12755             Reset(FALSE, TRUE);
12756             SendToICS(ics_prefix);
12757             SendToICS("refresh\n");
12758         } else if (currentMove < forwardMostMove) {
12759             ForwardInner(forwardMostMove);
12760         }
12761         pauseExamInvalid = FALSE;
12762     } else {
12763         switch (gameMode) {
12764           default:
12765             return;
12766           case IcsExamining:
12767             pauseExamForwardMostMove = forwardMostMove;
12768             pauseExamInvalid = FALSE;
12769             /* fall through */
12770           case IcsObserving:
12771           case IcsPlayingWhite:
12772           case IcsPlayingBlack:
12773             pausing = TRUE;
12774             ModeHighlight();
12775             return;
12776           case PlayFromGameFile:
12777             (void) StopLoadGameTimer();
12778             pausing = TRUE;
12779             ModeHighlight();
12780             break;
12781           case BeginningOfGame:
12782             if (appData.icsActive) return;
12783             /* else fall through */
12784           case MachinePlaysWhite:
12785           case MachinePlaysBlack:
12786           case TwoMachinesPlay:
12787             if (forwardMostMove == 0)
12788               return;           /* don't pause if no one has moved */
12789             if ((gameMode == MachinePlaysWhite &&
12790                  !WhiteOnMove(forwardMostMove)) ||
12791                 (gameMode == MachinePlaysBlack &&
12792                  WhiteOnMove(forwardMostMove))) {
12793                 StopClocks();
12794             }
12795             pausing = TRUE;
12796             ModeHighlight();
12797             break;
12798         }
12799     }
12800 }
12801
12802 void
12803 EditCommentEvent()
12804 {
12805     char title[MSG_SIZ];
12806
12807     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12808       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12809     } else {
12810       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12811                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12812                parseList[currentMove - 1]);
12813     }
12814
12815     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12816 }
12817
12818
12819 void
12820 EditTagsEvent()
12821 {
12822     char *tags = PGNTags(&gameInfo);
12823     bookUp = FALSE;
12824     EditTagsPopUp(tags, NULL);
12825     free(tags);
12826 }
12827
12828 void
12829 AnalyzeModeEvent()
12830 {
12831     if (appData.noChessProgram || gameMode == AnalyzeMode)
12832       return;
12833
12834     if (gameMode != AnalyzeFile) {
12835         if (!appData.icsEngineAnalyze) {
12836                EditGameEvent();
12837                if (gameMode != EditGame) return;
12838         }
12839         ResurrectChessProgram();
12840         SendToProgram("analyze\n", &first);
12841         first.analyzing = TRUE;
12842         /*first.maybeThinking = TRUE;*/
12843         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12844         EngineOutputPopUp();
12845     }
12846     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12847     pausing = FALSE;
12848     ModeHighlight();
12849     SetGameInfo();
12850
12851     StartAnalysisClock();
12852     GetTimeMark(&lastNodeCountTime);
12853     lastNodeCount = 0;
12854 }
12855
12856 void
12857 AnalyzeFileEvent()
12858 {
12859     if (appData.noChessProgram || gameMode == AnalyzeFile)
12860       return;
12861
12862     if (gameMode != AnalyzeMode) {
12863         EditGameEvent();
12864         if (gameMode != EditGame) return;
12865         ResurrectChessProgram();
12866         SendToProgram("analyze\n", &first);
12867         first.analyzing = TRUE;
12868         /*first.maybeThinking = TRUE;*/
12869         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12870         EngineOutputPopUp();
12871     }
12872     gameMode = AnalyzeFile;
12873     pausing = FALSE;
12874     ModeHighlight();
12875     SetGameInfo();
12876
12877     StartAnalysisClock();
12878     GetTimeMark(&lastNodeCountTime);
12879     lastNodeCount = 0;
12880 }
12881
12882 void
12883 MachineWhiteEvent()
12884 {
12885     char buf[MSG_SIZ];
12886     char *bookHit = NULL;
12887
12888     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12889       return;
12890
12891
12892     if (gameMode == PlayFromGameFile ||
12893         gameMode == TwoMachinesPlay  ||
12894         gameMode == Training         ||
12895         gameMode == AnalyzeMode      ||
12896         gameMode == EndOfGame)
12897         EditGameEvent();
12898
12899     if (gameMode == EditPosition)
12900         EditPositionDone(TRUE);
12901
12902     if (!WhiteOnMove(currentMove)) {
12903         DisplayError(_("It is not White's turn"), 0);
12904         return;
12905     }
12906
12907     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12908       ExitAnalyzeMode();
12909
12910     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12911         gameMode == AnalyzeFile)
12912         TruncateGame();
12913
12914     ResurrectChessProgram();    /* in case it isn't running */
12915     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12916         gameMode = MachinePlaysWhite;
12917         ResetClocks();
12918     } else
12919     gameMode = MachinePlaysWhite;
12920     pausing = FALSE;
12921     ModeHighlight();
12922     SetGameInfo();
12923     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12924     DisplayTitle(buf);
12925     if (first.sendName) {
12926       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12927       SendToProgram(buf, &first);
12928     }
12929     if (first.sendTime) {
12930       if (first.useColors) {
12931         SendToProgram("black\n", &first); /*gnu kludge*/
12932       }
12933       SendTimeRemaining(&first, TRUE);
12934     }
12935     if (first.useColors) {
12936       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12937     }
12938     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12939     SetMachineThinkingEnables();
12940     first.maybeThinking = TRUE;
12941     StartClocks();
12942     firstMove = FALSE;
12943
12944     if (appData.autoFlipView && !flipView) {
12945       flipView = !flipView;
12946       DrawPosition(FALSE, NULL);
12947       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12948     }
12949
12950     if(bookHit) { // [HGM] book: simulate book reply
12951         static char bookMove[MSG_SIZ]; // a bit generous?
12952
12953         programStats.nodes = programStats.depth = programStats.time =
12954         programStats.score = programStats.got_only_move = 0;
12955         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12956
12957         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12958         strcat(bookMove, bookHit);
12959         HandleMachineMove(bookMove, &first);
12960     }
12961 }
12962
12963 void
12964 MachineBlackEvent()
12965 {
12966   char buf[MSG_SIZ];
12967   char *bookHit = NULL;
12968
12969     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12970         return;
12971
12972
12973     if (gameMode == PlayFromGameFile ||
12974         gameMode == TwoMachinesPlay  ||
12975         gameMode == Training         ||
12976         gameMode == AnalyzeMode      ||
12977         gameMode == EndOfGame)
12978         EditGameEvent();
12979
12980     if (gameMode == EditPosition)
12981         EditPositionDone(TRUE);
12982
12983     if (WhiteOnMove(currentMove)) {
12984         DisplayError(_("It is not Black's turn"), 0);
12985         return;
12986     }
12987
12988     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12989       ExitAnalyzeMode();
12990
12991     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12992         gameMode == AnalyzeFile)
12993         TruncateGame();
12994
12995     ResurrectChessProgram();    /* in case it isn't running */
12996     gameMode = MachinePlaysBlack;
12997     pausing = FALSE;
12998     ModeHighlight();
12999     SetGameInfo();
13000     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13001     DisplayTitle(buf);
13002     if (first.sendName) {
13003       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13004       SendToProgram(buf, &first);
13005     }
13006     if (first.sendTime) {
13007       if (first.useColors) {
13008         SendToProgram("white\n", &first); /*gnu kludge*/
13009       }
13010       SendTimeRemaining(&first, FALSE);
13011     }
13012     if (first.useColors) {
13013       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13014     }
13015     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13016     SetMachineThinkingEnables();
13017     first.maybeThinking = TRUE;
13018     StartClocks();
13019
13020     if (appData.autoFlipView && flipView) {
13021       flipView = !flipView;
13022       DrawPosition(FALSE, NULL);
13023       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13024     }
13025     if(bookHit) { // [HGM] book: simulate book reply
13026         static char bookMove[MSG_SIZ]; // a bit generous?
13027
13028         programStats.nodes = programStats.depth = programStats.time =
13029         programStats.score = programStats.got_only_move = 0;
13030         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13031
13032         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13033         strcat(bookMove, bookHit);
13034         HandleMachineMove(bookMove, &first);
13035     }
13036 }
13037
13038
13039 void
13040 DisplayTwoMachinesTitle()
13041 {
13042     char buf[MSG_SIZ];
13043     if (appData.matchGames > 0) {
13044         if(appData.tourneyFile[0]) {
13045           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13046                    gameInfo.white, gameInfo.black,
13047                    nextGame+1, appData.matchGames+1,
13048                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13049         } else 
13050         if (first.twoMachinesColor[0] == 'w') {
13051           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13052                    gameInfo.white, gameInfo.black,
13053                    first.matchWins, second.matchWins,
13054                    matchGame - 1 - (first.matchWins + second.matchWins));
13055         } else {
13056           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13057                    gameInfo.white, gameInfo.black,
13058                    second.matchWins, first.matchWins,
13059                    matchGame - 1 - (first.matchWins + second.matchWins));
13060         }
13061     } else {
13062       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13063     }
13064     DisplayTitle(buf);
13065 }
13066
13067 void
13068 SettingsMenuIfReady()
13069 {
13070   if (second.lastPing != second.lastPong) {
13071     DisplayMessage("", _("Waiting for second chess program"));
13072     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13073     return;
13074   }
13075   ThawUI();
13076   DisplayMessage("", "");
13077   SettingsPopUp(&second);
13078 }
13079
13080 int
13081 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13082 {
13083     char buf[MSG_SIZ];
13084     if (cps->pr == NULL) {
13085         StartChessProgram(cps);
13086         if (cps->protocolVersion == 1) {
13087           retry();
13088         } else {
13089           /* kludge: allow timeout for initial "feature" command */
13090           FreezeUI();
13091           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13092           DisplayMessage("", buf);
13093           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13094         }
13095         return 1;
13096     }
13097     return 0;
13098 }
13099
13100 void
13101 TwoMachinesEvent P((void))
13102 {
13103     int i;
13104     char buf[MSG_SIZ];
13105     ChessProgramState *onmove;
13106     char *bookHit = NULL;
13107     static int stalling = 0;
13108     TimeMark now;
13109     long wait;
13110
13111     if (appData.noChessProgram) return;
13112
13113     switch (gameMode) {
13114       case TwoMachinesPlay:
13115         return;
13116       case MachinePlaysWhite:
13117       case MachinePlaysBlack:
13118         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13119             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13120             return;
13121         }
13122         /* fall through */
13123       case BeginningOfGame:
13124       case PlayFromGameFile:
13125       case EndOfGame:
13126         EditGameEvent();
13127         if (gameMode != EditGame) return;
13128         break;
13129       case EditPosition:
13130         EditPositionDone(TRUE);
13131         break;
13132       case AnalyzeMode:
13133       case AnalyzeFile:
13134         ExitAnalyzeMode();
13135         break;
13136       case EditGame:
13137       default:
13138         break;
13139     }
13140
13141 //    forwardMostMove = currentMove;
13142     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13143
13144     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13145
13146     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13147     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13148       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13149       return;
13150     }
13151     if(!stalling) {
13152       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13153       SendToProgram("force\n", &second);
13154       stalling = 1;
13155       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13156       return;
13157     }
13158     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13159     if(appData.matchPause>10000 || appData.matchPause<10)
13160                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13161     wait = SubtractTimeMarks(&now, &pauseStart);
13162     if(wait < appData.matchPause) {
13163         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13164         return;
13165     }
13166     stalling = 0;
13167     DisplayMessage("", "");
13168     if (startedFromSetupPosition) {
13169         SendBoard(&second, backwardMostMove);
13170     if (appData.debugMode) {
13171         fprintf(debugFP, "Two Machines\n");
13172     }
13173     }
13174     for (i = backwardMostMove; i < forwardMostMove; i++) {
13175         SendMoveToProgram(i, &second);
13176     }
13177
13178     gameMode = TwoMachinesPlay;
13179     pausing = FALSE;
13180     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13181     SetGameInfo();
13182     DisplayTwoMachinesTitle();
13183     firstMove = TRUE;
13184     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13185         onmove = &first;
13186     } else {
13187         onmove = &second;
13188     }
13189     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13190     SendToProgram(first.computerString, &first);
13191     if (first.sendName) {
13192       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13193       SendToProgram(buf, &first);
13194     }
13195     SendToProgram(second.computerString, &second);
13196     if (second.sendName) {
13197       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13198       SendToProgram(buf, &second);
13199     }
13200
13201     ResetClocks();
13202     if (!first.sendTime || !second.sendTime) {
13203         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13204         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13205     }
13206     if (onmove->sendTime) {
13207       if (onmove->useColors) {
13208         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13209       }
13210       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13211     }
13212     if (onmove->useColors) {
13213       SendToProgram(onmove->twoMachinesColor, onmove);
13214     }
13215     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13216 //    SendToProgram("go\n", onmove);
13217     onmove->maybeThinking = TRUE;
13218     SetMachineThinkingEnables();
13219
13220     StartClocks();
13221
13222     if(bookHit) { // [HGM] book: simulate book reply
13223         static char bookMove[MSG_SIZ]; // a bit generous?
13224
13225         programStats.nodes = programStats.depth = programStats.time =
13226         programStats.score = programStats.got_only_move = 0;
13227         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13228
13229         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13230         strcat(bookMove, bookHit);
13231         savedMessage = bookMove; // args for deferred call
13232         savedState = onmove;
13233         ScheduleDelayedEvent(DeferredBookMove, 1);
13234     }
13235 }
13236
13237 void
13238 TrainingEvent()
13239 {
13240     if (gameMode == Training) {
13241       SetTrainingModeOff();
13242       gameMode = PlayFromGameFile;
13243       DisplayMessage("", _("Training mode off"));
13244     } else {
13245       gameMode = Training;
13246       animateTraining = appData.animate;
13247
13248       /* make sure we are not already at the end of the game */
13249       if (currentMove < forwardMostMove) {
13250         SetTrainingModeOn();
13251         DisplayMessage("", _("Training mode on"));
13252       } else {
13253         gameMode = PlayFromGameFile;
13254         DisplayError(_("Already at end of game"), 0);
13255       }
13256     }
13257     ModeHighlight();
13258 }
13259
13260 void
13261 IcsClientEvent()
13262 {
13263     if (!appData.icsActive) return;
13264     switch (gameMode) {
13265       case IcsPlayingWhite:
13266       case IcsPlayingBlack:
13267       case IcsObserving:
13268       case IcsIdle:
13269       case BeginningOfGame:
13270       case IcsExamining:
13271         return;
13272
13273       case EditGame:
13274         break;
13275
13276       case EditPosition:
13277         EditPositionDone(TRUE);
13278         break;
13279
13280       case AnalyzeMode:
13281       case AnalyzeFile:
13282         ExitAnalyzeMode();
13283         break;
13284
13285       default:
13286         EditGameEvent();
13287         break;
13288     }
13289
13290     gameMode = IcsIdle;
13291     ModeHighlight();
13292     return;
13293 }
13294
13295
13296 void
13297 EditGameEvent()
13298 {
13299     int i;
13300
13301     switch (gameMode) {
13302       case Training:
13303         SetTrainingModeOff();
13304         break;
13305       case MachinePlaysWhite:
13306       case MachinePlaysBlack:
13307       case BeginningOfGame:
13308         SendToProgram("force\n", &first);
13309         SetUserThinkingEnables();
13310         break;
13311       case PlayFromGameFile:
13312         (void) StopLoadGameTimer();
13313         if (gameFileFP != NULL) {
13314             gameFileFP = NULL;
13315         }
13316         break;
13317       case EditPosition:
13318         EditPositionDone(TRUE);
13319         break;
13320       case AnalyzeMode:
13321       case AnalyzeFile:
13322         ExitAnalyzeMode();
13323         SendToProgram("force\n", &first);
13324         break;
13325       case TwoMachinesPlay:
13326         GameEnds(EndOfFile, NULL, GE_PLAYER);
13327         ResurrectChessProgram();
13328         SetUserThinkingEnables();
13329         break;
13330       case EndOfGame:
13331         ResurrectChessProgram();
13332         break;
13333       case IcsPlayingBlack:
13334       case IcsPlayingWhite:
13335         DisplayError(_("Warning: You are still playing a game"), 0);
13336         break;
13337       case IcsObserving:
13338         DisplayError(_("Warning: You are still observing a game"), 0);
13339         break;
13340       case IcsExamining:
13341         DisplayError(_("Warning: You are still examining a game"), 0);
13342         break;
13343       case IcsIdle:
13344         break;
13345       case EditGame:
13346       default:
13347         return;
13348     }
13349
13350     pausing = FALSE;
13351     StopClocks();
13352     first.offeredDraw = second.offeredDraw = 0;
13353
13354     if (gameMode == PlayFromGameFile) {
13355         whiteTimeRemaining = timeRemaining[0][currentMove];
13356         blackTimeRemaining = timeRemaining[1][currentMove];
13357         DisplayTitle("");
13358     }
13359
13360     if (gameMode == MachinePlaysWhite ||
13361         gameMode == MachinePlaysBlack ||
13362         gameMode == TwoMachinesPlay ||
13363         gameMode == EndOfGame) {
13364         i = forwardMostMove;
13365         while (i > currentMove) {
13366             SendToProgram("undo\n", &first);
13367             i--;
13368         }
13369         whiteTimeRemaining = timeRemaining[0][currentMove];
13370         blackTimeRemaining = timeRemaining[1][currentMove];
13371         DisplayBothClocks();
13372         if (whiteFlag || blackFlag) {
13373             whiteFlag = blackFlag = 0;
13374         }
13375         DisplayTitle("");
13376     }
13377
13378     gameMode = EditGame;
13379     ModeHighlight();
13380     SetGameInfo();
13381 }
13382
13383
13384 void
13385 EditPositionEvent()
13386 {
13387     if (gameMode == EditPosition) {
13388         EditGameEvent();
13389         return;
13390     }
13391
13392     EditGameEvent();
13393     if (gameMode != EditGame) return;
13394
13395     gameMode = EditPosition;
13396     ModeHighlight();
13397     SetGameInfo();
13398     if (currentMove > 0)
13399       CopyBoard(boards[0], boards[currentMove]);
13400
13401     blackPlaysFirst = !WhiteOnMove(currentMove);
13402     ResetClocks();
13403     currentMove = forwardMostMove = backwardMostMove = 0;
13404     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13405     DisplayMove(-1);
13406 }
13407
13408 void
13409 ExitAnalyzeMode()
13410 {
13411     /* [DM] icsEngineAnalyze - possible call from other functions */
13412     if (appData.icsEngineAnalyze) {
13413         appData.icsEngineAnalyze = FALSE;
13414
13415         DisplayMessage("",_("Close ICS engine analyze..."));
13416     }
13417     if (first.analysisSupport && first.analyzing) {
13418       SendToProgram("exit\n", &first);
13419       first.analyzing = FALSE;
13420     }
13421     thinkOutput[0] = NULLCHAR;
13422 }
13423
13424 void
13425 EditPositionDone(Boolean fakeRights)
13426 {
13427     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13428
13429     startedFromSetupPosition = TRUE;
13430     InitChessProgram(&first, FALSE);
13431     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13432       boards[0][EP_STATUS] = EP_NONE;
13433       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13434     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13435         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13436         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13437       } else boards[0][CASTLING][2] = NoRights;
13438     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13439         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13440         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13441       } else boards[0][CASTLING][5] = NoRights;
13442     }
13443     SendToProgram("force\n", &first);
13444     if (blackPlaysFirst) {
13445         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13446         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13447         currentMove = forwardMostMove = backwardMostMove = 1;
13448         CopyBoard(boards[1], boards[0]);
13449     } else {
13450         currentMove = forwardMostMove = backwardMostMove = 0;
13451     }
13452     SendBoard(&first, forwardMostMove);
13453     if (appData.debugMode) {
13454         fprintf(debugFP, "EditPosDone\n");
13455     }
13456     DisplayTitle("");
13457     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13458     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13459     gameMode = EditGame;
13460     ModeHighlight();
13461     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13462     ClearHighlights(); /* [AS] */
13463 }
13464
13465 /* Pause for `ms' milliseconds */
13466 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13467 void
13468 TimeDelay(ms)
13469      long ms;
13470 {
13471     TimeMark m1, m2;
13472
13473     GetTimeMark(&m1);
13474     do {
13475         GetTimeMark(&m2);
13476     } while (SubtractTimeMarks(&m2, &m1) < ms);
13477 }
13478
13479 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13480 void
13481 SendMultiLineToICS(buf)
13482      char *buf;
13483 {
13484     char temp[MSG_SIZ+1], *p;
13485     int len;
13486
13487     len = strlen(buf);
13488     if (len > MSG_SIZ)
13489       len = MSG_SIZ;
13490
13491     strncpy(temp, buf, len);
13492     temp[len] = 0;
13493
13494     p = temp;
13495     while (*p) {
13496         if (*p == '\n' || *p == '\r')
13497           *p = ' ';
13498         ++p;
13499     }
13500
13501     strcat(temp, "\n");
13502     SendToICS(temp);
13503     SendToPlayer(temp, strlen(temp));
13504 }
13505
13506 void
13507 SetWhiteToPlayEvent()
13508 {
13509     if (gameMode == EditPosition) {
13510         blackPlaysFirst = FALSE;
13511         DisplayBothClocks();    /* works because currentMove is 0 */
13512     } else if (gameMode == IcsExamining) {
13513         SendToICS(ics_prefix);
13514         SendToICS("tomove white\n");
13515     }
13516 }
13517
13518 void
13519 SetBlackToPlayEvent()
13520 {
13521     if (gameMode == EditPosition) {
13522         blackPlaysFirst = TRUE;
13523         currentMove = 1;        /* kludge */
13524         DisplayBothClocks();
13525         currentMove = 0;
13526     } else if (gameMode == IcsExamining) {
13527         SendToICS(ics_prefix);
13528         SendToICS("tomove black\n");
13529     }
13530 }
13531
13532 void
13533 EditPositionMenuEvent(selection, x, y)
13534      ChessSquare selection;
13535      int x, y;
13536 {
13537     char buf[MSG_SIZ];
13538     ChessSquare piece = boards[0][y][x];
13539
13540     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13541
13542     switch (selection) {
13543       case ClearBoard:
13544         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13545             SendToICS(ics_prefix);
13546             SendToICS("bsetup clear\n");
13547         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13548             SendToICS(ics_prefix);
13549             SendToICS("clearboard\n");
13550         } else {
13551             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13552                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13553                 for (y = 0; y < BOARD_HEIGHT; y++) {
13554                     if (gameMode == IcsExamining) {
13555                         if (boards[currentMove][y][x] != EmptySquare) {
13556                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13557                                     AAA + x, ONE + y);
13558                             SendToICS(buf);
13559                         }
13560                     } else {
13561                         boards[0][y][x] = p;
13562                     }
13563                 }
13564             }
13565         }
13566         if (gameMode == EditPosition) {
13567             DrawPosition(FALSE, boards[0]);
13568         }
13569         break;
13570
13571       case WhitePlay:
13572         SetWhiteToPlayEvent();
13573         break;
13574
13575       case BlackPlay:
13576         SetBlackToPlayEvent();
13577         break;
13578
13579       case EmptySquare:
13580         if (gameMode == IcsExamining) {
13581             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13582             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13583             SendToICS(buf);
13584         } else {
13585             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13586                 if(x == BOARD_LEFT-2) {
13587                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13588                     boards[0][y][1] = 0;
13589                 } else
13590                 if(x == BOARD_RGHT+1) {
13591                     if(y >= gameInfo.holdingsSize) break;
13592                     boards[0][y][BOARD_WIDTH-2] = 0;
13593                 } else break;
13594             }
13595             boards[0][y][x] = EmptySquare;
13596             DrawPosition(FALSE, boards[0]);
13597         }
13598         break;
13599
13600       case PromotePiece:
13601         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13602            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13603             selection = (ChessSquare) (PROMOTED piece);
13604         } else if(piece == EmptySquare) selection = WhiteSilver;
13605         else selection = (ChessSquare)((int)piece - 1);
13606         goto defaultlabel;
13607
13608       case DemotePiece:
13609         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13610            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13611             selection = (ChessSquare) (DEMOTED piece);
13612         } else if(piece == EmptySquare) selection = BlackSilver;
13613         else selection = (ChessSquare)((int)piece + 1);
13614         goto defaultlabel;
13615
13616       case WhiteQueen:
13617       case BlackQueen:
13618         if(gameInfo.variant == VariantShatranj ||
13619            gameInfo.variant == VariantXiangqi  ||
13620            gameInfo.variant == VariantCourier  ||
13621            gameInfo.variant == VariantMakruk     )
13622             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13623         goto defaultlabel;
13624
13625       case WhiteKing:
13626       case BlackKing:
13627         if(gameInfo.variant == VariantXiangqi)
13628             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13629         if(gameInfo.variant == VariantKnightmate)
13630             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13631       default:
13632         defaultlabel:
13633         if (gameMode == IcsExamining) {
13634             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13635             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13636                      PieceToChar(selection), AAA + x, ONE + y);
13637             SendToICS(buf);
13638         } else {
13639             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13640                 int n;
13641                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13642                     n = PieceToNumber(selection - BlackPawn);
13643                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13644                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13645                     boards[0][BOARD_HEIGHT-1-n][1]++;
13646                 } else
13647                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13648                     n = PieceToNumber(selection);
13649                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13650                     boards[0][n][BOARD_WIDTH-1] = selection;
13651                     boards[0][n][BOARD_WIDTH-2]++;
13652                 }
13653             } else
13654             boards[0][y][x] = selection;
13655             DrawPosition(TRUE, boards[0]);
13656         }
13657         break;
13658     }
13659 }
13660
13661
13662 void
13663 DropMenuEvent(selection, x, y)
13664      ChessSquare selection;
13665      int x, y;
13666 {
13667     ChessMove moveType;
13668
13669     switch (gameMode) {
13670       case IcsPlayingWhite:
13671       case MachinePlaysBlack:
13672         if (!WhiteOnMove(currentMove)) {
13673             DisplayMoveError(_("It is Black's turn"));
13674             return;
13675         }
13676         moveType = WhiteDrop;
13677         break;
13678       case IcsPlayingBlack:
13679       case MachinePlaysWhite:
13680         if (WhiteOnMove(currentMove)) {
13681             DisplayMoveError(_("It is White's turn"));
13682             return;
13683         }
13684         moveType = BlackDrop;
13685         break;
13686       case EditGame:
13687         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13688         break;
13689       default:
13690         return;
13691     }
13692
13693     if (moveType == BlackDrop && selection < BlackPawn) {
13694       selection = (ChessSquare) ((int) selection
13695                                  + (int) BlackPawn - (int) WhitePawn);
13696     }
13697     if (boards[currentMove][y][x] != EmptySquare) {
13698         DisplayMoveError(_("That square is occupied"));
13699         return;
13700     }
13701
13702     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13703 }
13704
13705 void
13706 AcceptEvent()
13707 {
13708     /* Accept a pending offer of any kind from opponent */
13709
13710     if (appData.icsActive) {
13711         SendToICS(ics_prefix);
13712         SendToICS("accept\n");
13713     } else if (cmailMsgLoaded) {
13714         if (currentMove == cmailOldMove &&
13715             commentList[cmailOldMove] != NULL &&
13716             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13717                    "Black offers a draw" : "White offers a draw")) {
13718             TruncateGame();
13719             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13720             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13721         } else {
13722             DisplayError(_("There is no pending offer on this move"), 0);
13723             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13724         }
13725     } else {
13726         /* Not used for offers from chess program */
13727     }
13728 }
13729
13730 void
13731 DeclineEvent()
13732 {
13733     /* Decline a pending offer of any kind from opponent */
13734
13735     if (appData.icsActive) {
13736         SendToICS(ics_prefix);
13737         SendToICS("decline\n");
13738     } else if (cmailMsgLoaded) {
13739         if (currentMove == cmailOldMove &&
13740             commentList[cmailOldMove] != NULL &&
13741             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13742                    "Black offers a draw" : "White offers a draw")) {
13743 #ifdef NOTDEF
13744             AppendComment(cmailOldMove, "Draw declined", TRUE);
13745             DisplayComment(cmailOldMove - 1, "Draw declined");
13746 #endif /*NOTDEF*/
13747         } else {
13748             DisplayError(_("There is no pending offer on this move"), 0);
13749         }
13750     } else {
13751         /* Not used for offers from chess program */
13752     }
13753 }
13754
13755 void
13756 RematchEvent()
13757 {
13758     /* Issue ICS rematch command */
13759     if (appData.icsActive) {
13760         SendToICS(ics_prefix);
13761         SendToICS("rematch\n");
13762     }
13763 }
13764
13765 void
13766 CallFlagEvent()
13767 {
13768     /* Call your opponent's flag (claim a win on time) */
13769     if (appData.icsActive) {
13770         SendToICS(ics_prefix);
13771         SendToICS("flag\n");
13772     } else {
13773         switch (gameMode) {
13774           default:
13775             return;
13776           case MachinePlaysWhite:
13777             if (whiteFlag) {
13778                 if (blackFlag)
13779                   GameEnds(GameIsDrawn, "Both players ran out of time",
13780                            GE_PLAYER);
13781                 else
13782                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13783             } else {
13784                 DisplayError(_("Your opponent is not out of time"), 0);
13785             }
13786             break;
13787           case MachinePlaysBlack:
13788             if (blackFlag) {
13789                 if (whiteFlag)
13790                   GameEnds(GameIsDrawn, "Both players ran out of time",
13791                            GE_PLAYER);
13792                 else
13793                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13794             } else {
13795                 DisplayError(_("Your opponent is not out of time"), 0);
13796             }
13797             break;
13798         }
13799     }
13800 }
13801
13802 void
13803 ClockClick(int which)
13804 {       // [HGM] code moved to back-end from winboard.c
13805         if(which) { // black clock
13806           if (gameMode == EditPosition || gameMode == IcsExamining) {
13807             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13808             SetBlackToPlayEvent();
13809           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13810           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13811           } else if (shiftKey) {
13812             AdjustClock(which, -1);
13813           } else if (gameMode == IcsPlayingWhite ||
13814                      gameMode == MachinePlaysBlack) {
13815             CallFlagEvent();
13816           }
13817         } else { // white clock
13818           if (gameMode == EditPosition || gameMode == IcsExamining) {
13819             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13820             SetWhiteToPlayEvent();
13821           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13822           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13823           } else if (shiftKey) {
13824             AdjustClock(which, -1);
13825           } else if (gameMode == IcsPlayingBlack ||
13826                    gameMode == MachinePlaysWhite) {
13827             CallFlagEvent();
13828           }
13829         }
13830 }
13831
13832 void
13833 DrawEvent()
13834 {
13835     /* Offer draw or accept pending draw offer from opponent */
13836
13837     if (appData.icsActive) {
13838         /* Note: tournament rules require draw offers to be
13839            made after you make your move but before you punch
13840            your clock.  Currently ICS doesn't let you do that;
13841            instead, you immediately punch your clock after making
13842            a move, but you can offer a draw at any time. */
13843
13844         SendToICS(ics_prefix);
13845         SendToICS("draw\n");
13846         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13847     } else if (cmailMsgLoaded) {
13848         if (currentMove == cmailOldMove &&
13849             commentList[cmailOldMove] != NULL &&
13850             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13851                    "Black offers a draw" : "White offers a draw")) {
13852             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13853             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13854         } else if (currentMove == cmailOldMove + 1) {
13855             char *offer = WhiteOnMove(cmailOldMove) ?
13856               "White offers a draw" : "Black offers a draw";
13857             AppendComment(currentMove, offer, TRUE);
13858             DisplayComment(currentMove - 1, offer);
13859             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13860         } else {
13861             DisplayError(_("You must make your move before offering a draw"), 0);
13862             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13863         }
13864     } else if (first.offeredDraw) {
13865         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13866     } else {
13867         if (first.sendDrawOffers) {
13868             SendToProgram("draw\n", &first);
13869             userOfferedDraw = TRUE;
13870         }
13871     }
13872 }
13873
13874 void
13875 AdjournEvent()
13876 {
13877     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13878
13879     if (appData.icsActive) {
13880         SendToICS(ics_prefix);
13881         SendToICS("adjourn\n");
13882     } else {
13883         /* Currently GNU Chess doesn't offer or accept Adjourns */
13884     }
13885 }
13886
13887
13888 void
13889 AbortEvent()
13890 {
13891     /* Offer Abort or accept pending Abort offer from opponent */
13892
13893     if (appData.icsActive) {
13894         SendToICS(ics_prefix);
13895         SendToICS("abort\n");
13896     } else {
13897         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13898     }
13899 }
13900
13901 void
13902 ResignEvent()
13903 {
13904     /* Resign.  You can do this even if it's not your turn. */
13905
13906     if (appData.icsActive) {
13907         SendToICS(ics_prefix);
13908         SendToICS("resign\n");
13909     } else {
13910         switch (gameMode) {
13911           case MachinePlaysWhite:
13912             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13913             break;
13914           case MachinePlaysBlack:
13915             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13916             break;
13917           case EditGame:
13918             if (cmailMsgLoaded) {
13919                 TruncateGame();
13920                 if (WhiteOnMove(cmailOldMove)) {
13921                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13922                 } else {
13923                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13924                 }
13925                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13926             }
13927             break;
13928           default:
13929             break;
13930         }
13931     }
13932 }
13933
13934
13935 void
13936 StopObservingEvent()
13937 {
13938     /* Stop observing current games */
13939     SendToICS(ics_prefix);
13940     SendToICS("unobserve\n");
13941 }
13942
13943 void
13944 StopExaminingEvent()
13945 {
13946     /* Stop observing current game */
13947     SendToICS(ics_prefix);
13948     SendToICS("unexamine\n");
13949 }
13950
13951 void
13952 ForwardInner(target)
13953      int target;
13954 {
13955     int limit;
13956
13957     if (appData.debugMode)
13958         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13959                 target, currentMove, forwardMostMove);
13960
13961     if (gameMode == EditPosition)
13962       return;
13963
13964     if (gameMode == PlayFromGameFile && !pausing)
13965       PauseEvent();
13966
13967     if (gameMode == IcsExamining && pausing)
13968       limit = pauseExamForwardMostMove;
13969     else
13970       limit = forwardMostMove;
13971
13972     if (target > limit) target = limit;
13973
13974     if (target > 0 && moveList[target - 1][0]) {
13975         int fromX, fromY, toX, toY;
13976         toX = moveList[target - 1][2] - AAA;
13977         toY = moveList[target - 1][3] - ONE;
13978         if (moveList[target - 1][1] == '@') {
13979             if (appData.highlightLastMove) {
13980                 SetHighlights(-1, -1, toX, toY);
13981             }
13982         } else {
13983             fromX = moveList[target - 1][0] - AAA;
13984             fromY = moveList[target - 1][1] - ONE;
13985             if (target == currentMove + 1) {
13986                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13987             }
13988             if (appData.highlightLastMove) {
13989                 SetHighlights(fromX, fromY, toX, toY);
13990             }
13991         }
13992     }
13993     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13994         gameMode == Training || gameMode == PlayFromGameFile ||
13995         gameMode == AnalyzeFile) {
13996         while (currentMove < target) {
13997             SendMoveToProgram(currentMove++, &first);
13998         }
13999     } else {
14000         currentMove = target;
14001     }
14002
14003     if (gameMode == EditGame || gameMode == EndOfGame) {
14004         whiteTimeRemaining = timeRemaining[0][currentMove];
14005         blackTimeRemaining = timeRemaining[1][currentMove];
14006     }
14007     DisplayBothClocks();
14008     DisplayMove(currentMove - 1);
14009     DrawPosition(FALSE, boards[currentMove]);
14010     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14011     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14012         DisplayComment(currentMove - 1, commentList[currentMove]);
14013     }
14014     DisplayBook(currentMove);
14015 }
14016
14017
14018 void
14019 ForwardEvent()
14020 {
14021     if (gameMode == IcsExamining && !pausing) {
14022         SendToICS(ics_prefix);
14023         SendToICS("forward\n");
14024     } else {
14025         ForwardInner(currentMove + 1);
14026     }
14027 }
14028
14029 void
14030 ToEndEvent()
14031 {
14032     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14033         /* to optimze, we temporarily turn off analysis mode while we feed
14034          * the remaining moves to the engine. Otherwise we get analysis output
14035          * after each move.
14036          */
14037         if (first.analysisSupport) {
14038           SendToProgram("exit\nforce\n", &first);
14039           first.analyzing = FALSE;
14040         }
14041     }
14042
14043     if (gameMode == IcsExamining && !pausing) {
14044         SendToICS(ics_prefix);
14045         SendToICS("forward 999999\n");
14046     } else {
14047         ForwardInner(forwardMostMove);
14048     }
14049
14050     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14051         /* we have fed all the moves, so reactivate analysis mode */
14052         SendToProgram("analyze\n", &first);
14053         first.analyzing = TRUE;
14054         /*first.maybeThinking = TRUE;*/
14055         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14056     }
14057 }
14058
14059 void
14060 BackwardInner(target)
14061      int target;
14062 {
14063     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14064
14065     if (appData.debugMode)
14066         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14067                 target, currentMove, forwardMostMove);
14068
14069     if (gameMode == EditPosition) return;
14070     if (currentMove <= backwardMostMove) {
14071         ClearHighlights();
14072         DrawPosition(full_redraw, boards[currentMove]);
14073         return;
14074     }
14075     if (gameMode == PlayFromGameFile && !pausing)
14076       PauseEvent();
14077
14078     if (moveList[target][0]) {
14079         int fromX, fromY, toX, toY;
14080         toX = moveList[target][2] - AAA;
14081         toY = moveList[target][3] - ONE;
14082         if (moveList[target][1] == '@') {
14083             if (appData.highlightLastMove) {
14084                 SetHighlights(-1, -1, toX, toY);
14085             }
14086         } else {
14087             fromX = moveList[target][0] - AAA;
14088             fromY = moveList[target][1] - ONE;
14089             if (target == currentMove - 1) {
14090                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14091             }
14092             if (appData.highlightLastMove) {
14093                 SetHighlights(fromX, fromY, toX, toY);
14094             }
14095         }
14096     }
14097     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14098         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14099         while (currentMove > target) {
14100             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14101                 // null move cannot be undone. Reload program with move history before it.
14102                 int i;
14103                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14104                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14105                 }
14106                 SendBoard(&first, i); 
14107                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14108                 break;
14109             }
14110             SendToProgram("undo\n", &first);
14111             currentMove--;
14112         }
14113     } else {
14114         currentMove = target;
14115     }
14116
14117     if (gameMode == EditGame || gameMode == EndOfGame) {
14118         whiteTimeRemaining = timeRemaining[0][currentMove];
14119         blackTimeRemaining = timeRemaining[1][currentMove];
14120     }
14121     DisplayBothClocks();
14122     DisplayMove(currentMove - 1);
14123     DrawPosition(full_redraw, boards[currentMove]);
14124     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14125     // [HGM] PV info: routine tests if comment empty
14126     DisplayComment(currentMove - 1, commentList[currentMove]);
14127     DisplayBook(currentMove);
14128 }
14129
14130 void
14131 BackwardEvent()
14132 {
14133     if (gameMode == IcsExamining && !pausing) {
14134         SendToICS(ics_prefix);
14135         SendToICS("backward\n");
14136     } else {
14137         BackwardInner(currentMove - 1);
14138     }
14139 }
14140
14141 void
14142 ToStartEvent()
14143 {
14144     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14145         /* to optimize, we temporarily turn off analysis mode while we undo
14146          * all the moves. Otherwise we get analysis output after each undo.
14147          */
14148         if (first.analysisSupport) {
14149           SendToProgram("exit\nforce\n", &first);
14150           first.analyzing = FALSE;
14151         }
14152     }
14153
14154     if (gameMode == IcsExamining && !pausing) {
14155         SendToICS(ics_prefix);
14156         SendToICS("backward 999999\n");
14157     } else {
14158         BackwardInner(backwardMostMove);
14159     }
14160
14161     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14162         /* we have fed all the moves, so reactivate analysis mode */
14163         SendToProgram("analyze\n", &first);
14164         first.analyzing = TRUE;
14165         /*first.maybeThinking = TRUE;*/
14166         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14167     }
14168 }
14169
14170 void
14171 ToNrEvent(int to)
14172 {
14173   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14174   if (to >= forwardMostMove) to = forwardMostMove;
14175   if (to <= backwardMostMove) to = backwardMostMove;
14176   if (to < currentMove) {
14177     BackwardInner(to);
14178   } else {
14179     ForwardInner(to);
14180   }
14181 }
14182
14183 void
14184 RevertEvent(Boolean annotate)
14185 {
14186     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14187         return;
14188     }
14189     if (gameMode != IcsExamining) {
14190         DisplayError(_("You are not examining a game"), 0);
14191         return;
14192     }
14193     if (pausing) {
14194         DisplayError(_("You can't revert while pausing"), 0);
14195         return;
14196     }
14197     SendToICS(ics_prefix);
14198     SendToICS("revert\n");
14199 }
14200
14201 void
14202 RetractMoveEvent()
14203 {
14204     switch (gameMode) {
14205       case MachinePlaysWhite:
14206       case MachinePlaysBlack:
14207         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14208             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14209             return;
14210         }
14211         if (forwardMostMove < 2) return;
14212         currentMove = forwardMostMove = forwardMostMove - 2;
14213         whiteTimeRemaining = timeRemaining[0][currentMove];
14214         blackTimeRemaining = timeRemaining[1][currentMove];
14215         DisplayBothClocks();
14216         DisplayMove(currentMove - 1);
14217         ClearHighlights();/*!! could figure this out*/
14218         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14219         SendToProgram("remove\n", &first);
14220         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14221         break;
14222
14223       case BeginningOfGame:
14224       default:
14225         break;
14226
14227       case IcsPlayingWhite:
14228       case IcsPlayingBlack:
14229         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14230             SendToICS(ics_prefix);
14231             SendToICS("takeback 2\n");
14232         } else {
14233             SendToICS(ics_prefix);
14234             SendToICS("takeback 1\n");
14235         }
14236         break;
14237     }
14238 }
14239
14240 void
14241 MoveNowEvent()
14242 {
14243     ChessProgramState *cps;
14244
14245     switch (gameMode) {
14246       case MachinePlaysWhite:
14247         if (!WhiteOnMove(forwardMostMove)) {
14248             DisplayError(_("It is your turn"), 0);
14249             return;
14250         }
14251         cps = &first;
14252         break;
14253       case MachinePlaysBlack:
14254         if (WhiteOnMove(forwardMostMove)) {
14255             DisplayError(_("It is your turn"), 0);
14256             return;
14257         }
14258         cps = &first;
14259         break;
14260       case TwoMachinesPlay:
14261         if (WhiteOnMove(forwardMostMove) ==
14262             (first.twoMachinesColor[0] == 'w')) {
14263             cps = &first;
14264         } else {
14265             cps = &second;
14266         }
14267         break;
14268       case BeginningOfGame:
14269       default:
14270         return;
14271     }
14272     SendToProgram("?\n", cps);
14273 }
14274
14275 void
14276 TruncateGameEvent()
14277 {
14278     EditGameEvent();
14279     if (gameMode != EditGame) return;
14280     TruncateGame();
14281 }
14282
14283 void
14284 TruncateGame()
14285 {
14286     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14287     if (forwardMostMove > currentMove) {
14288         if (gameInfo.resultDetails != NULL) {
14289             free(gameInfo.resultDetails);
14290             gameInfo.resultDetails = NULL;
14291             gameInfo.result = GameUnfinished;
14292         }
14293         forwardMostMove = currentMove;
14294         HistorySet(parseList, backwardMostMove, forwardMostMove,
14295                    currentMove-1);
14296     }
14297 }
14298
14299 void
14300 HintEvent()
14301 {
14302     if (appData.noChessProgram) return;
14303     switch (gameMode) {
14304       case MachinePlaysWhite:
14305         if (WhiteOnMove(forwardMostMove)) {
14306             DisplayError(_("Wait until your turn"), 0);
14307             return;
14308         }
14309         break;
14310       case BeginningOfGame:
14311       case MachinePlaysBlack:
14312         if (!WhiteOnMove(forwardMostMove)) {
14313             DisplayError(_("Wait until your turn"), 0);
14314             return;
14315         }
14316         break;
14317       default:
14318         DisplayError(_("No hint available"), 0);
14319         return;
14320     }
14321     SendToProgram("hint\n", &first);
14322     hintRequested = TRUE;
14323 }
14324
14325 void
14326 BookEvent()
14327 {
14328     if (appData.noChessProgram) return;
14329     switch (gameMode) {
14330       case MachinePlaysWhite:
14331         if (WhiteOnMove(forwardMostMove)) {
14332             DisplayError(_("Wait until your turn"), 0);
14333             return;
14334         }
14335         break;
14336       case BeginningOfGame:
14337       case MachinePlaysBlack:
14338         if (!WhiteOnMove(forwardMostMove)) {
14339             DisplayError(_("Wait until your turn"), 0);
14340             return;
14341         }
14342         break;
14343       case EditPosition:
14344         EditPositionDone(TRUE);
14345         break;
14346       case TwoMachinesPlay:
14347         return;
14348       default:
14349         break;
14350     }
14351     SendToProgram("bk\n", &first);
14352     bookOutput[0] = NULLCHAR;
14353     bookRequested = TRUE;
14354 }
14355
14356 void
14357 AboutGameEvent()
14358 {
14359     char *tags = PGNTags(&gameInfo);
14360     TagsPopUp(tags, CmailMsg());
14361     free(tags);
14362 }
14363
14364 /* end button procedures */
14365
14366 void
14367 PrintPosition(fp, move)
14368      FILE *fp;
14369      int move;
14370 {
14371     int i, j;
14372
14373     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14374         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14375             char c = PieceToChar(boards[move][i][j]);
14376             fputc(c == 'x' ? '.' : c, fp);
14377             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14378         }
14379     }
14380     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14381       fprintf(fp, "white to play\n");
14382     else
14383       fprintf(fp, "black to play\n");
14384 }
14385
14386 void
14387 PrintOpponents(fp)
14388      FILE *fp;
14389 {
14390     if (gameInfo.white != NULL) {
14391         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14392     } else {
14393         fprintf(fp, "\n");
14394     }
14395 }
14396
14397 /* Find last component of program's own name, using some heuristics */
14398 void
14399 TidyProgramName(prog, host, buf)
14400      char *prog, *host, buf[MSG_SIZ];
14401 {
14402     char *p, *q;
14403     int local = (strcmp(host, "localhost") == 0);
14404     while (!local && (p = strchr(prog, ';')) != NULL) {
14405         p++;
14406         while (*p == ' ') p++;
14407         prog = p;
14408     }
14409     if (*prog == '"' || *prog == '\'') {
14410         q = strchr(prog + 1, *prog);
14411     } else {
14412         q = strchr(prog, ' ');
14413     }
14414     if (q == NULL) q = prog + strlen(prog);
14415     p = q;
14416     while (p >= prog && *p != '/' && *p != '\\') p--;
14417     p++;
14418     if(p == prog && *p == '"') p++;
14419     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14420     memcpy(buf, p, q - p);
14421     buf[q - p] = NULLCHAR;
14422     if (!local) {
14423         strcat(buf, "@");
14424         strcat(buf, host);
14425     }
14426 }
14427
14428 char *
14429 TimeControlTagValue()
14430 {
14431     char buf[MSG_SIZ];
14432     if (!appData.clockMode) {
14433       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14434     } else if (movesPerSession > 0) {
14435       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14436     } else if (timeIncrement == 0) {
14437       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14438     } else {
14439       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14440     }
14441     return StrSave(buf);
14442 }
14443
14444 void
14445 SetGameInfo()
14446 {
14447     /* This routine is used only for certain modes */
14448     VariantClass v = gameInfo.variant;
14449     ChessMove r = GameUnfinished;
14450     char *p = NULL;
14451
14452     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14453         r = gameInfo.result;
14454         p = gameInfo.resultDetails;
14455         gameInfo.resultDetails = NULL;
14456     }
14457     ClearGameInfo(&gameInfo);
14458     gameInfo.variant = v;
14459
14460     switch (gameMode) {
14461       case MachinePlaysWhite:
14462         gameInfo.event = StrSave( appData.pgnEventHeader );
14463         gameInfo.site = StrSave(HostName());
14464         gameInfo.date = PGNDate();
14465         gameInfo.round = StrSave("-");
14466         gameInfo.white = StrSave(first.tidy);
14467         gameInfo.black = StrSave(UserName());
14468         gameInfo.timeControl = TimeControlTagValue();
14469         break;
14470
14471       case MachinePlaysBlack:
14472         gameInfo.event = StrSave( appData.pgnEventHeader );
14473         gameInfo.site = StrSave(HostName());
14474         gameInfo.date = PGNDate();
14475         gameInfo.round = StrSave("-");
14476         gameInfo.white = StrSave(UserName());
14477         gameInfo.black = StrSave(first.tidy);
14478         gameInfo.timeControl = TimeControlTagValue();
14479         break;
14480
14481       case TwoMachinesPlay:
14482         gameInfo.event = StrSave( appData.pgnEventHeader );
14483         gameInfo.site = StrSave(HostName());
14484         gameInfo.date = PGNDate();
14485         if (roundNr > 0) {
14486             char buf[MSG_SIZ];
14487             snprintf(buf, MSG_SIZ, "%d", roundNr);
14488             gameInfo.round = StrSave(buf);
14489         } else {
14490             gameInfo.round = StrSave("-");
14491         }
14492         if (first.twoMachinesColor[0] == 'w') {
14493             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14494             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14495         } else {
14496             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14497             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14498         }
14499         gameInfo.timeControl = TimeControlTagValue();
14500         break;
14501
14502       case EditGame:
14503         gameInfo.event = StrSave("Edited game");
14504         gameInfo.site = StrSave(HostName());
14505         gameInfo.date = PGNDate();
14506         gameInfo.round = StrSave("-");
14507         gameInfo.white = StrSave("-");
14508         gameInfo.black = StrSave("-");
14509         gameInfo.result = r;
14510         gameInfo.resultDetails = p;
14511         break;
14512
14513       case EditPosition:
14514         gameInfo.event = StrSave("Edited position");
14515         gameInfo.site = StrSave(HostName());
14516         gameInfo.date = PGNDate();
14517         gameInfo.round = StrSave("-");
14518         gameInfo.white = StrSave("-");
14519         gameInfo.black = StrSave("-");
14520         break;
14521
14522       case IcsPlayingWhite:
14523       case IcsPlayingBlack:
14524       case IcsObserving:
14525       case IcsExamining:
14526         break;
14527
14528       case PlayFromGameFile:
14529         gameInfo.event = StrSave("Game from non-PGN file");
14530         gameInfo.site = StrSave(HostName());
14531         gameInfo.date = PGNDate();
14532         gameInfo.round = StrSave("-");
14533         gameInfo.white = StrSave("?");
14534         gameInfo.black = StrSave("?");
14535         break;
14536
14537       default:
14538         break;
14539     }
14540 }
14541
14542 void
14543 ReplaceComment(index, text)
14544      int index;
14545      char *text;
14546 {
14547     int len;
14548     char *p;
14549     float score;
14550
14551     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14552        pvInfoList[index-1].depth == len &&
14553        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14554        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14555     while (*text == '\n') text++;
14556     len = strlen(text);
14557     while (len > 0 && text[len - 1] == '\n') len--;
14558
14559     if (commentList[index] != NULL)
14560       free(commentList[index]);
14561
14562     if (len == 0) {
14563         commentList[index] = NULL;
14564         return;
14565     }
14566   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14567       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14568       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14569     commentList[index] = (char *) malloc(len + 2);
14570     strncpy(commentList[index], text, len);
14571     commentList[index][len] = '\n';
14572     commentList[index][len + 1] = NULLCHAR;
14573   } else {
14574     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14575     char *p;
14576     commentList[index] = (char *) malloc(len + 7);
14577     safeStrCpy(commentList[index], "{\n", 3);
14578     safeStrCpy(commentList[index]+2, text, len+1);
14579     commentList[index][len+2] = NULLCHAR;
14580     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14581     strcat(commentList[index], "\n}\n");
14582   }
14583 }
14584
14585 void
14586 CrushCRs(text)
14587      char *text;
14588 {
14589   char *p = text;
14590   char *q = text;
14591   char ch;
14592
14593   do {
14594     ch = *p++;
14595     if (ch == '\r') continue;
14596     *q++ = ch;
14597   } while (ch != '\0');
14598 }
14599
14600 void
14601 AppendComment(index, text, addBraces)
14602      int index;
14603      char *text;
14604      Boolean addBraces; // [HGM] braces: tells if we should add {}
14605 {
14606     int oldlen, len;
14607     char *old;
14608
14609 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14610     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14611
14612     CrushCRs(text);
14613     while (*text == '\n') text++;
14614     len = strlen(text);
14615     while (len > 0 && text[len - 1] == '\n') len--;
14616
14617     if (len == 0) return;
14618
14619     if (commentList[index] != NULL) {
14620         old = commentList[index];
14621         oldlen = strlen(old);
14622         while(commentList[index][oldlen-1] ==  '\n')
14623           commentList[index][--oldlen] = NULLCHAR;
14624         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14625         safeStrCpy(commentList[index], old, oldlen + len + 6);
14626         free(old);
14627         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14628         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14629           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14630           while (*text == '\n') { text++; len--; }
14631           commentList[index][--oldlen] = NULLCHAR;
14632       }
14633         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14634         else          strcat(commentList[index], "\n");
14635         strcat(commentList[index], text);
14636         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14637         else          strcat(commentList[index], "\n");
14638     } else {
14639         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14640         if(addBraces)
14641           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14642         else commentList[index][0] = NULLCHAR;
14643         strcat(commentList[index], text);
14644         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14645         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14646     }
14647 }
14648
14649 static char * FindStr( char * text, char * sub_text )
14650 {
14651     char * result = strstr( text, sub_text );
14652
14653     if( result != NULL ) {
14654         result += strlen( sub_text );
14655     }
14656
14657     return result;
14658 }
14659
14660 /* [AS] Try to extract PV info from PGN comment */
14661 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14662 char *GetInfoFromComment( int index, char * text )
14663 {
14664     char * sep = text, *p;
14665
14666     if( text != NULL && index > 0 ) {
14667         int score = 0;
14668         int depth = 0;
14669         int time = -1, sec = 0, deci;
14670         char * s_eval = FindStr( text, "[%eval " );
14671         char * s_emt = FindStr( text, "[%emt " );
14672
14673         if( s_eval != NULL || s_emt != NULL ) {
14674             /* New style */
14675             char delim;
14676
14677             if( s_eval != NULL ) {
14678                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14679                     return text;
14680                 }
14681
14682                 if( delim != ']' ) {
14683                     return text;
14684                 }
14685             }
14686
14687             if( s_emt != NULL ) {
14688             }
14689                 return text;
14690         }
14691         else {
14692             /* We expect something like: [+|-]nnn.nn/dd */
14693             int score_lo = 0;
14694
14695             if(*text != '{') return text; // [HGM] braces: must be normal comment
14696
14697             sep = strchr( text, '/' );
14698             if( sep == NULL || sep < (text+4) ) {
14699                 return text;
14700             }
14701
14702             p = text;
14703             if(p[1] == '(') { // comment starts with PV
14704                p = strchr(p, ')'); // locate end of PV
14705                if(p == NULL || sep < p+5) return text;
14706                // at this point we have something like "{(.*) +0.23/6 ..."
14707                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14708                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14709                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14710             }
14711             time = -1; sec = -1; deci = -1;
14712             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14713                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14714                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14715                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14716                 return text;
14717             }
14718
14719             if( score_lo < 0 || score_lo >= 100 ) {
14720                 return text;
14721             }
14722
14723             if(sec >= 0) time = 600*time + 10*sec; else
14724             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14725
14726             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14727
14728             /* [HGM] PV time: now locate end of PV info */
14729             while( *++sep >= '0' && *sep <= '9'); // strip depth
14730             if(time >= 0)
14731             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14732             if(sec >= 0)
14733             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14734             if(deci >= 0)
14735             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14736             while(*sep == ' ') sep++;
14737         }
14738
14739         if( depth <= 0 ) {
14740             return text;
14741         }
14742
14743         if( time < 0 ) {
14744             time = -1;
14745         }
14746
14747         pvInfoList[index-1].depth = depth;
14748         pvInfoList[index-1].score = score;
14749         pvInfoList[index-1].time  = 10*time; // centi-sec
14750         if(*sep == '}') *sep = 0; else *--sep = '{';
14751         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14752     }
14753     return sep;
14754 }
14755
14756 void
14757 SendToProgram(message, cps)
14758      char *message;
14759      ChessProgramState *cps;
14760 {
14761     int count, outCount, error;
14762     char buf[MSG_SIZ];
14763
14764     if (cps->pr == NULL) return;
14765     Attention(cps);
14766
14767     if (appData.debugMode) {
14768         TimeMark now;
14769         GetTimeMark(&now);
14770         fprintf(debugFP, "%ld >%-6s: %s",
14771                 SubtractTimeMarks(&now, &programStartTime),
14772                 cps->which, message);
14773     }
14774
14775     count = strlen(message);
14776     outCount = OutputToProcess(cps->pr, message, count, &error);
14777     if (outCount < count && !exiting
14778                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14779       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14780       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14781         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14782             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14783                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14784                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14785                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14786             } else {
14787                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14788                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14789                 gameInfo.result = res;
14790             }
14791             gameInfo.resultDetails = StrSave(buf);
14792         }
14793         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14794         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14795     }
14796 }
14797
14798 void
14799 ReceiveFromProgram(isr, closure, message, count, error)
14800      InputSourceRef isr;
14801      VOIDSTAR closure;
14802      char *message;
14803      int count;
14804      int error;
14805 {
14806     char *end_str;
14807     char buf[MSG_SIZ];
14808     ChessProgramState *cps = (ChessProgramState *)closure;
14809
14810     if (isr != cps->isr) return; /* Killed intentionally */
14811     if (count <= 0) {
14812         if (count == 0) {
14813             RemoveInputSource(cps->isr);
14814             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14815             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14816                     _(cps->which), cps->program);
14817         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14818                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14819                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14820                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14821                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14822                 } else {
14823                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14824                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14825                     gameInfo.result = res;
14826                 }
14827                 gameInfo.resultDetails = StrSave(buf);
14828             }
14829             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14830             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14831         } else {
14832             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14833                     _(cps->which), cps->program);
14834             RemoveInputSource(cps->isr);
14835
14836             /* [AS] Program is misbehaving badly... kill it */
14837             if( count == -2 ) {
14838                 DestroyChildProcess( cps->pr, 9 );
14839                 cps->pr = NoProc;
14840             }
14841
14842             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14843         }
14844         return;
14845     }
14846
14847     if ((end_str = strchr(message, '\r')) != NULL)
14848       *end_str = NULLCHAR;
14849     if ((end_str = strchr(message, '\n')) != NULL)
14850       *end_str = NULLCHAR;
14851
14852     if (appData.debugMode) {
14853         TimeMark now; int print = 1;
14854         char *quote = ""; char c; int i;
14855
14856         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14857                 char start = message[0];
14858                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14859                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14860                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14861                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14862                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14863                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14864                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14865                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14866                    sscanf(message, "hint: %c", &c)!=1 && 
14867                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14868                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14869                     print = (appData.engineComments >= 2);
14870                 }
14871                 message[0] = start; // restore original message
14872         }
14873         if(print) {
14874                 GetTimeMark(&now);
14875                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14876                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14877                         quote,
14878                         message);
14879         }
14880     }
14881
14882     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14883     if (appData.icsEngineAnalyze) {
14884         if (strstr(message, "whisper") != NULL ||
14885              strstr(message, "kibitz") != NULL ||
14886             strstr(message, "tellics") != NULL) return;
14887     }
14888
14889     HandleMachineMove(message, cps);
14890 }
14891
14892
14893 void
14894 SendTimeControl(cps, mps, tc, inc, sd, st)
14895      ChessProgramState *cps;
14896      int mps, inc, sd, st;
14897      long tc;
14898 {
14899     char buf[MSG_SIZ];
14900     int seconds;
14901
14902     if( timeControl_2 > 0 ) {
14903         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14904             tc = timeControl_2;
14905         }
14906     }
14907     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14908     inc /= cps->timeOdds;
14909     st  /= cps->timeOdds;
14910
14911     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14912
14913     if (st > 0) {
14914       /* Set exact time per move, normally using st command */
14915       if (cps->stKludge) {
14916         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14917         seconds = st % 60;
14918         if (seconds == 0) {
14919           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14920         } else {
14921           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14922         }
14923       } else {
14924         snprintf(buf, MSG_SIZ, "st %d\n", st);
14925       }
14926     } else {
14927       /* Set conventional or incremental time control, using level command */
14928       if (seconds == 0) {
14929         /* Note old gnuchess bug -- minutes:seconds used to not work.
14930            Fixed in later versions, but still avoid :seconds
14931            when seconds is 0. */
14932         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14933       } else {
14934         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14935                  seconds, inc/1000.);
14936       }
14937     }
14938     SendToProgram(buf, cps);
14939
14940     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14941     /* Orthogonally, limit search to given depth */
14942     if (sd > 0) {
14943       if (cps->sdKludge) {
14944         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14945       } else {
14946         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14947       }
14948       SendToProgram(buf, cps);
14949     }
14950
14951     if(cps->nps >= 0) { /* [HGM] nps */
14952         if(cps->supportsNPS == FALSE)
14953           cps->nps = -1; // don't use if engine explicitly says not supported!
14954         else {
14955           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14956           SendToProgram(buf, cps);
14957         }
14958     }
14959 }
14960
14961 ChessProgramState *WhitePlayer()
14962 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14963 {
14964     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14965        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14966         return &second;
14967     return &first;
14968 }
14969
14970 void
14971 SendTimeRemaining(cps, machineWhite)
14972      ChessProgramState *cps;
14973      int /*boolean*/ machineWhite;
14974 {
14975     char message[MSG_SIZ];
14976     long time, otime;
14977
14978     /* Note: this routine must be called when the clocks are stopped
14979        or when they have *just* been set or switched; otherwise
14980        it will be off by the time since the current tick started.
14981     */
14982     if (machineWhite) {
14983         time = whiteTimeRemaining / 10;
14984         otime = blackTimeRemaining / 10;
14985     } else {
14986         time = blackTimeRemaining / 10;
14987         otime = whiteTimeRemaining / 10;
14988     }
14989     /* [HGM] translate opponent's time by time-odds factor */
14990     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14991     if (appData.debugMode) {
14992         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14993     }
14994
14995     if (time <= 0) time = 1;
14996     if (otime <= 0) otime = 1;
14997
14998     snprintf(message, MSG_SIZ, "time %ld\n", time);
14999     SendToProgram(message, cps);
15000
15001     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15002     SendToProgram(message, cps);
15003 }
15004
15005 int
15006 BoolFeature(p, name, loc, cps)
15007      char **p;
15008      char *name;
15009      int *loc;
15010      ChessProgramState *cps;
15011 {
15012   char buf[MSG_SIZ];
15013   int len = strlen(name);
15014   int val;
15015
15016   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15017     (*p) += len + 1;
15018     sscanf(*p, "%d", &val);
15019     *loc = (val != 0);
15020     while (**p && **p != ' ')
15021       (*p)++;
15022     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15023     SendToProgram(buf, cps);
15024     return TRUE;
15025   }
15026   return FALSE;
15027 }
15028
15029 int
15030 IntFeature(p, name, loc, cps)
15031      char **p;
15032      char *name;
15033      int *loc;
15034      ChessProgramState *cps;
15035 {
15036   char buf[MSG_SIZ];
15037   int len = strlen(name);
15038   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15039     (*p) += len + 1;
15040     sscanf(*p, "%d", loc);
15041     while (**p && **p != ' ') (*p)++;
15042     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15043     SendToProgram(buf, cps);
15044     return TRUE;
15045   }
15046   return FALSE;
15047 }
15048
15049 int
15050 StringFeature(p, name, loc, cps)
15051      char **p;
15052      char *name;
15053      char loc[];
15054      ChessProgramState *cps;
15055 {
15056   char buf[MSG_SIZ];
15057   int len = strlen(name);
15058   if (strncmp((*p), name, len) == 0
15059       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15060     (*p) += len + 2;
15061     sscanf(*p, "%[^\"]", loc);
15062     while (**p && **p != '\"') (*p)++;
15063     if (**p == '\"') (*p)++;
15064     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15065     SendToProgram(buf, cps);
15066     return TRUE;
15067   }
15068   return FALSE;
15069 }
15070
15071 int
15072 ParseOption(Option *opt, ChessProgramState *cps)
15073 // [HGM] options: process the string that defines an engine option, and determine
15074 // name, type, default value, and allowed value range
15075 {
15076         char *p, *q, buf[MSG_SIZ];
15077         int n, min = (-1)<<31, max = 1<<31, def;
15078
15079         if(p = strstr(opt->name, " -spin ")) {
15080             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15081             if(max < min) max = min; // enforce consistency
15082             if(def < min) def = min;
15083             if(def > max) def = max;
15084             opt->value = def;
15085             opt->min = min;
15086             opt->max = max;
15087             opt->type = Spin;
15088         } else if((p = strstr(opt->name, " -slider "))) {
15089             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15090             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15091             if(max < min) max = min; // enforce consistency
15092             if(def < min) def = min;
15093             if(def > max) def = max;
15094             opt->value = def;
15095             opt->min = min;
15096             opt->max = max;
15097             opt->type = Spin; // Slider;
15098         } else if((p = strstr(opt->name, " -string "))) {
15099             opt->textValue = p+9;
15100             opt->type = TextBox;
15101         } else if((p = strstr(opt->name, " -file "))) {
15102             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15103             opt->textValue = p+7;
15104             opt->type = FileName; // FileName;
15105         } else if((p = strstr(opt->name, " -path "))) {
15106             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15107             opt->textValue = p+7;
15108             opt->type = PathName; // PathName;
15109         } else if(p = strstr(opt->name, " -check ")) {
15110             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15111             opt->value = (def != 0);
15112             opt->type = CheckBox;
15113         } else if(p = strstr(opt->name, " -combo ")) {
15114             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15115             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15116             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15117             opt->value = n = 0;
15118             while(q = StrStr(q, " /// ")) {
15119                 n++; *q = 0;    // count choices, and null-terminate each of them
15120                 q += 5;
15121                 if(*q == '*') { // remember default, which is marked with * prefix
15122                     q++;
15123                     opt->value = n;
15124                 }
15125                 cps->comboList[cps->comboCnt++] = q;
15126             }
15127             cps->comboList[cps->comboCnt++] = NULL;
15128             opt->max = n + 1;
15129             opt->type = ComboBox;
15130         } else if(p = strstr(opt->name, " -button")) {
15131             opt->type = Button;
15132         } else if(p = strstr(opt->name, " -save")) {
15133             opt->type = SaveButton;
15134         } else return FALSE;
15135         *p = 0; // terminate option name
15136         // now look if the command-line options define a setting for this engine option.
15137         if(cps->optionSettings && cps->optionSettings[0])
15138             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15139         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15140           snprintf(buf, MSG_SIZ, "option %s", p);
15141                 if(p = strstr(buf, ",")) *p = 0;
15142                 if(q = strchr(buf, '=')) switch(opt->type) {
15143                     case ComboBox:
15144                         for(n=0; n<opt->max; n++)
15145                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15146                         break;
15147                     case TextBox:
15148                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15149                         break;
15150                     case Spin:
15151                     case CheckBox:
15152                         opt->value = atoi(q+1);
15153                     default:
15154                         break;
15155                 }
15156                 strcat(buf, "\n");
15157                 SendToProgram(buf, cps);
15158         }
15159         return TRUE;
15160 }
15161
15162 void
15163 FeatureDone(cps, val)
15164      ChessProgramState* cps;
15165      int val;
15166 {
15167   DelayedEventCallback cb = GetDelayedEvent();
15168   if ((cb == InitBackEnd3 && cps == &first) ||
15169       (cb == SettingsMenuIfReady && cps == &second) ||
15170       (cb == LoadEngine) ||
15171       (cb == TwoMachinesEventIfReady)) {
15172     CancelDelayedEvent();
15173     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15174   }
15175   cps->initDone = val;
15176 }
15177
15178 /* Parse feature command from engine */
15179 void
15180 ParseFeatures(args, cps)
15181      char* args;
15182      ChessProgramState *cps;
15183 {
15184   char *p = args;
15185   char *q;
15186   int val;
15187   char buf[MSG_SIZ];
15188
15189   for (;;) {
15190     while (*p == ' ') p++;
15191     if (*p == NULLCHAR) return;
15192
15193     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15194     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15195     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15196     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15197     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15198     if (BoolFeature(&p, "reuse", &val, cps)) {
15199       /* Engine can disable reuse, but can't enable it if user said no */
15200       if (!val) cps->reuse = FALSE;
15201       continue;
15202     }
15203     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15204     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15205       if (gameMode == TwoMachinesPlay) {
15206         DisplayTwoMachinesTitle();
15207       } else {
15208         DisplayTitle("");
15209       }
15210       continue;
15211     }
15212     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15213     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15214     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15215     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15216     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15217     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15218     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15219     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15220     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15221     if (IntFeature(&p, "done", &val, cps)) {
15222       FeatureDone(cps, val);
15223       continue;
15224     }
15225     /* Added by Tord: */
15226     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15227     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15228     /* End of additions by Tord */
15229
15230     /* [HGM] added features: */
15231     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15232     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15233     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15234     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15235     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15236     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15237     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15238         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15239           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15240             SendToProgram(buf, cps);
15241             continue;
15242         }
15243         if(cps->nrOptions >= MAX_OPTIONS) {
15244             cps->nrOptions--;
15245             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15246             DisplayError(buf, 0);
15247         }
15248         continue;
15249     }
15250     /* End of additions by HGM */
15251
15252     /* unknown feature: complain and skip */
15253     q = p;
15254     while (*q && *q != '=') q++;
15255     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15256     SendToProgram(buf, cps);
15257     p = q;
15258     if (*p == '=') {
15259       p++;
15260       if (*p == '\"') {
15261         p++;
15262         while (*p && *p != '\"') p++;
15263         if (*p == '\"') p++;
15264       } else {
15265         while (*p && *p != ' ') p++;
15266       }
15267     }
15268   }
15269
15270 }
15271
15272 void
15273 PeriodicUpdatesEvent(newState)
15274      int newState;
15275 {
15276     if (newState == appData.periodicUpdates)
15277       return;
15278
15279     appData.periodicUpdates=newState;
15280
15281     /* Display type changes, so update it now */
15282 //    DisplayAnalysis();
15283
15284     /* Get the ball rolling again... */
15285     if (newState) {
15286         AnalysisPeriodicEvent(1);
15287         StartAnalysisClock();
15288     }
15289 }
15290
15291 void
15292 PonderNextMoveEvent(newState)
15293      int newState;
15294 {
15295     if (newState == appData.ponderNextMove) return;
15296     if (gameMode == EditPosition) EditPositionDone(TRUE);
15297     if (newState) {
15298         SendToProgram("hard\n", &first);
15299         if (gameMode == TwoMachinesPlay) {
15300             SendToProgram("hard\n", &second);
15301         }
15302     } else {
15303         SendToProgram("easy\n", &first);
15304         thinkOutput[0] = NULLCHAR;
15305         if (gameMode == TwoMachinesPlay) {
15306             SendToProgram("easy\n", &second);
15307         }
15308     }
15309     appData.ponderNextMove = newState;
15310 }
15311
15312 void
15313 NewSettingEvent(option, feature, command, value)
15314      char *command;
15315      int option, value, *feature;
15316 {
15317     char buf[MSG_SIZ];
15318
15319     if (gameMode == EditPosition) EditPositionDone(TRUE);
15320     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15321     if(feature == NULL || *feature) SendToProgram(buf, &first);
15322     if (gameMode == TwoMachinesPlay) {
15323         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15324     }
15325 }
15326
15327 void
15328 ShowThinkingEvent()
15329 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15330 {
15331     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15332     int newState = appData.showThinking
15333         // [HGM] thinking: other features now need thinking output as well
15334         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15335
15336     if (oldState == newState) return;
15337     oldState = newState;
15338     if (gameMode == EditPosition) EditPositionDone(TRUE);
15339     if (oldState) {
15340         SendToProgram("post\n", &first);
15341         if (gameMode == TwoMachinesPlay) {
15342             SendToProgram("post\n", &second);
15343         }
15344     } else {
15345         SendToProgram("nopost\n", &first);
15346         thinkOutput[0] = NULLCHAR;
15347         if (gameMode == TwoMachinesPlay) {
15348             SendToProgram("nopost\n", &second);
15349         }
15350     }
15351 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15352 }
15353
15354 void
15355 AskQuestionEvent(title, question, replyPrefix, which)
15356      char *title; char *question; char *replyPrefix; char *which;
15357 {
15358   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15359   if (pr == NoProc) return;
15360   AskQuestion(title, question, replyPrefix, pr);
15361 }
15362
15363 void
15364 TypeInEvent(char firstChar)
15365 {
15366     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15367         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15368         gameMode == AnalyzeMode || gameMode == EditGame || 
15369         gameMode == EditPosition || gameMode == IcsExamining ||
15370         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15371         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15372                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15373                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15374         gameMode == Training) PopUpMoveDialog(firstChar);
15375 }
15376
15377 void
15378 TypeInDoneEvent(char *move)
15379 {
15380         Board board;
15381         int n, fromX, fromY, toX, toY;
15382         char promoChar;
15383         ChessMove moveType;
15384
15385         // [HGM] FENedit
15386         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15387                 EditPositionPasteFEN(move);
15388                 return;
15389         }
15390         // [HGM] movenum: allow move number to be typed in any mode
15391         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15392           ToNrEvent(2*n-1);
15393           return;
15394         }
15395
15396       if (gameMode != EditGame && currentMove != forwardMostMove && 
15397         gameMode != Training) {
15398         DisplayMoveError(_("Displayed move is not current"));
15399       } else {
15400         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15401           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15402         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15403         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15404           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15405           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15406         } else {
15407           DisplayMoveError(_("Could not parse move"));
15408         }
15409       }
15410 }
15411
15412 void
15413 DisplayMove(moveNumber)
15414      int moveNumber;
15415 {
15416     char message[MSG_SIZ];
15417     char res[MSG_SIZ];
15418     char cpThinkOutput[MSG_SIZ];
15419
15420     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15421
15422     if (moveNumber == forwardMostMove - 1 ||
15423         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15424
15425         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15426
15427         if (strchr(cpThinkOutput, '\n')) {
15428             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15429         }
15430     } else {
15431         *cpThinkOutput = NULLCHAR;
15432     }
15433
15434     /* [AS] Hide thinking from human user */
15435     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15436         *cpThinkOutput = NULLCHAR;
15437         if( thinkOutput[0] != NULLCHAR ) {
15438             int i;
15439
15440             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15441                 cpThinkOutput[i] = '.';
15442             }
15443             cpThinkOutput[i] = NULLCHAR;
15444             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15445         }
15446     }
15447
15448     if (moveNumber == forwardMostMove - 1 &&
15449         gameInfo.resultDetails != NULL) {
15450         if (gameInfo.resultDetails[0] == NULLCHAR) {
15451           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15452         } else {
15453           snprintf(res, MSG_SIZ, " {%s} %s",
15454                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15455         }
15456     } else {
15457         res[0] = NULLCHAR;
15458     }
15459
15460     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15461         DisplayMessage(res, cpThinkOutput);
15462     } else {
15463       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15464                 WhiteOnMove(moveNumber) ? " " : ".. ",
15465                 parseList[moveNumber], res);
15466         DisplayMessage(message, cpThinkOutput);
15467     }
15468 }
15469
15470 void
15471 DisplayComment(moveNumber, text)
15472      int moveNumber;
15473      char *text;
15474 {
15475     char title[MSG_SIZ];
15476
15477     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15478       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15479     } else {
15480       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15481               WhiteOnMove(moveNumber) ? " " : ".. ",
15482               parseList[moveNumber]);
15483     }
15484     if (text != NULL && (appData.autoDisplayComment || commentUp))
15485         CommentPopUp(title, text);
15486 }
15487
15488 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15489  * might be busy thinking or pondering.  It can be omitted if your
15490  * gnuchess is configured to stop thinking immediately on any user
15491  * input.  However, that gnuchess feature depends on the FIONREAD
15492  * ioctl, which does not work properly on some flavors of Unix.
15493  */
15494 void
15495 Attention(cps)
15496      ChessProgramState *cps;
15497 {
15498 #if ATTENTION
15499     if (!cps->useSigint) return;
15500     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15501     switch (gameMode) {
15502       case MachinePlaysWhite:
15503       case MachinePlaysBlack:
15504       case TwoMachinesPlay:
15505       case IcsPlayingWhite:
15506       case IcsPlayingBlack:
15507       case AnalyzeMode:
15508       case AnalyzeFile:
15509         /* Skip if we know it isn't thinking */
15510         if (!cps->maybeThinking) return;
15511         if (appData.debugMode)
15512           fprintf(debugFP, "Interrupting %s\n", cps->which);
15513         InterruptChildProcess(cps->pr);
15514         cps->maybeThinking = FALSE;
15515         break;
15516       default:
15517         break;
15518     }
15519 #endif /*ATTENTION*/
15520 }
15521
15522 int
15523 CheckFlags()
15524 {
15525     if (whiteTimeRemaining <= 0) {
15526         if (!whiteFlag) {
15527             whiteFlag = TRUE;
15528             if (appData.icsActive) {
15529                 if (appData.autoCallFlag &&
15530                     gameMode == IcsPlayingBlack && !blackFlag) {
15531                   SendToICS(ics_prefix);
15532                   SendToICS("flag\n");
15533                 }
15534             } else {
15535                 if (blackFlag) {
15536                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15537                 } else {
15538                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15539                     if (appData.autoCallFlag) {
15540                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15541                         return TRUE;
15542                     }
15543                 }
15544             }
15545         }
15546     }
15547     if (blackTimeRemaining <= 0) {
15548         if (!blackFlag) {
15549             blackFlag = TRUE;
15550             if (appData.icsActive) {
15551                 if (appData.autoCallFlag &&
15552                     gameMode == IcsPlayingWhite && !whiteFlag) {
15553                   SendToICS(ics_prefix);
15554                   SendToICS("flag\n");
15555                 }
15556             } else {
15557                 if (whiteFlag) {
15558                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15559                 } else {
15560                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15561                     if (appData.autoCallFlag) {
15562                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15563                         return TRUE;
15564                     }
15565                 }
15566             }
15567         }
15568     }
15569     return FALSE;
15570 }
15571
15572 void
15573 CheckTimeControl()
15574 {
15575     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15576         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15577
15578     /*
15579      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15580      */
15581     if ( !WhiteOnMove(forwardMostMove) ) {
15582         /* White made time control */
15583         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15584         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15585         /* [HGM] time odds: correct new time quota for time odds! */
15586                                             / WhitePlayer()->timeOdds;
15587         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15588     } else {
15589         lastBlack -= blackTimeRemaining;
15590         /* Black made time control */
15591         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15592                                             / WhitePlayer()->other->timeOdds;
15593         lastWhite = whiteTimeRemaining;
15594     }
15595 }
15596
15597 void
15598 DisplayBothClocks()
15599 {
15600     int wom = gameMode == EditPosition ?
15601       !blackPlaysFirst : WhiteOnMove(currentMove);
15602     DisplayWhiteClock(whiteTimeRemaining, wom);
15603     DisplayBlackClock(blackTimeRemaining, !wom);
15604 }
15605
15606
15607 /* Timekeeping seems to be a portability nightmare.  I think everyone
15608    has ftime(), but I'm really not sure, so I'm including some ifdefs
15609    to use other calls if you don't.  Clocks will be less accurate if
15610    you have neither ftime nor gettimeofday.
15611 */
15612
15613 /* VS 2008 requires the #include outside of the function */
15614 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15615 #include <sys/timeb.h>
15616 #endif
15617
15618 /* Get the current time as a TimeMark */
15619 void
15620 GetTimeMark(tm)
15621      TimeMark *tm;
15622 {
15623 #if HAVE_GETTIMEOFDAY
15624
15625     struct timeval timeVal;
15626     struct timezone timeZone;
15627
15628     gettimeofday(&timeVal, &timeZone);
15629     tm->sec = (long) timeVal.tv_sec;
15630     tm->ms = (int) (timeVal.tv_usec / 1000L);
15631
15632 #else /*!HAVE_GETTIMEOFDAY*/
15633 #if HAVE_FTIME
15634
15635 // include <sys/timeb.h> / moved to just above start of function
15636     struct timeb timeB;
15637
15638     ftime(&timeB);
15639     tm->sec = (long) timeB.time;
15640     tm->ms = (int) timeB.millitm;
15641
15642 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15643     tm->sec = (long) time(NULL);
15644     tm->ms = 0;
15645 #endif
15646 #endif
15647 }
15648
15649 /* Return the difference in milliseconds between two
15650    time marks.  We assume the difference will fit in a long!
15651 */
15652 long
15653 SubtractTimeMarks(tm2, tm1)
15654      TimeMark *tm2, *tm1;
15655 {
15656     return 1000L*(tm2->sec - tm1->sec) +
15657            (long) (tm2->ms - tm1->ms);
15658 }
15659
15660
15661 /*
15662  * Code to manage the game clocks.
15663  *
15664  * In tournament play, black starts the clock and then white makes a move.
15665  * We give the human user a slight advantage if he is playing white---the
15666  * clocks don't run until he makes his first move, so it takes zero time.
15667  * Also, we don't account for network lag, so we could get out of sync
15668  * with GNU Chess's clock -- but then, referees are always right.
15669  */
15670
15671 static TimeMark tickStartTM;
15672 static long intendedTickLength;
15673
15674 long
15675 NextTickLength(timeRemaining)
15676      long timeRemaining;
15677 {
15678     long nominalTickLength, nextTickLength;
15679
15680     if (timeRemaining > 0L && timeRemaining <= 10000L)
15681       nominalTickLength = 100L;
15682     else
15683       nominalTickLength = 1000L;
15684     nextTickLength = timeRemaining % nominalTickLength;
15685     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15686
15687     return nextTickLength;
15688 }
15689
15690 /* Adjust clock one minute up or down */
15691 void
15692 AdjustClock(Boolean which, int dir)
15693 {
15694     if(which) blackTimeRemaining += 60000*dir;
15695     else      whiteTimeRemaining += 60000*dir;
15696     DisplayBothClocks();
15697 }
15698
15699 /* Stop clocks and reset to a fresh time control */
15700 void
15701 ResetClocks()
15702 {
15703     (void) StopClockTimer();
15704     if (appData.icsActive) {
15705         whiteTimeRemaining = blackTimeRemaining = 0;
15706     } else if (searchTime) {
15707         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15708         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15709     } else { /* [HGM] correct new time quote for time odds */
15710         whiteTC = blackTC = fullTimeControlString;
15711         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15712         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15713     }
15714     if (whiteFlag || blackFlag) {
15715         DisplayTitle("");
15716         whiteFlag = blackFlag = FALSE;
15717     }
15718     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15719     DisplayBothClocks();
15720 }
15721
15722 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15723
15724 /* Decrement running clock by amount of time that has passed */
15725 void
15726 DecrementClocks()
15727 {
15728     long timeRemaining;
15729     long lastTickLength, fudge;
15730     TimeMark now;
15731
15732     if (!appData.clockMode) return;
15733     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15734
15735     GetTimeMark(&now);
15736
15737     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15738
15739     /* Fudge if we woke up a little too soon */
15740     fudge = intendedTickLength - lastTickLength;
15741     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15742
15743     if (WhiteOnMove(forwardMostMove)) {
15744         if(whiteNPS >= 0) lastTickLength = 0;
15745         timeRemaining = whiteTimeRemaining -= lastTickLength;
15746         if(timeRemaining < 0 && !appData.icsActive) {
15747             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15748             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15749                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15750                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15751             }
15752         }
15753         DisplayWhiteClock(whiteTimeRemaining - fudge,
15754                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15755     } else {
15756         if(blackNPS >= 0) lastTickLength = 0;
15757         timeRemaining = blackTimeRemaining -= lastTickLength;
15758         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15759             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15760             if(suddenDeath) {
15761                 blackStartMove = forwardMostMove;
15762                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15763             }
15764         }
15765         DisplayBlackClock(blackTimeRemaining - fudge,
15766                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15767     }
15768     if (CheckFlags()) return;
15769
15770     tickStartTM = now;
15771     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15772     StartClockTimer(intendedTickLength);
15773
15774     /* if the time remaining has fallen below the alarm threshold, sound the
15775      * alarm. if the alarm has sounded and (due to a takeback or time control
15776      * with increment) the time remaining has increased to a level above the
15777      * threshold, reset the alarm so it can sound again.
15778      */
15779
15780     if (appData.icsActive && appData.icsAlarm) {
15781
15782         /* make sure we are dealing with the user's clock */
15783         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15784                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15785            )) return;
15786
15787         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15788             alarmSounded = FALSE;
15789         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15790             PlayAlarmSound();
15791             alarmSounded = TRUE;
15792         }
15793     }
15794 }
15795
15796
15797 /* A player has just moved, so stop the previously running
15798    clock and (if in clock mode) start the other one.
15799    We redisplay both clocks in case we're in ICS mode, because
15800    ICS gives us an update to both clocks after every move.
15801    Note that this routine is called *after* forwardMostMove
15802    is updated, so the last fractional tick must be subtracted
15803    from the color that is *not* on move now.
15804 */
15805 void
15806 SwitchClocks(int newMoveNr)
15807 {
15808     long lastTickLength;
15809     TimeMark now;
15810     int flagged = FALSE;
15811
15812     GetTimeMark(&now);
15813
15814     if (StopClockTimer() && appData.clockMode) {
15815         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15816         if (!WhiteOnMove(forwardMostMove)) {
15817             if(blackNPS >= 0) lastTickLength = 0;
15818             blackTimeRemaining -= lastTickLength;
15819            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15820 //         if(pvInfoList[forwardMostMove].time == -1)
15821                  pvInfoList[forwardMostMove].time =               // use GUI time
15822                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15823         } else {
15824            if(whiteNPS >= 0) lastTickLength = 0;
15825            whiteTimeRemaining -= 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 =
15829                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15830         }
15831         flagged = CheckFlags();
15832     }
15833     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15834     CheckTimeControl();
15835
15836     if (flagged || !appData.clockMode) return;
15837
15838     switch (gameMode) {
15839       case MachinePlaysBlack:
15840       case MachinePlaysWhite:
15841       case BeginningOfGame:
15842         if (pausing) return;
15843         break;
15844
15845       case EditGame:
15846       case PlayFromGameFile:
15847       case IcsExamining:
15848         return;
15849
15850       default:
15851         break;
15852     }
15853
15854     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15855         if(WhiteOnMove(forwardMostMove))
15856              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15857         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15858     }
15859
15860     tickStartTM = now;
15861     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15862       whiteTimeRemaining : blackTimeRemaining);
15863     StartClockTimer(intendedTickLength);
15864 }
15865
15866
15867 /* Stop both clocks */
15868 void
15869 StopClocks()
15870 {
15871     long lastTickLength;
15872     TimeMark now;
15873
15874     if (!StopClockTimer()) return;
15875     if (!appData.clockMode) return;
15876
15877     GetTimeMark(&now);
15878
15879     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15880     if (WhiteOnMove(forwardMostMove)) {
15881         if(whiteNPS >= 0) lastTickLength = 0;
15882         whiteTimeRemaining -= lastTickLength;
15883         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15884     } else {
15885         if(blackNPS >= 0) lastTickLength = 0;
15886         blackTimeRemaining -= lastTickLength;
15887         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15888     }
15889     CheckFlags();
15890 }
15891
15892 /* Start clock of player on move.  Time may have been reset, so
15893    if clock is already running, stop and restart it. */
15894 void
15895 StartClocks()
15896 {
15897     (void) StopClockTimer(); /* in case it was running already */
15898     DisplayBothClocks();
15899     if (CheckFlags()) return;
15900
15901     if (!appData.clockMode) return;
15902     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15903
15904     GetTimeMark(&tickStartTM);
15905     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15906       whiteTimeRemaining : blackTimeRemaining);
15907
15908    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15909     whiteNPS = blackNPS = -1;
15910     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15911        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15912         whiteNPS = first.nps;
15913     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15914        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15915         blackNPS = first.nps;
15916     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15917         whiteNPS = second.nps;
15918     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15919         blackNPS = second.nps;
15920     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15921
15922     StartClockTimer(intendedTickLength);
15923 }
15924
15925 char *
15926 TimeString(ms)
15927      long ms;
15928 {
15929     long second, minute, hour, day;
15930     char *sign = "";
15931     static char buf[32];
15932
15933     if (ms > 0 && ms <= 9900) {
15934       /* convert milliseconds to tenths, rounding up */
15935       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15936
15937       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15938       return buf;
15939     }
15940
15941     /* convert milliseconds to seconds, rounding up */
15942     /* use floating point to avoid strangeness of integer division
15943        with negative dividends on many machines */
15944     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15945
15946     if (second < 0) {
15947         sign = "-";
15948         second = -second;
15949     }
15950
15951     day = second / (60 * 60 * 24);
15952     second = second % (60 * 60 * 24);
15953     hour = second / (60 * 60);
15954     second = second % (60 * 60);
15955     minute = second / 60;
15956     second = second % 60;
15957
15958     if (day > 0)
15959       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15960               sign, day, hour, minute, second);
15961     else if (hour > 0)
15962       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15963     else
15964       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15965
15966     return buf;
15967 }
15968
15969
15970 /*
15971  * This is necessary because some C libraries aren't ANSI C compliant yet.
15972  */
15973 char *
15974 StrStr(string, match)
15975      char *string, *match;
15976 {
15977     int i, length;
15978
15979     length = strlen(match);
15980
15981     for (i = strlen(string) - length; i >= 0; i--, string++)
15982       if (!strncmp(match, string, length))
15983         return string;
15984
15985     return NULL;
15986 }
15987
15988 char *
15989 StrCaseStr(string, match)
15990      char *string, *match;
15991 {
15992     int i, j, length;
15993
15994     length = strlen(match);
15995
15996     for (i = strlen(string) - length; i >= 0; i--, string++) {
15997         for (j = 0; j < length; j++) {
15998             if (ToLower(match[j]) != ToLower(string[j]))
15999               break;
16000         }
16001         if (j == length) return string;
16002     }
16003
16004     return NULL;
16005 }
16006
16007 #ifndef _amigados
16008 int
16009 StrCaseCmp(s1, s2)
16010      char *s1, *s2;
16011 {
16012     char c1, c2;
16013
16014     for (;;) {
16015         c1 = ToLower(*s1++);
16016         c2 = ToLower(*s2++);
16017         if (c1 > c2) return 1;
16018         if (c1 < c2) return -1;
16019         if (c1 == NULLCHAR) return 0;
16020     }
16021 }
16022
16023
16024 int
16025 ToLower(c)
16026      int c;
16027 {
16028     return isupper(c) ? tolower(c) : c;
16029 }
16030
16031
16032 int
16033 ToUpper(c)
16034      int c;
16035 {
16036     return islower(c) ? toupper(c) : c;
16037 }
16038 #endif /* !_amigados    */
16039
16040 char *
16041 StrSave(s)
16042      char *s;
16043 {
16044   char *ret;
16045
16046   if ((ret = (char *) malloc(strlen(s) + 1)))
16047     {
16048       safeStrCpy(ret, s, strlen(s)+1);
16049     }
16050   return ret;
16051 }
16052
16053 char *
16054 StrSavePtr(s, savePtr)
16055      char *s, **savePtr;
16056 {
16057     if (*savePtr) {
16058         free(*savePtr);
16059     }
16060     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16061       safeStrCpy(*savePtr, s, strlen(s)+1);
16062     }
16063     return(*savePtr);
16064 }
16065
16066 char *
16067 PGNDate()
16068 {
16069     time_t clock;
16070     struct tm *tm;
16071     char buf[MSG_SIZ];
16072
16073     clock = time((time_t *)NULL);
16074     tm = localtime(&clock);
16075     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16076             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16077     return StrSave(buf);
16078 }
16079
16080
16081 char *
16082 PositionToFEN(move, overrideCastling)
16083      int move;
16084      char *overrideCastling;
16085 {
16086     int i, j, fromX, fromY, toX, toY;
16087     int whiteToPlay;
16088     char buf[MSG_SIZ];
16089     char *p, *q;
16090     int emptycount;
16091     ChessSquare piece;
16092
16093     whiteToPlay = (gameMode == EditPosition) ?
16094       !blackPlaysFirst : (move % 2 == 0);
16095     p = buf;
16096
16097     /* Piece placement data */
16098     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16099         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16100         emptycount = 0;
16101         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16102             if (boards[move][i][j] == EmptySquare) {
16103                 emptycount++;
16104             } else { ChessSquare piece = boards[move][i][j];
16105                 if (emptycount > 0) {
16106                     if(emptycount<10) /* [HGM] can be >= 10 */
16107                         *p++ = '0' + emptycount;
16108                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16109                     emptycount = 0;
16110                 }
16111                 if(PieceToChar(piece) == '+') {
16112                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16113                     *p++ = '+';
16114                     piece = (ChessSquare)(DEMOTED piece);
16115                 }
16116                 *p++ = PieceToChar(piece);
16117                 if(p[-1] == '~') {
16118                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16119                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16120                     *p++ = '~';
16121                 }
16122             }
16123         }
16124         if (emptycount > 0) {
16125             if(emptycount<10) /* [HGM] can be >= 10 */
16126                 *p++ = '0' + emptycount;
16127             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16128             emptycount = 0;
16129         }
16130         *p++ = '/';
16131     }
16132     *(p - 1) = ' ';
16133
16134     /* [HGM] print Crazyhouse or Shogi holdings */
16135     if( gameInfo.holdingsWidth ) {
16136         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16137         q = p;
16138         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16139             piece = boards[move][i][BOARD_WIDTH-1];
16140             if( piece != EmptySquare )
16141               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16142                   *p++ = PieceToChar(piece);
16143         }
16144         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16145             piece = boards[move][BOARD_HEIGHT-i-1][0];
16146             if( piece != EmptySquare )
16147               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16148                   *p++ = PieceToChar(piece);
16149         }
16150
16151         if( q == p ) *p++ = '-';
16152         *p++ = ']';
16153         *p++ = ' ';
16154     }
16155
16156     /* Active color */
16157     *p++ = whiteToPlay ? 'w' : 'b';
16158     *p++ = ' ';
16159
16160   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16161     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16162   } else {
16163   if(nrCastlingRights) {
16164      q = p;
16165      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16166        /* [HGM] write directly from rights */
16167            if(boards[move][CASTLING][2] != NoRights &&
16168               boards[move][CASTLING][0] != NoRights   )
16169                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16170            if(boards[move][CASTLING][2] != NoRights &&
16171               boards[move][CASTLING][1] != NoRights   )
16172                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16173            if(boards[move][CASTLING][5] != NoRights &&
16174               boards[move][CASTLING][3] != NoRights   )
16175                 *p++ = boards[move][CASTLING][3] + AAA;
16176            if(boards[move][CASTLING][5] != NoRights &&
16177               boards[move][CASTLING][4] != NoRights   )
16178                 *p++ = boards[move][CASTLING][4] + AAA;
16179      } else {
16180
16181         /* [HGM] write true castling rights */
16182         if( nrCastlingRights == 6 ) {
16183             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16184                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16185             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16186                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16187             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16188                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16189             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16190                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16191         }
16192      }
16193      if (q == p) *p++ = '-'; /* No castling rights */
16194      *p++ = ' ';
16195   }
16196
16197   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16198      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16199     /* En passant target square */
16200     if (move > backwardMostMove) {
16201         fromX = moveList[move - 1][0] - AAA;
16202         fromY = moveList[move - 1][1] - ONE;
16203         toX = moveList[move - 1][2] - AAA;
16204         toY = moveList[move - 1][3] - ONE;
16205         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16206             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16207             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16208             fromX == toX) {
16209             /* 2-square pawn move just happened */
16210             *p++ = toX + AAA;
16211             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16212         } else {
16213             *p++ = '-';
16214         }
16215     } else if(move == backwardMostMove) {
16216         // [HGM] perhaps we should always do it like this, and forget the above?
16217         if((signed char)boards[move][EP_STATUS] >= 0) {
16218             *p++ = boards[move][EP_STATUS] + AAA;
16219             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16220         } else {
16221             *p++ = '-';
16222         }
16223     } else {
16224         *p++ = '-';
16225     }
16226     *p++ = ' ';
16227   }
16228   }
16229
16230     /* [HGM] find reversible plies */
16231     {   int i = 0, j=move;
16232
16233         if (appData.debugMode) { int k;
16234             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16235             for(k=backwardMostMove; k<=forwardMostMove; k++)
16236                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16237
16238         }
16239
16240         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16241         if( j == backwardMostMove ) i += initialRulePlies;
16242         sprintf(p, "%d ", i);
16243         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16244     }
16245     /* Fullmove number */
16246     sprintf(p, "%d", (move / 2) + 1);
16247
16248     return StrSave(buf);
16249 }
16250
16251 Boolean
16252 ParseFEN(board, blackPlaysFirst, fen)
16253     Board board;
16254      int *blackPlaysFirst;
16255      char *fen;
16256 {
16257     int i, j;
16258     char *p, c;
16259     int emptycount;
16260     ChessSquare piece;
16261
16262     p = fen;
16263
16264     /* [HGM] by default clear Crazyhouse holdings, if present */
16265     if(gameInfo.holdingsWidth) {
16266        for(i=0; i<BOARD_HEIGHT; i++) {
16267            board[i][0]             = EmptySquare; /* black holdings */
16268            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16269            board[i][1]             = (ChessSquare) 0; /* black counts */
16270            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16271        }
16272     }
16273
16274     /* Piece placement data */
16275     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16276         j = 0;
16277         for (;;) {
16278             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16279                 if (*p == '/') p++;
16280                 emptycount = gameInfo.boardWidth - j;
16281                 while (emptycount--)
16282                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16283                 break;
16284 #if(BOARD_FILES >= 10)
16285             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16286                 p++; emptycount=10;
16287                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16288                 while (emptycount--)
16289                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16290 #endif
16291             } else if (isdigit(*p)) {
16292                 emptycount = *p++ - '0';
16293                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16294                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16295                 while (emptycount--)
16296                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16297             } else if (*p == '+' || isalpha(*p)) {
16298                 if (j >= gameInfo.boardWidth) return FALSE;
16299                 if(*p=='+') {
16300                     piece = CharToPiece(*++p);
16301                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16302                     piece = (ChessSquare) (PROMOTED piece ); p++;
16303                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16304                 } else piece = CharToPiece(*p++);
16305
16306                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16307                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16308                     piece = (ChessSquare) (PROMOTED piece);
16309                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16310                     p++;
16311                 }
16312                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16313             } else {
16314                 return FALSE;
16315             }
16316         }
16317     }
16318     while (*p == '/' || *p == ' ') p++;
16319
16320     /* [HGM] look for Crazyhouse holdings here */
16321     while(*p==' ') p++;
16322     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16323         if(*p == '[') p++;
16324         if(*p == '-' ) p++; /* empty holdings */ else {
16325             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16326             /* if we would allow FEN reading to set board size, we would   */
16327             /* have to add holdings and shift the board read so far here   */
16328             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16329                 p++;
16330                 if((int) piece >= (int) BlackPawn ) {
16331                     i = (int)piece - (int)BlackPawn;
16332                     i = PieceToNumber((ChessSquare)i);
16333                     if( i >= gameInfo.holdingsSize ) return FALSE;
16334                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16335                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16336                 } else {
16337                     i = (int)piece - (int)WhitePawn;
16338                     i = PieceToNumber((ChessSquare)i);
16339                     if( i >= gameInfo.holdingsSize ) return FALSE;
16340                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16341                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16342                 }
16343             }
16344         }
16345         if(*p == ']') p++;
16346     }
16347
16348     while(*p == ' ') p++;
16349
16350     /* Active color */
16351     c = *p++;
16352     if(appData.colorNickNames) {
16353       if( c == appData.colorNickNames[0] ) c = 'w'; else
16354       if( c == appData.colorNickNames[1] ) c = 'b';
16355     }
16356     switch (c) {
16357       case 'w':
16358         *blackPlaysFirst = FALSE;
16359         break;
16360       case 'b':
16361         *blackPlaysFirst = TRUE;
16362         break;
16363       default:
16364         return FALSE;
16365     }
16366
16367     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16368     /* return the extra info in global variiables             */
16369
16370     /* set defaults in case FEN is incomplete */
16371     board[EP_STATUS] = EP_UNKNOWN;
16372     for(i=0; i<nrCastlingRights; i++ ) {
16373         board[CASTLING][i] =
16374             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16375     }   /* assume possible unless obviously impossible */
16376     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16377     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16378     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16379                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16380     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16381     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16382     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16383                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16384     FENrulePlies = 0;
16385
16386     while(*p==' ') p++;
16387     if(nrCastlingRights) {
16388       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16389           /* castling indicator present, so default becomes no castlings */
16390           for(i=0; i<nrCastlingRights; i++ ) {
16391                  board[CASTLING][i] = NoRights;
16392           }
16393       }
16394       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16395              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16396              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16397              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16398         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16399
16400         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16401             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16402             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16403         }
16404         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16405             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16406         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16407                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16408         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16409                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16410         switch(c) {
16411           case'K':
16412               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16413               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16414               board[CASTLING][2] = whiteKingFile;
16415               break;
16416           case'Q':
16417               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16418               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16419               board[CASTLING][2] = whiteKingFile;
16420               break;
16421           case'k':
16422               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16423               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16424               board[CASTLING][5] = blackKingFile;
16425               break;
16426           case'q':
16427               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16428               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16429               board[CASTLING][5] = blackKingFile;
16430           case '-':
16431               break;
16432           default: /* FRC castlings */
16433               if(c >= 'a') { /* black rights */
16434                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16435                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16436                   if(i == BOARD_RGHT) break;
16437                   board[CASTLING][5] = i;
16438                   c -= AAA;
16439                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16440                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16441                   if(c > i)
16442                       board[CASTLING][3] = c;
16443                   else
16444                       board[CASTLING][4] = c;
16445               } else { /* white rights */
16446                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16447                     if(board[0][i] == WhiteKing) break;
16448                   if(i == BOARD_RGHT) break;
16449                   board[CASTLING][2] = i;
16450                   c -= AAA - 'a' + 'A';
16451                   if(board[0][c] >= WhiteKing) break;
16452                   if(c > i)
16453                       board[CASTLING][0] = c;
16454                   else
16455                       board[CASTLING][1] = c;
16456               }
16457         }
16458       }
16459       for(i=0; i<nrCastlingRights; i++)
16460         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16461     if (appData.debugMode) {
16462         fprintf(debugFP, "FEN castling rights:");
16463         for(i=0; i<nrCastlingRights; i++)
16464         fprintf(debugFP, " %d", board[CASTLING][i]);
16465         fprintf(debugFP, "\n");
16466     }
16467
16468       while(*p==' ') p++;
16469     }
16470
16471     /* read e.p. field in games that know e.p. capture */
16472     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16473        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16474       if(*p=='-') {
16475         p++; board[EP_STATUS] = EP_NONE;
16476       } else {
16477          char c = *p++ - AAA;
16478
16479          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16480          if(*p >= '0' && *p <='9') p++;
16481          board[EP_STATUS] = c;
16482       }
16483     }
16484
16485
16486     if(sscanf(p, "%d", &i) == 1) {
16487         FENrulePlies = i; /* 50-move ply counter */
16488         /* (The move number is still ignored)    */
16489     }
16490
16491     return TRUE;
16492 }
16493
16494 void
16495 EditPositionPasteFEN(char *fen)
16496 {
16497   if (fen != NULL) {
16498     Board initial_position;
16499
16500     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16501       DisplayError(_("Bad FEN position in clipboard"), 0);
16502       return ;
16503     } else {
16504       int savedBlackPlaysFirst = blackPlaysFirst;
16505       EditPositionEvent();
16506       blackPlaysFirst = savedBlackPlaysFirst;
16507       CopyBoard(boards[0], initial_position);
16508       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16509       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16510       DisplayBothClocks();
16511       DrawPosition(FALSE, boards[currentMove]);
16512     }
16513   }
16514 }
16515
16516 static char cseq[12] = "\\   ";
16517
16518 Boolean set_cont_sequence(char *new_seq)
16519 {
16520     int len;
16521     Boolean ret;
16522
16523     // handle bad attempts to set the sequence
16524         if (!new_seq)
16525                 return 0; // acceptable error - no debug
16526
16527     len = strlen(new_seq);
16528     ret = (len > 0) && (len < sizeof(cseq));
16529     if (ret)
16530       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16531     else if (appData.debugMode)
16532       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16533     return ret;
16534 }
16535
16536 /*
16537     reformat a source message so words don't cross the width boundary.  internal
16538     newlines are not removed.  returns the wrapped size (no null character unless
16539     included in source message).  If dest is NULL, only calculate the size required
16540     for the dest buffer.  lp argument indicats line position upon entry, and it's
16541     passed back upon exit.
16542 */
16543 int wrap(char *dest, char *src, int count, int width, int *lp)
16544 {
16545     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16546
16547     cseq_len = strlen(cseq);
16548     old_line = line = *lp;
16549     ansi = len = clen = 0;
16550
16551     for (i=0; i < count; i++)
16552     {
16553         if (src[i] == '\033')
16554             ansi = 1;
16555
16556         // if we hit the width, back up
16557         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16558         {
16559             // store i & len in case the word is too long
16560             old_i = i, old_len = len;
16561
16562             // find the end of the last word
16563             while (i && src[i] != ' ' && src[i] != '\n')
16564             {
16565                 i--;
16566                 len--;
16567             }
16568
16569             // word too long?  restore i & len before splitting it
16570             if ((old_i-i+clen) >= width)
16571             {
16572                 i = old_i;
16573                 len = old_len;
16574             }
16575
16576             // extra space?
16577             if (i && src[i-1] == ' ')
16578                 len--;
16579
16580             if (src[i] != ' ' && src[i] != '\n')
16581             {
16582                 i--;
16583                 if (len)
16584                     len--;
16585             }
16586
16587             // now append the newline and continuation sequence
16588             if (dest)
16589                 dest[len] = '\n';
16590             len++;
16591             if (dest)
16592                 strncpy(dest+len, cseq, cseq_len);
16593             len += cseq_len;
16594             line = cseq_len;
16595             clen = cseq_len;
16596             continue;
16597         }
16598
16599         if (dest)
16600             dest[len] = src[i];
16601         len++;
16602         if (!ansi)
16603             line++;
16604         if (src[i] == '\n')
16605             line = 0;
16606         if (src[i] == 'm')
16607             ansi = 0;
16608     }
16609     if (dest && appData.debugMode)
16610     {
16611         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16612             count, width, line, len, *lp);
16613         show_bytes(debugFP, src, count);
16614         fprintf(debugFP, "\ndest: ");
16615         show_bytes(debugFP, dest, len);
16616         fprintf(debugFP, "\n");
16617     }
16618     *lp = dest ? line : old_line;
16619
16620     return len;
16621 }
16622
16623 // [HGM] vari: routines for shelving variations
16624 Boolean modeRestore = FALSE;
16625
16626 void
16627 PushInner(int firstMove, int lastMove)
16628 {
16629         int i, j, nrMoves = lastMove - firstMove;
16630
16631         // push current tail of game on stack
16632         savedResult[storedGames] = gameInfo.result;
16633         savedDetails[storedGames] = gameInfo.resultDetails;
16634         gameInfo.resultDetails = NULL;
16635         savedFirst[storedGames] = firstMove;
16636         savedLast [storedGames] = lastMove;
16637         savedFramePtr[storedGames] = framePtr;
16638         framePtr -= nrMoves; // reserve space for the boards
16639         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16640             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16641             for(j=0; j<MOVE_LEN; j++)
16642                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16643             for(j=0; j<2*MOVE_LEN; j++)
16644                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16645             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16646             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16647             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16648             pvInfoList[firstMove+i-1].depth = 0;
16649             commentList[framePtr+i] = commentList[firstMove+i];
16650             commentList[firstMove+i] = NULL;
16651         }
16652
16653         storedGames++;
16654         forwardMostMove = firstMove; // truncate game so we can start variation
16655 }
16656
16657 void
16658 PushTail(int firstMove, int lastMove)
16659 {
16660         if(appData.icsActive) { // only in local mode
16661                 forwardMostMove = currentMove; // mimic old ICS behavior
16662                 return;
16663         }
16664         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16665
16666         PushInner(firstMove, lastMove);
16667         if(storedGames == 1) GreyRevert(FALSE);
16668         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16669 }
16670
16671 void
16672 PopInner(Boolean annotate)
16673 {
16674         int i, j, nrMoves;
16675         char buf[8000], moveBuf[20];
16676
16677         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16678         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16679         nrMoves = savedLast[storedGames] - currentMove;
16680         if(annotate) {
16681                 int cnt = 10;
16682                 if(!WhiteOnMove(currentMove))
16683                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16684                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16685                 for(i=currentMove; i<forwardMostMove; i++) {
16686                         if(WhiteOnMove(i))
16687                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16688                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16689                         strcat(buf, moveBuf);
16690                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16691                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16692                 }
16693                 strcat(buf, ")");
16694         }
16695         for(i=1; i<=nrMoves; i++) { // copy last variation back
16696             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16697             for(j=0; j<MOVE_LEN; j++)
16698                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16699             for(j=0; j<2*MOVE_LEN; j++)
16700                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16701             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16702             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16703             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16704             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16705             commentList[currentMove+i] = commentList[framePtr+i];
16706             commentList[framePtr+i] = NULL;
16707         }
16708         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16709         framePtr = savedFramePtr[storedGames];
16710         gameInfo.result = savedResult[storedGames];
16711         if(gameInfo.resultDetails != NULL) {
16712             free(gameInfo.resultDetails);
16713       }
16714         gameInfo.resultDetails = savedDetails[storedGames];
16715         forwardMostMove = currentMove + nrMoves;
16716 }
16717
16718 Boolean
16719 PopTail(Boolean annotate)
16720 {
16721         if(appData.icsActive) return FALSE; // only in local mode
16722         if(!storedGames) return FALSE; // sanity
16723         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16724
16725         PopInner(annotate);
16726         if(currentMove < forwardMostMove) ForwardEvent(); else
16727         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16728
16729         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16730         return TRUE;
16731 }
16732
16733 void
16734 CleanupTail()
16735 {       // remove all shelved variations
16736         int i;
16737         for(i=0; i<storedGames; i++) {
16738             if(savedDetails[i])
16739                 free(savedDetails[i]);
16740             savedDetails[i] = NULL;
16741         }
16742         for(i=framePtr; i<MAX_MOVES; i++) {
16743                 if(commentList[i]) free(commentList[i]);
16744                 commentList[i] = NULL;
16745         }
16746         framePtr = MAX_MOVES-1;
16747         storedGames = 0;
16748 }
16749
16750 void
16751 LoadVariation(int index, char *text)
16752 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16753         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16754         int level = 0, move;
16755
16756         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16757         // first find outermost bracketing variation
16758         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16759             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16760                 if(*p == '{') wait = '}'; else
16761                 if(*p == '[') wait = ']'; else
16762                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16763                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16764             }
16765             if(*p == wait) wait = NULLCHAR; // closing ]} found
16766             p++;
16767         }
16768         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16769         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16770         end[1] = NULLCHAR; // clip off comment beyond variation
16771         ToNrEvent(currentMove-1);
16772         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16773         // kludge: use ParsePV() to append variation to game
16774         move = currentMove;
16775         ParsePV(start, TRUE, TRUE);
16776         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16777         ClearPremoveHighlights();
16778         CommentPopDown();
16779         ToNrEvent(currentMove+1);
16780 }
16781