Allow two-games-per-opening to work with book
[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     appData.seedBase = random() + (random()<<15);
985     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
986
987     ClearProgramStats();
988     programStats.ok_to_send = 1;
989     programStats.seen_stat = 0;
990
991     /*
992      * Initialize game list
993      */
994     ListNew(&gameList);
995
996
997     /*
998      * Internet chess server status
999      */
1000     if (appData.icsActive) {
1001         appData.matchMode = FALSE;
1002         appData.matchGames = 0;
1003 #if ZIPPY
1004         appData.noChessProgram = !appData.zippyPlay;
1005 #else
1006         appData.zippyPlay = FALSE;
1007         appData.zippyTalk = FALSE;
1008         appData.noChessProgram = TRUE;
1009 #endif
1010         if (*appData.icsHelper != NULLCHAR) {
1011             appData.useTelnet = TRUE;
1012             appData.telnetProgram = appData.icsHelper;
1013         }
1014     } else {
1015         appData.zippyTalk = appData.zippyPlay = FALSE;
1016     }
1017
1018     /* [AS] Initialize pv info list [HGM] and game state */
1019     {
1020         int i, j;
1021
1022         for( i=0; i<=framePtr; i++ ) {
1023             pvInfoList[i].depth = -1;
1024             boards[i][EP_STATUS] = EP_NONE;
1025             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1026         }
1027     }
1028
1029     InitTimeControls();
1030
1031     /* [AS] Adjudication threshold */
1032     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1033
1034     InitEngine(&first, 0);
1035     InitEngine(&second, 1);
1036     CommonEngineInit();
1037
1038     pairing.which = "pairing"; // pairing engine
1039     pairing.pr = NoProc;
1040     pairing.isr = NULL;
1041     pairing.program = appData.pairingEngine;
1042     pairing.host = "localhost";
1043     pairing.dir = ".";
1044
1045     if (appData.icsActive) {
1046         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1047     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1048         appData.clockMode = FALSE;
1049         first.sendTime = second.sendTime = 0;
1050     }
1051
1052 #if ZIPPY
1053     /* Override some settings from environment variables, for backward
1054        compatibility.  Unfortunately it's not feasible to have the env
1055        vars just set defaults, at least in xboard.  Ugh.
1056     */
1057     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1058       ZippyInit();
1059     }
1060 #endif
1061
1062     if (!appData.icsActive) {
1063       char buf[MSG_SIZ];
1064       int len;
1065
1066       /* Check for variants that are supported only in ICS mode,
1067          or not at all.  Some that are accepted here nevertheless
1068          have bugs; see comments below.
1069       */
1070       VariantClass variant = StringToVariant(appData.variant);
1071       switch (variant) {
1072       case VariantBughouse:     /* need four players and two boards */
1073       case VariantKriegspiel:   /* need to hide pieces and move details */
1074         /* case VariantFischeRandom: (Fabien: moved below) */
1075         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1076         if( (len > MSG_SIZ) && appData.debugMode )
1077           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1078
1079         DisplayFatalError(buf, 0, 2);
1080         return;
1081
1082       case VariantUnknown:
1083       case VariantLoadable:
1084       case Variant29:
1085       case Variant30:
1086       case Variant31:
1087       case Variant32:
1088       case Variant33:
1089       case Variant34:
1090       case Variant35:
1091       case Variant36:
1092       default:
1093         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1094         if( (len > MSG_SIZ) && appData.debugMode )
1095           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1096
1097         DisplayFatalError(buf, 0, 2);
1098         return;
1099
1100       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1101       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1102       case VariantGothic:     /* [HGM] should work */
1103       case VariantCapablanca: /* [HGM] should work */
1104       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1105       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1106       case VariantKnightmate: /* [HGM] should work */
1107       case VariantCylinder:   /* [HGM] untested */
1108       case VariantFalcon:     /* [HGM] untested */
1109       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1110                                  offboard interposition not understood */
1111       case VariantNormal:     /* definitely works! */
1112       case VariantWildCastle: /* pieces not automatically shuffled */
1113       case VariantNoCastle:   /* pieces not automatically shuffled */
1114       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1115       case VariantLosers:     /* should work except for win condition,
1116                                  and doesn't know captures are mandatory */
1117       case VariantSuicide:    /* should work except for win condition,
1118                                  and doesn't know captures are mandatory */
1119       case VariantGiveaway:   /* should work except for win condition,
1120                                  and doesn't know captures are mandatory */
1121       case VariantTwoKings:   /* should work */
1122       case VariantAtomic:     /* should work except for win condition */
1123       case Variant3Check:     /* should work except for win condition */
1124       case VariantShatranj:   /* should work except for all win conditions */
1125       case VariantMakruk:     /* should work except for draw countdown */
1126       case VariantBerolina:   /* might work if TestLegality is off */
1127       case VariantCapaRandom: /* should work */
1128       case VariantJanus:      /* should work */
1129       case VariantSuper:      /* experimental */
1130       case VariantGreat:      /* experimental, requires legality testing to be off */
1131       case VariantSChess:     /* S-Chess, should work */
1132       case VariantGrand:      /* should work */
1133       case VariantSpartan:    /* should work */
1134         break;
1135       }
1136     }
1137
1138 }
1139
1140 int NextIntegerFromString( char ** str, long * value )
1141 {
1142     int result = -1;
1143     char * s = *str;
1144
1145     while( *s == ' ' || *s == '\t' ) {
1146         s++;
1147     }
1148
1149     *value = 0;
1150
1151     if( *s >= '0' && *s <= '9' ) {
1152         while( *s >= '0' && *s <= '9' ) {
1153             *value = *value * 10 + (*s - '0');
1154             s++;
1155         }
1156
1157         result = 0;
1158     }
1159
1160     *str = s;
1161
1162     return result;
1163 }
1164
1165 int NextTimeControlFromString( char ** str, long * value )
1166 {
1167     long temp;
1168     int result = NextIntegerFromString( str, &temp );
1169
1170     if( result == 0 ) {
1171         *value = temp * 60; /* Minutes */
1172         if( **str == ':' ) {
1173             (*str)++;
1174             result = NextIntegerFromString( str, &temp );
1175             *value += temp; /* Seconds */
1176         }
1177     }
1178
1179     return result;
1180 }
1181
1182 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1183 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1184     int result = -1, type = 0; long temp, temp2;
1185
1186     if(**str != ':') return -1; // old params remain in force!
1187     (*str)++;
1188     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1189     if( NextIntegerFromString( str, &temp ) ) return -1;
1190     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1191
1192     if(**str != '/') {
1193         /* time only: incremental or sudden-death time control */
1194         if(**str == '+') { /* increment follows; read it */
1195             (*str)++;
1196             if(**str == '!') type = *(*str)++; // Bronstein TC
1197             if(result = NextIntegerFromString( str, &temp2)) return -1;
1198             *inc = temp2 * 1000;
1199             if(**str == '.') { // read fraction of increment
1200                 char *start = ++(*str);
1201                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1202                 temp2 *= 1000;
1203                 while(start++ < *str) temp2 /= 10;
1204                 *inc += temp2;
1205             }
1206         } else *inc = 0;
1207         *moves = 0; *tc = temp * 1000; *incType = type;
1208         return 0;
1209     }
1210
1211     (*str)++; /* classical time control */
1212     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1213
1214     if(result == 0) {
1215         *moves = temp;
1216         *tc    = temp2 * 1000;
1217         *inc   = 0;
1218         *incType = type;
1219     }
1220     return result;
1221 }
1222
1223 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1224 {   /* [HGM] get time to add from the multi-session time-control string */
1225     int incType, moves=1; /* kludge to force reading of first session */
1226     long time, increment;
1227     char *s = tcString;
1228
1229     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1230     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1231     do {
1232         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1233         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1234         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1235         if(movenr == -1) return time;    /* last move before new session     */
1236         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1237         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1238         if(!moves) return increment;     /* current session is incremental   */
1239         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1240     } while(movenr >= -1);               /* try again for next session       */
1241
1242     return 0; // no new time quota on this move
1243 }
1244
1245 int
1246 ParseTimeControl(tc, ti, mps)
1247      char *tc;
1248      float ti;
1249      int mps;
1250 {
1251   long tc1;
1252   long tc2;
1253   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1254   int min, sec=0;
1255
1256   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1257   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1258       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1259   if(ti > 0) {
1260
1261     if(mps)
1262       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1263     else 
1264       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1265   } else {
1266     if(mps)
1267       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1268     else 
1269       snprintf(buf, MSG_SIZ, ":%s", mytc);
1270   }
1271   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1272   
1273   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1274     return FALSE;
1275   }
1276
1277   if( *tc == '/' ) {
1278     /* Parse second time control */
1279     tc++;
1280
1281     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1282       return FALSE;
1283     }
1284
1285     if( tc2 == 0 ) {
1286       return FALSE;
1287     }
1288
1289     timeControl_2 = tc2 * 1000;
1290   }
1291   else {
1292     timeControl_2 = 0;
1293   }
1294
1295   if( tc1 == 0 ) {
1296     return FALSE;
1297   }
1298
1299   timeControl = tc1 * 1000;
1300
1301   if (ti >= 0) {
1302     timeIncrement = ti * 1000;  /* convert to ms */
1303     movesPerSession = 0;
1304   } else {
1305     timeIncrement = 0;
1306     movesPerSession = mps;
1307   }
1308   return TRUE;
1309 }
1310
1311 void
1312 InitBackEnd2()
1313 {
1314     if (appData.debugMode) {
1315         fprintf(debugFP, "%s\n", programVersion);
1316     }
1317
1318     set_cont_sequence(appData.wrapContSeq);
1319     if (appData.matchGames > 0) {
1320         appData.matchMode = TRUE;
1321     } else if (appData.matchMode) {
1322         appData.matchGames = 1;
1323     }
1324     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1325         appData.matchGames = appData.sameColorGames;
1326     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1327         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1328         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1329     }
1330     Reset(TRUE, FALSE);
1331     if (appData.noChessProgram || first.protocolVersion == 1) {
1332       InitBackEnd3();
1333     } else {
1334       /* kludge: allow timeout for initial "feature" commands */
1335       FreezeUI();
1336       DisplayMessage("", _("Starting chess program"));
1337       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1338     }
1339 }
1340
1341 int
1342 CalculateIndex(int index, int gameNr)
1343 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1344     int res;
1345     if(index > 0) return index; // fixed nmber
1346     if(index == 0) return 1;
1347     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1348     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1349     return res;
1350 }
1351
1352 int
1353 LoadGameOrPosition(int gameNr)
1354 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1355     if (*appData.loadGameFile != NULLCHAR) {
1356         if (!LoadGameFromFile(appData.loadGameFile,
1357                 CalculateIndex(appData.loadGameIndex, gameNr),
1358                               appData.loadGameFile, FALSE)) {
1359             DisplayFatalError(_("Bad game file"), 0, 1);
1360             return 0;
1361         }
1362     } else if (*appData.loadPositionFile != NULLCHAR) {
1363         if (!LoadPositionFromFile(appData.loadPositionFile,
1364                 CalculateIndex(appData.loadPositionIndex, gameNr),
1365                                   appData.loadPositionFile)) {
1366             DisplayFatalError(_("Bad position file"), 0, 1);
1367             return 0;
1368         }
1369     }
1370     return 1;
1371 }
1372
1373 void
1374 ReserveGame(int gameNr, char resChar)
1375 {
1376     FILE *tf = fopen(appData.tourneyFile, "r+");
1377     char *p, *q, c, buf[MSG_SIZ];
1378     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1379     safeStrCpy(buf, lastMsg, MSG_SIZ);
1380     DisplayMessage(_("Pick new game"), "");
1381     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1382     ParseArgsFromFile(tf);
1383     p = q = appData.results;
1384     if(appData.debugMode) {
1385       char *r = appData.participants;
1386       fprintf(debugFP, "results = '%s'\n", p);
1387       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1388       fprintf(debugFP, "\n");
1389     }
1390     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1391     nextGame = q - p;
1392     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1393     safeStrCpy(q, p, strlen(p) + 2);
1394     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1395     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1396     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1397         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1398         q[nextGame] = '*';
1399     }
1400     fseek(tf, -(strlen(p)+4), SEEK_END);
1401     c = fgetc(tf);
1402     if(c != '"') // depending on DOS or Unix line endings we can be one off
1403          fseek(tf, -(strlen(p)+2), SEEK_END);
1404     else fseek(tf, -(strlen(p)+3), SEEK_END);
1405     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1406     DisplayMessage(buf, "");
1407     free(p); appData.results = q;
1408     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1409        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1410         UnloadEngine(&first);  // next game belongs to other pairing;
1411         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1412     }
1413 }
1414
1415 void
1416 MatchEvent(int mode)
1417 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1418         int dummy;
1419         if(matchMode) { // already in match mode: switch it off
1420             abortMatch = TRUE;
1421             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1422             return;
1423         }
1424 //      if(gameMode != BeginningOfGame) {
1425 //          DisplayError(_("You can only start a match from the initial position."), 0);
1426 //          return;
1427 //      }
1428         abortMatch = FALSE;
1429         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1430         /* Set up machine vs. machine match */
1431         nextGame = 0;
1432         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1433         if(appData.tourneyFile[0]) {
1434             ReserveGame(-1, 0);
1435             if(nextGame > appData.matchGames) {
1436                 char buf[MSG_SIZ];
1437                 if(strchr(appData.results, '*') == NULL) {
1438                     FILE *f;
1439                     appData.tourneyCycles++;
1440                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1441                         fclose(f);
1442                         NextTourneyGame(-1, &dummy);
1443                         ReserveGame(-1, 0);
1444                         if(nextGame <= appData.matchGames) {
1445                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1446                             matchMode = mode;
1447                             ScheduleDelayedEvent(NextMatchGame, 10000);
1448                             return;
1449                         }
1450                     }
1451                 }
1452                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1453                 DisplayError(buf, 0);
1454                 appData.tourneyFile[0] = 0;
1455                 return;
1456             }
1457         } else
1458         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1459             DisplayFatalError(_("Can't have a match with no chess programs"),
1460                               0, 2);
1461             return;
1462         }
1463         matchMode = mode;
1464         matchGame = roundNr = 1;
1465         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1466         NextMatchGame();
1467 }
1468
1469 void
1470 InitBackEnd3 P((void))
1471 {
1472     GameMode initialMode;
1473     char buf[MSG_SIZ];
1474     int err, len;
1475
1476     InitChessProgram(&first, startedFromSetupPosition);
1477
1478     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1479         free(programVersion);
1480         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1481         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1482     }
1483
1484     if (appData.icsActive) {
1485 #ifdef WIN32
1486         /* [DM] Make a console window if needed [HGM] merged ifs */
1487         ConsoleCreate();
1488 #endif
1489         err = establish();
1490         if (err != 0)
1491           {
1492             if (*appData.icsCommPort != NULLCHAR)
1493               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1494                              appData.icsCommPort);
1495             else
1496               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1497                         appData.icsHost, appData.icsPort);
1498
1499             if( (len > MSG_SIZ) && appData.debugMode )
1500               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1501
1502             DisplayFatalError(buf, err, 1);
1503             return;
1504         }
1505         SetICSMode();
1506         telnetISR =
1507           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1508         fromUserISR =
1509           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1510         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1511             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1512     } else if (appData.noChessProgram) {
1513         SetNCPMode();
1514     } else {
1515         SetGNUMode();
1516     }
1517
1518     if (*appData.cmailGameName != NULLCHAR) {
1519         SetCmailMode();
1520         OpenLoopback(&cmailPR);
1521         cmailISR =
1522           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1523     }
1524
1525     ThawUI();
1526     DisplayMessage("", "");
1527     if (StrCaseCmp(appData.initialMode, "") == 0) {
1528       initialMode = BeginningOfGame;
1529       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1530         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1531         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1532         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1533         ModeHighlight();
1534       }
1535     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1536       initialMode = TwoMachinesPlay;
1537     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1538       initialMode = AnalyzeFile;
1539     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1540       initialMode = AnalyzeMode;
1541     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1542       initialMode = MachinePlaysWhite;
1543     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1544       initialMode = MachinePlaysBlack;
1545     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1546       initialMode = EditGame;
1547     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1548       initialMode = EditPosition;
1549     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1550       initialMode = Training;
1551     } else {
1552       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1553       if( (len > MSG_SIZ) && appData.debugMode )
1554         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1555
1556       DisplayFatalError(buf, 0, 2);
1557       return;
1558     }
1559
1560     if (appData.matchMode) {
1561         if(appData.tourneyFile[0]) { // start tourney from command line
1562             FILE *f;
1563             if(f = fopen(appData.tourneyFile, "r")) {
1564                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1565                 fclose(f);
1566                 appData.clockMode = TRUE;
1567                 SetGNUMode();
1568             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1569         }
1570         MatchEvent(TRUE);
1571     } else if (*appData.cmailGameName != NULLCHAR) {
1572         /* Set up cmail mode */
1573         ReloadCmailMsgEvent(TRUE);
1574     } else {
1575         /* Set up other modes */
1576         if (initialMode == AnalyzeFile) {
1577           if (*appData.loadGameFile == NULLCHAR) {
1578             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1579             return;
1580           }
1581         }
1582         if (*appData.loadGameFile != NULLCHAR) {
1583             (void) LoadGameFromFile(appData.loadGameFile,
1584                                     appData.loadGameIndex,
1585                                     appData.loadGameFile, TRUE);
1586         } else if (*appData.loadPositionFile != NULLCHAR) {
1587             (void) LoadPositionFromFile(appData.loadPositionFile,
1588                                         appData.loadPositionIndex,
1589                                         appData.loadPositionFile);
1590             /* [HGM] try to make self-starting even after FEN load */
1591             /* to allow automatic setup of fairy variants with wtm */
1592             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1593                 gameMode = BeginningOfGame;
1594                 setboardSpoiledMachineBlack = 1;
1595             }
1596             /* [HGM] loadPos: make that every new game uses the setup */
1597             /* from file as long as we do not switch variant          */
1598             if(!blackPlaysFirst) {
1599                 startedFromPositionFile = TRUE;
1600                 CopyBoard(filePosition, boards[0]);
1601             }
1602         }
1603         if (initialMode == AnalyzeMode) {
1604           if (appData.noChessProgram) {
1605             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1606             return;
1607           }
1608           if (appData.icsActive) {
1609             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1610             return;
1611           }
1612           AnalyzeModeEvent();
1613         } else if (initialMode == AnalyzeFile) {
1614           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1615           ShowThinkingEvent();
1616           AnalyzeFileEvent();
1617           AnalysisPeriodicEvent(1);
1618         } else if (initialMode == MachinePlaysWhite) {
1619           if (appData.noChessProgram) {
1620             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1621                               0, 2);
1622             return;
1623           }
1624           if (appData.icsActive) {
1625             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1626                               0, 2);
1627             return;
1628           }
1629           MachineWhiteEvent();
1630         } else if (initialMode == MachinePlaysBlack) {
1631           if (appData.noChessProgram) {
1632             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1633                               0, 2);
1634             return;
1635           }
1636           if (appData.icsActive) {
1637             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1638                               0, 2);
1639             return;
1640           }
1641           MachineBlackEvent();
1642         } else if (initialMode == TwoMachinesPlay) {
1643           if (appData.noChessProgram) {
1644             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1645                               0, 2);
1646             return;
1647           }
1648           if (appData.icsActive) {
1649             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1650                               0, 2);
1651             return;
1652           }
1653           TwoMachinesEvent();
1654         } else if (initialMode == EditGame) {
1655           EditGameEvent();
1656         } else if (initialMode == EditPosition) {
1657           EditPositionEvent();
1658         } else if (initialMode == Training) {
1659           if (*appData.loadGameFile == NULLCHAR) {
1660             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1661             return;
1662           }
1663           TrainingEvent();
1664         }
1665     }
1666 }
1667
1668 /*
1669  * Establish will establish a contact to a remote host.port.
1670  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1671  *  used to talk to the host.
1672  * Returns 0 if okay, error code if not.
1673  */
1674 int
1675 establish()
1676 {
1677     char buf[MSG_SIZ];
1678
1679     if (*appData.icsCommPort != NULLCHAR) {
1680         /* Talk to the host through a serial comm port */
1681         return OpenCommPort(appData.icsCommPort, &icsPR);
1682
1683     } else if (*appData.gateway != NULLCHAR) {
1684         if (*appData.remoteShell == NULLCHAR) {
1685             /* Use the rcmd protocol to run telnet program on a gateway host */
1686             snprintf(buf, sizeof(buf), "%s %s %s",
1687                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1688             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1689
1690         } else {
1691             /* Use the rsh program to run telnet program on a gateway host */
1692             if (*appData.remoteUser == NULLCHAR) {
1693                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1694                         appData.gateway, appData.telnetProgram,
1695                         appData.icsHost, appData.icsPort);
1696             } else {
1697                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1698                         appData.remoteShell, appData.gateway,
1699                         appData.remoteUser, appData.telnetProgram,
1700                         appData.icsHost, appData.icsPort);
1701             }
1702             return StartChildProcess(buf, "", &icsPR);
1703
1704         }
1705     } else if (appData.useTelnet) {
1706         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1707
1708     } else {
1709         /* TCP socket interface differs somewhat between
1710            Unix and NT; handle details in the front end.
1711            */
1712         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1713     }
1714 }
1715
1716 void EscapeExpand(char *p, char *q)
1717 {       // [HGM] initstring: routine to shape up string arguments
1718         while(*p++ = *q++) if(p[-1] == '\\')
1719             switch(*q++) {
1720                 case 'n': p[-1] = '\n'; break;
1721                 case 'r': p[-1] = '\r'; break;
1722                 case 't': p[-1] = '\t'; break;
1723                 case '\\': p[-1] = '\\'; break;
1724                 case 0: *p = 0; return;
1725                 default: p[-1] = q[-1]; break;
1726             }
1727 }
1728
1729 void
1730 show_bytes(fp, buf, count)
1731      FILE *fp;
1732      char *buf;
1733      int count;
1734 {
1735     while (count--) {
1736         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1737             fprintf(fp, "\\%03o", *buf & 0xff);
1738         } else {
1739             putc(*buf, fp);
1740         }
1741         buf++;
1742     }
1743     fflush(fp);
1744 }
1745
1746 /* Returns an errno value */
1747 int
1748 OutputMaybeTelnet(pr, message, count, outError)
1749      ProcRef pr;
1750      char *message;
1751      int count;
1752      int *outError;
1753 {
1754     char buf[8192], *p, *q, *buflim;
1755     int left, newcount, outcount;
1756
1757     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1758         *appData.gateway != NULLCHAR) {
1759         if (appData.debugMode) {
1760             fprintf(debugFP, ">ICS: ");
1761             show_bytes(debugFP, message, count);
1762             fprintf(debugFP, "\n");
1763         }
1764         return OutputToProcess(pr, message, count, outError);
1765     }
1766
1767     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1768     p = message;
1769     q = buf;
1770     left = count;
1771     newcount = 0;
1772     while (left) {
1773         if (q >= buflim) {
1774             if (appData.debugMode) {
1775                 fprintf(debugFP, ">ICS: ");
1776                 show_bytes(debugFP, buf, newcount);
1777                 fprintf(debugFP, "\n");
1778             }
1779             outcount = OutputToProcess(pr, buf, newcount, outError);
1780             if (outcount < newcount) return -1; /* to be sure */
1781             q = buf;
1782             newcount = 0;
1783         }
1784         if (*p == '\n') {
1785             *q++ = '\r';
1786             newcount++;
1787         } else if (((unsigned char) *p) == TN_IAC) {
1788             *q++ = (char) TN_IAC;
1789             newcount ++;
1790         }
1791         *q++ = *p++;
1792         newcount++;
1793         left--;
1794     }
1795     if (appData.debugMode) {
1796         fprintf(debugFP, ">ICS: ");
1797         show_bytes(debugFP, buf, newcount);
1798         fprintf(debugFP, "\n");
1799     }
1800     outcount = OutputToProcess(pr, buf, newcount, outError);
1801     if (outcount < newcount) return -1; /* to be sure */
1802     return count;
1803 }
1804
1805 void
1806 read_from_player(isr, closure, message, count, error)
1807      InputSourceRef isr;
1808      VOIDSTAR closure;
1809      char *message;
1810      int count;
1811      int error;
1812 {
1813     int outError, outCount;
1814     static int gotEof = 0;
1815
1816     /* Pass data read from player on to ICS */
1817     if (count > 0) {
1818         gotEof = 0;
1819         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1820         if (outCount < count) {
1821             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1822         }
1823     } else if (count < 0) {
1824         RemoveInputSource(isr);
1825         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1826     } else if (gotEof++ > 0) {
1827         RemoveInputSource(isr);
1828         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1829     }
1830 }
1831
1832 void
1833 KeepAlive()
1834 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1835     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1836     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1837     SendToICS("date\n");
1838     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1839 }
1840
1841 /* added routine for printf style output to ics */
1842 void ics_printf(char *format, ...)
1843 {
1844     char buffer[MSG_SIZ];
1845     va_list args;
1846
1847     va_start(args, format);
1848     vsnprintf(buffer, sizeof(buffer), format, args);
1849     buffer[sizeof(buffer)-1] = '\0';
1850     SendToICS(buffer);
1851     va_end(args);
1852 }
1853
1854 void
1855 SendToICS(s)
1856      char *s;
1857 {
1858     int count, outCount, outError;
1859
1860     if (icsPR == NULL) return;
1861
1862     count = strlen(s);
1863     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1864     if (outCount < count) {
1865         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1866     }
1867 }
1868
1869 /* This is used for sending logon scripts to the ICS. Sending
1870    without a delay causes problems when using timestamp on ICC
1871    (at least on my machine). */
1872 void
1873 SendToICSDelayed(s,msdelay)
1874      char *s;
1875      long msdelay;
1876 {
1877     int count, outCount, outError;
1878
1879     if (icsPR == NULL) return;
1880
1881     count = strlen(s);
1882     if (appData.debugMode) {
1883         fprintf(debugFP, ">ICS: ");
1884         show_bytes(debugFP, s, count);
1885         fprintf(debugFP, "\n");
1886     }
1887     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1888                                       msdelay);
1889     if (outCount < count) {
1890         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1891     }
1892 }
1893
1894
1895 /* Remove all highlighting escape sequences in s
1896    Also deletes any suffix starting with '('
1897    */
1898 char *
1899 StripHighlightAndTitle(s)
1900      char *s;
1901 {
1902     static char retbuf[MSG_SIZ];
1903     char *p = retbuf;
1904
1905     while (*s != NULLCHAR) {
1906         while (*s == '\033') {
1907             while (*s != NULLCHAR && !isalpha(*s)) s++;
1908             if (*s != NULLCHAR) s++;
1909         }
1910         while (*s != NULLCHAR && *s != '\033') {
1911             if (*s == '(' || *s == '[') {
1912                 *p = NULLCHAR;
1913                 return retbuf;
1914             }
1915             *p++ = *s++;
1916         }
1917     }
1918     *p = NULLCHAR;
1919     return retbuf;
1920 }
1921
1922 /* Remove all highlighting escape sequences in s */
1923 char *
1924 StripHighlight(s)
1925      char *s;
1926 {
1927     static char retbuf[MSG_SIZ];
1928     char *p = retbuf;
1929
1930     while (*s != NULLCHAR) {
1931         while (*s == '\033') {
1932             while (*s != NULLCHAR && !isalpha(*s)) s++;
1933             if (*s != NULLCHAR) s++;
1934         }
1935         while (*s != NULLCHAR && *s != '\033') {
1936             *p++ = *s++;
1937         }
1938     }
1939     *p = NULLCHAR;
1940     return retbuf;
1941 }
1942
1943 char *variantNames[] = VARIANT_NAMES;
1944 char *
1945 VariantName(v)
1946      VariantClass v;
1947 {
1948     return variantNames[v];
1949 }
1950
1951
1952 /* Identify a variant from the strings the chess servers use or the
1953    PGN Variant tag names we use. */
1954 VariantClass
1955 StringToVariant(e)
1956      char *e;
1957 {
1958     char *p;
1959     int wnum = -1;
1960     VariantClass v = VariantNormal;
1961     int i, found = FALSE;
1962     char buf[MSG_SIZ];
1963     int len;
1964
1965     if (!e) return v;
1966
1967     /* [HGM] skip over optional board-size prefixes */
1968     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1969         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1970         while( *e++ != '_');
1971     }
1972
1973     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1974         v = VariantNormal;
1975         found = TRUE;
1976     } else
1977     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1978       if (StrCaseStr(e, variantNames[i])) {
1979         v = (VariantClass) i;
1980         found = TRUE;
1981         break;
1982       }
1983     }
1984
1985     if (!found) {
1986       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1987           || StrCaseStr(e, "wild/fr")
1988           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1989         v = VariantFischeRandom;
1990       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1991                  (i = 1, p = StrCaseStr(e, "w"))) {
1992         p += i;
1993         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1994         if (isdigit(*p)) {
1995           wnum = atoi(p);
1996         } else {
1997           wnum = -1;
1998         }
1999         switch (wnum) {
2000         case 0: /* FICS only, actually */
2001         case 1:
2002           /* Castling legal even if K starts on d-file */
2003           v = VariantWildCastle;
2004           break;
2005         case 2:
2006         case 3:
2007         case 4:
2008           /* Castling illegal even if K & R happen to start in
2009              normal positions. */
2010           v = VariantNoCastle;
2011           break;
2012         case 5:
2013         case 7:
2014         case 8:
2015         case 10:
2016         case 11:
2017         case 12:
2018         case 13:
2019         case 14:
2020         case 15:
2021         case 18:
2022         case 19:
2023           /* Castling legal iff K & R start in normal positions */
2024           v = VariantNormal;
2025           break;
2026         case 6:
2027         case 20:
2028         case 21:
2029           /* Special wilds for position setup; unclear what to do here */
2030           v = VariantLoadable;
2031           break;
2032         case 9:
2033           /* Bizarre ICC game */
2034           v = VariantTwoKings;
2035           break;
2036         case 16:
2037           v = VariantKriegspiel;
2038           break;
2039         case 17:
2040           v = VariantLosers;
2041           break;
2042         case 22:
2043           v = VariantFischeRandom;
2044           break;
2045         case 23:
2046           v = VariantCrazyhouse;
2047           break;
2048         case 24:
2049           v = VariantBughouse;
2050           break;
2051         case 25:
2052           v = Variant3Check;
2053           break;
2054         case 26:
2055           /* Not quite the same as FICS suicide! */
2056           v = VariantGiveaway;
2057           break;
2058         case 27:
2059           v = VariantAtomic;
2060           break;
2061         case 28:
2062           v = VariantShatranj;
2063           break;
2064
2065         /* Temporary names for future ICC types.  The name *will* change in
2066            the next xboard/WinBoard release after ICC defines it. */
2067         case 29:
2068           v = Variant29;
2069           break;
2070         case 30:
2071           v = Variant30;
2072           break;
2073         case 31:
2074           v = Variant31;
2075           break;
2076         case 32:
2077           v = Variant32;
2078           break;
2079         case 33:
2080           v = Variant33;
2081           break;
2082         case 34:
2083           v = Variant34;
2084           break;
2085         case 35:
2086           v = Variant35;
2087           break;
2088         case 36:
2089           v = Variant36;
2090           break;
2091         case 37:
2092           v = VariantShogi;
2093           break;
2094         case 38:
2095           v = VariantXiangqi;
2096           break;
2097         case 39:
2098           v = VariantCourier;
2099           break;
2100         case 40:
2101           v = VariantGothic;
2102           break;
2103         case 41:
2104           v = VariantCapablanca;
2105           break;
2106         case 42:
2107           v = VariantKnightmate;
2108           break;
2109         case 43:
2110           v = VariantFairy;
2111           break;
2112         case 44:
2113           v = VariantCylinder;
2114           break;
2115         case 45:
2116           v = VariantFalcon;
2117           break;
2118         case 46:
2119           v = VariantCapaRandom;
2120           break;
2121         case 47:
2122           v = VariantBerolina;
2123           break;
2124         case 48:
2125           v = VariantJanus;
2126           break;
2127         case 49:
2128           v = VariantSuper;
2129           break;
2130         case 50:
2131           v = VariantGreat;
2132           break;
2133         case -1:
2134           /* Found "wild" or "w" in the string but no number;
2135              must assume it's normal chess. */
2136           v = VariantNormal;
2137           break;
2138         default:
2139           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2140           if( (len > MSG_SIZ) && appData.debugMode )
2141             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2142
2143           DisplayError(buf, 0);
2144           v = VariantUnknown;
2145           break;
2146         }
2147       }
2148     }
2149     if (appData.debugMode) {
2150       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2151               e, wnum, VariantName(v));
2152     }
2153     return v;
2154 }
2155
2156 static int leftover_start = 0, leftover_len = 0;
2157 char star_match[STAR_MATCH_N][MSG_SIZ];
2158
2159 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2160    advance *index beyond it, and set leftover_start to the new value of
2161    *index; else return FALSE.  If pattern contains the character '*', it
2162    matches any sequence of characters not containing '\r', '\n', or the
2163    character following the '*' (if any), and the matched sequence(s) are
2164    copied into star_match.
2165    */
2166 int
2167 looking_at(buf, index, pattern)
2168      char *buf;
2169      int *index;
2170      char *pattern;
2171 {
2172     char *bufp = &buf[*index], *patternp = pattern;
2173     int star_count = 0;
2174     char *matchp = star_match[0];
2175
2176     for (;;) {
2177         if (*patternp == NULLCHAR) {
2178             *index = leftover_start = bufp - buf;
2179             *matchp = NULLCHAR;
2180             return TRUE;
2181         }
2182         if (*bufp == NULLCHAR) return FALSE;
2183         if (*patternp == '*') {
2184             if (*bufp == *(patternp + 1)) {
2185                 *matchp = NULLCHAR;
2186                 matchp = star_match[++star_count];
2187                 patternp += 2;
2188                 bufp++;
2189                 continue;
2190             } else if (*bufp == '\n' || *bufp == '\r') {
2191                 patternp++;
2192                 if (*patternp == NULLCHAR)
2193                   continue;
2194                 else
2195                   return FALSE;
2196             } else {
2197                 *matchp++ = *bufp++;
2198                 continue;
2199             }
2200         }
2201         if (*patternp != *bufp) return FALSE;
2202         patternp++;
2203         bufp++;
2204     }
2205 }
2206
2207 void
2208 SendToPlayer(data, length)
2209      char *data;
2210      int length;
2211 {
2212     int error, outCount;
2213     outCount = OutputToProcess(NoProc, data, length, &error);
2214     if (outCount < length) {
2215         DisplayFatalError(_("Error writing to display"), error, 1);
2216     }
2217 }
2218
2219 void
2220 PackHolding(packed, holding)
2221      char packed[];
2222      char *holding;
2223 {
2224     char *p = holding;
2225     char *q = packed;
2226     int runlength = 0;
2227     int curr = 9999;
2228     do {
2229         if (*p == curr) {
2230             runlength++;
2231         } else {
2232             switch (runlength) {
2233               case 0:
2234                 break;
2235               case 1:
2236                 *q++ = curr;
2237                 break;
2238               case 2:
2239                 *q++ = curr;
2240                 *q++ = curr;
2241                 break;
2242               default:
2243                 sprintf(q, "%d", runlength);
2244                 while (*q) q++;
2245                 *q++ = curr;
2246                 break;
2247             }
2248             runlength = 1;
2249             curr = *p;
2250         }
2251     } while (*p++);
2252     *q = NULLCHAR;
2253 }
2254
2255 /* Telnet protocol requests from the front end */
2256 void
2257 TelnetRequest(ddww, option)
2258      unsigned char ddww, option;
2259 {
2260     unsigned char msg[3];
2261     int outCount, outError;
2262
2263     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2264
2265     if (appData.debugMode) {
2266         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2267         switch (ddww) {
2268           case TN_DO:
2269             ddwwStr = "DO";
2270             break;
2271           case TN_DONT:
2272             ddwwStr = "DONT";
2273             break;
2274           case TN_WILL:
2275             ddwwStr = "WILL";
2276             break;
2277           case TN_WONT:
2278             ddwwStr = "WONT";
2279             break;
2280           default:
2281             ddwwStr = buf1;
2282             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2283             break;
2284         }
2285         switch (option) {
2286           case TN_ECHO:
2287             optionStr = "ECHO";
2288             break;
2289           default:
2290             optionStr = buf2;
2291             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2292             break;
2293         }
2294         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2295     }
2296     msg[0] = TN_IAC;
2297     msg[1] = ddww;
2298     msg[2] = option;
2299     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2300     if (outCount < 3) {
2301         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2302     }
2303 }
2304
2305 void
2306 DoEcho()
2307 {
2308     if (!appData.icsActive) return;
2309     TelnetRequest(TN_DO, TN_ECHO);
2310 }
2311
2312 void
2313 DontEcho()
2314 {
2315     if (!appData.icsActive) return;
2316     TelnetRequest(TN_DONT, TN_ECHO);
2317 }
2318
2319 void
2320 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2321 {
2322     /* put the holdings sent to us by the server on the board holdings area */
2323     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2324     char p;
2325     ChessSquare piece;
2326
2327     if(gameInfo.holdingsWidth < 2)  return;
2328     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2329         return; // prevent overwriting by pre-board holdings
2330
2331     if( (int)lowestPiece >= BlackPawn ) {
2332         holdingsColumn = 0;
2333         countsColumn = 1;
2334         holdingsStartRow = BOARD_HEIGHT-1;
2335         direction = -1;
2336     } else {
2337         holdingsColumn = BOARD_WIDTH-1;
2338         countsColumn = BOARD_WIDTH-2;
2339         holdingsStartRow = 0;
2340         direction = 1;
2341     }
2342
2343     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2344         board[i][holdingsColumn] = EmptySquare;
2345         board[i][countsColumn]   = (ChessSquare) 0;
2346     }
2347     while( (p=*holdings++) != NULLCHAR ) {
2348         piece = CharToPiece( ToUpper(p) );
2349         if(piece == EmptySquare) continue;
2350         /*j = (int) piece - (int) WhitePawn;*/
2351         j = PieceToNumber(piece);
2352         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2353         if(j < 0) continue;               /* should not happen */
2354         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2355         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2356         board[holdingsStartRow+j*direction][countsColumn]++;
2357     }
2358 }
2359
2360
2361 void
2362 VariantSwitch(Board board, VariantClass newVariant)
2363 {
2364    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2365    static Board oldBoard;
2366
2367    startedFromPositionFile = FALSE;
2368    if(gameInfo.variant == newVariant) return;
2369
2370    /* [HGM] This routine is called each time an assignment is made to
2371     * gameInfo.variant during a game, to make sure the board sizes
2372     * are set to match the new variant. If that means adding or deleting
2373     * holdings, we shift the playing board accordingly
2374     * This kludge is needed because in ICS observe mode, we get boards
2375     * of an ongoing game without knowing the variant, and learn about the
2376     * latter only later. This can be because of the move list we requested,
2377     * in which case the game history is refilled from the beginning anyway,
2378     * but also when receiving holdings of a crazyhouse game. In the latter
2379     * case we want to add those holdings to the already received position.
2380     */
2381
2382
2383    if (appData.debugMode) {
2384      fprintf(debugFP, "Switch board from %s to %s\n",
2385              VariantName(gameInfo.variant), VariantName(newVariant));
2386      setbuf(debugFP, NULL);
2387    }
2388    shuffleOpenings = 0;       /* [HGM] shuffle */
2389    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2390    switch(newVariant)
2391      {
2392      case VariantShogi:
2393        newWidth = 9;  newHeight = 9;
2394        gameInfo.holdingsSize = 7;
2395      case VariantBughouse:
2396      case VariantCrazyhouse:
2397        newHoldingsWidth = 2; break;
2398      case VariantGreat:
2399        newWidth = 10;
2400      case VariantSuper:
2401        newHoldingsWidth = 2;
2402        gameInfo.holdingsSize = 8;
2403        break;
2404      case VariantGothic:
2405      case VariantCapablanca:
2406      case VariantCapaRandom:
2407        newWidth = 10;
2408      default:
2409        newHoldingsWidth = gameInfo.holdingsSize = 0;
2410      };
2411
2412    if(newWidth  != gameInfo.boardWidth  ||
2413       newHeight != gameInfo.boardHeight ||
2414       newHoldingsWidth != gameInfo.holdingsWidth ) {
2415
2416      /* shift position to new playing area, if needed */
2417      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2418        for(i=0; i<BOARD_HEIGHT; i++)
2419          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2420            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2421              board[i][j];
2422        for(i=0; i<newHeight; i++) {
2423          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2424          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2425        }
2426      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2427        for(i=0; i<BOARD_HEIGHT; i++)
2428          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2429            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2430              board[i][j];
2431      }
2432      gameInfo.boardWidth  = newWidth;
2433      gameInfo.boardHeight = newHeight;
2434      gameInfo.holdingsWidth = newHoldingsWidth;
2435      gameInfo.variant = newVariant;
2436      InitDrawingSizes(-2, 0);
2437    } else gameInfo.variant = newVariant;
2438    CopyBoard(oldBoard, board);   // remember correctly formatted board
2439      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2440    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2441 }
2442
2443 static int loggedOn = FALSE;
2444
2445 /*-- Game start info cache: --*/
2446 int gs_gamenum;
2447 char gs_kind[MSG_SIZ];
2448 static char player1Name[128] = "";
2449 static char player2Name[128] = "";
2450 static char cont_seq[] = "\n\\   ";
2451 static int player1Rating = -1;
2452 static int player2Rating = -1;
2453 /*----------------------------*/
2454
2455 ColorClass curColor = ColorNormal;
2456 int suppressKibitz = 0;
2457
2458 // [HGM] seekgraph
2459 Boolean soughtPending = FALSE;
2460 Boolean seekGraphUp;
2461 #define MAX_SEEK_ADS 200
2462 #define SQUARE 0x80
2463 char *seekAdList[MAX_SEEK_ADS];
2464 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2465 float tcList[MAX_SEEK_ADS];
2466 char colorList[MAX_SEEK_ADS];
2467 int nrOfSeekAds = 0;
2468 int minRating = 1010, maxRating = 2800;
2469 int hMargin = 10, vMargin = 20, h, w;
2470 extern int squareSize, lineGap;
2471
2472 void
2473 PlotSeekAd(int i)
2474 {
2475         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2476         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2477         if(r < minRating+100 && r >=0 ) r = minRating+100;
2478         if(r > maxRating) r = maxRating;
2479         if(tc < 1.) tc = 1.;
2480         if(tc > 95.) tc = 95.;
2481         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2482         y = ((double)r - minRating)/(maxRating - minRating)
2483             * (h-vMargin-squareSize/8-1) + vMargin;
2484         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2485         if(strstr(seekAdList[i], " u ")) color = 1;
2486         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2487            !strstr(seekAdList[i], "bullet") &&
2488            !strstr(seekAdList[i], "blitz") &&
2489            !strstr(seekAdList[i], "standard") ) color = 2;
2490         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2491         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2492 }
2493
2494 void
2495 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2496 {
2497         char buf[MSG_SIZ], *ext = "";
2498         VariantClass v = StringToVariant(type);
2499         if(strstr(type, "wild")) {
2500             ext = type + 4; // append wild number
2501             if(v == VariantFischeRandom) type = "chess960"; else
2502             if(v == VariantLoadable) type = "setup"; else
2503             type = VariantName(v);
2504         }
2505         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2506         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2507             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2508             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2509             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2510             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2511             seekNrList[nrOfSeekAds] = nr;
2512             zList[nrOfSeekAds] = 0;
2513             seekAdList[nrOfSeekAds++] = StrSave(buf);
2514             if(plot) PlotSeekAd(nrOfSeekAds-1);
2515         }
2516 }
2517
2518 void
2519 EraseSeekDot(int i)
2520 {
2521     int x = xList[i], y = yList[i], d=squareSize/4, k;
2522     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2523     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2524     // now replot every dot that overlapped
2525     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2526         int xx = xList[k], yy = yList[k];
2527         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2528             DrawSeekDot(xx, yy, colorList[k]);
2529     }
2530 }
2531
2532 void
2533 RemoveSeekAd(int nr)
2534 {
2535         int i;
2536         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2537             EraseSeekDot(i);
2538             if(seekAdList[i]) free(seekAdList[i]);
2539             seekAdList[i] = seekAdList[--nrOfSeekAds];
2540             seekNrList[i] = seekNrList[nrOfSeekAds];
2541             ratingList[i] = ratingList[nrOfSeekAds];
2542             colorList[i]  = colorList[nrOfSeekAds];
2543             tcList[i] = tcList[nrOfSeekAds];
2544             xList[i]  = xList[nrOfSeekAds];
2545             yList[i]  = yList[nrOfSeekAds];
2546             zList[i]  = zList[nrOfSeekAds];
2547             seekAdList[nrOfSeekAds] = NULL;
2548             break;
2549         }
2550 }
2551
2552 Boolean
2553 MatchSoughtLine(char *line)
2554 {
2555     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2556     int nr, base, inc, u=0; char dummy;
2557
2558     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2559        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2560        (u=1) &&
2561        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2562         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2563         // match: compact and save the line
2564         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2565         return TRUE;
2566     }
2567     return FALSE;
2568 }
2569
2570 int
2571 DrawSeekGraph()
2572 {
2573     int i;
2574     if(!seekGraphUp) return FALSE;
2575     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2576     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2577
2578     DrawSeekBackground(0, 0, w, h);
2579     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2580     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2581     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2582         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2583         yy = h-1-yy;
2584         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2585         if(i%500 == 0) {
2586             char buf[MSG_SIZ];
2587             snprintf(buf, MSG_SIZ, "%d", i);
2588             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2589         }
2590     }
2591     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2592     for(i=1; i<100; i+=(i<10?1:5)) {
2593         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2594         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2595         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2596             char buf[MSG_SIZ];
2597             snprintf(buf, MSG_SIZ, "%d", i);
2598             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2599         }
2600     }
2601     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2602     return TRUE;
2603 }
2604
2605 int SeekGraphClick(ClickType click, int x, int y, int moving)
2606 {
2607     static int lastDown = 0, displayed = 0, lastSecond;
2608     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2609         if(click == Release || moving) return FALSE;
2610         nrOfSeekAds = 0;
2611         soughtPending = TRUE;
2612         SendToICS(ics_prefix);
2613         SendToICS("sought\n"); // should this be "sought all"?
2614     } else { // issue challenge based on clicked ad
2615         int dist = 10000; int i, closest = 0, second = 0;
2616         for(i=0; i<nrOfSeekAds; i++) {
2617             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2618             if(d < dist) { dist = d; closest = i; }
2619             second += (d - zList[i] < 120); // count in-range ads
2620             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2621         }
2622         if(dist < 120) {
2623             char buf[MSG_SIZ];
2624             second = (second > 1);
2625             if(displayed != closest || second != lastSecond) {
2626                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2627                 lastSecond = second; displayed = closest;
2628             }
2629             if(click == Press) {
2630                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2631                 lastDown = closest;
2632                 return TRUE;
2633             } // on press 'hit', only show info
2634             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2635             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2636             SendToICS(ics_prefix);
2637             SendToICS(buf);
2638             return TRUE; // let incoming board of started game pop down the graph
2639         } else if(click == Release) { // release 'miss' is ignored
2640             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2641             if(moving == 2) { // right up-click
2642                 nrOfSeekAds = 0; // refresh graph
2643                 soughtPending = TRUE;
2644                 SendToICS(ics_prefix);
2645                 SendToICS("sought\n"); // should this be "sought all"?
2646             }
2647             return TRUE;
2648         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2649         // press miss or release hit 'pop down' seek graph
2650         seekGraphUp = FALSE;
2651         DrawPosition(TRUE, NULL);
2652     }
2653     return TRUE;
2654 }
2655
2656 void
2657 read_from_ics(isr, closure, data, count, error)
2658      InputSourceRef isr;
2659      VOIDSTAR closure;
2660      char *data;
2661      int count;
2662      int error;
2663 {
2664 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2665 #define STARTED_NONE 0
2666 #define STARTED_MOVES 1
2667 #define STARTED_BOARD 2
2668 #define STARTED_OBSERVE 3
2669 #define STARTED_HOLDINGS 4
2670 #define STARTED_CHATTER 5
2671 #define STARTED_COMMENT 6
2672 #define STARTED_MOVES_NOHIDE 7
2673
2674     static int started = STARTED_NONE;
2675     static char parse[20000];
2676     static int parse_pos = 0;
2677     static char buf[BUF_SIZE + 1];
2678     static int firstTime = TRUE, intfSet = FALSE;
2679     static ColorClass prevColor = ColorNormal;
2680     static int savingComment = FALSE;
2681     static int cmatch = 0; // continuation sequence match
2682     char *bp;
2683     char str[MSG_SIZ];
2684     int i, oldi;
2685     int buf_len;
2686     int next_out;
2687     int tkind;
2688     int backup;    /* [DM] For zippy color lines */
2689     char *p;
2690     char talker[MSG_SIZ]; // [HGM] chat
2691     int channel;
2692
2693     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2694
2695     if (appData.debugMode) {
2696       if (!error) {
2697         fprintf(debugFP, "<ICS: ");
2698         show_bytes(debugFP, data, count);
2699         fprintf(debugFP, "\n");
2700       }
2701     }
2702
2703     if (appData.debugMode) { int f = forwardMostMove;
2704         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2705                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2706                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2707     }
2708     if (count > 0) {
2709         /* If last read ended with a partial line that we couldn't parse,
2710            prepend it to the new read and try again. */
2711         if (leftover_len > 0) {
2712             for (i=0; i<leftover_len; i++)
2713               buf[i] = buf[leftover_start + i];
2714         }
2715
2716     /* copy new characters into the buffer */
2717     bp = buf + leftover_len;
2718     buf_len=leftover_len;
2719     for (i=0; i<count; i++)
2720     {
2721         // ignore these
2722         if (data[i] == '\r')
2723             continue;
2724
2725         // join lines split by ICS?
2726         if (!appData.noJoin)
2727         {
2728             /*
2729                 Joining just consists of finding matches against the
2730                 continuation sequence, and discarding that sequence
2731                 if found instead of copying it.  So, until a match
2732                 fails, there's nothing to do since it might be the
2733                 complete sequence, and thus, something we don't want
2734                 copied.
2735             */
2736             if (data[i] == cont_seq[cmatch])
2737             {
2738                 cmatch++;
2739                 if (cmatch == strlen(cont_seq))
2740                 {
2741                     cmatch = 0; // complete match.  just reset the counter
2742
2743                     /*
2744                         it's possible for the ICS to not include the space
2745                         at the end of the last word, making our [correct]
2746                         join operation fuse two separate words.  the server
2747                         does this when the space occurs at the width setting.
2748                     */
2749                     if (!buf_len || buf[buf_len-1] != ' ')
2750                     {
2751                         *bp++ = ' ';
2752                         buf_len++;
2753                     }
2754                 }
2755                 continue;
2756             }
2757             else if (cmatch)
2758             {
2759                 /*
2760                     match failed, so we have to copy what matched before
2761                     falling through and copying this character.  In reality,
2762                     this will only ever be just the newline character, but
2763                     it doesn't hurt to be precise.
2764                 */
2765                 strncpy(bp, cont_seq, cmatch);
2766                 bp += cmatch;
2767                 buf_len += cmatch;
2768                 cmatch = 0;
2769             }
2770         }
2771
2772         // copy this char
2773         *bp++ = data[i];
2774         buf_len++;
2775     }
2776
2777         buf[buf_len] = NULLCHAR;
2778 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2779         next_out = 0;
2780         leftover_start = 0;
2781
2782         i = 0;
2783         while (i < buf_len) {
2784             /* Deal with part of the TELNET option negotiation
2785                protocol.  We refuse to do anything beyond the
2786                defaults, except that we allow the WILL ECHO option,
2787                which ICS uses to turn off password echoing when we are
2788                directly connected to it.  We reject this option
2789                if localLineEditing mode is on (always on in xboard)
2790                and we are talking to port 23, which might be a real
2791                telnet server that will try to keep WILL ECHO on permanently.
2792              */
2793             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2794                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2795                 unsigned char option;
2796                 oldi = i;
2797                 switch ((unsigned char) buf[++i]) {
2798                   case TN_WILL:
2799                     if (appData.debugMode)
2800                       fprintf(debugFP, "\n<WILL ");
2801                     switch (option = (unsigned char) buf[++i]) {
2802                       case TN_ECHO:
2803                         if (appData.debugMode)
2804                           fprintf(debugFP, "ECHO ");
2805                         /* Reply only if this is a change, according
2806                            to the protocol rules. */
2807                         if (remoteEchoOption) break;
2808                         if (appData.localLineEditing &&
2809                             atoi(appData.icsPort) == TN_PORT) {
2810                             TelnetRequest(TN_DONT, TN_ECHO);
2811                         } else {
2812                             EchoOff();
2813                             TelnetRequest(TN_DO, TN_ECHO);
2814                             remoteEchoOption = TRUE;
2815                         }
2816                         break;
2817                       default:
2818                         if (appData.debugMode)
2819                           fprintf(debugFP, "%d ", option);
2820                         /* Whatever this is, we don't want it. */
2821                         TelnetRequest(TN_DONT, option);
2822                         break;
2823                     }
2824                     break;
2825                   case TN_WONT:
2826                     if (appData.debugMode)
2827                       fprintf(debugFP, "\n<WONT ");
2828                     switch (option = (unsigned char) buf[++i]) {
2829                       case TN_ECHO:
2830                         if (appData.debugMode)
2831                           fprintf(debugFP, "ECHO ");
2832                         /* Reply only if this is a change, according
2833                            to the protocol rules. */
2834                         if (!remoteEchoOption) break;
2835                         EchoOn();
2836                         TelnetRequest(TN_DONT, TN_ECHO);
2837                         remoteEchoOption = FALSE;
2838                         break;
2839                       default:
2840                         if (appData.debugMode)
2841                           fprintf(debugFP, "%d ", (unsigned char) option);
2842                         /* Whatever this is, it must already be turned
2843                            off, because we never agree to turn on
2844                            anything non-default, so according to the
2845                            protocol rules, we don't reply. */
2846                         break;
2847                     }
2848                     break;
2849                   case TN_DO:
2850                     if (appData.debugMode)
2851                       fprintf(debugFP, "\n<DO ");
2852                     switch (option = (unsigned char) buf[++i]) {
2853                       default:
2854                         /* Whatever this is, we refuse to do it. */
2855                         if (appData.debugMode)
2856                           fprintf(debugFP, "%d ", option);
2857                         TelnetRequest(TN_WONT, option);
2858                         break;
2859                     }
2860                     break;
2861                   case TN_DONT:
2862                     if (appData.debugMode)
2863                       fprintf(debugFP, "\n<DONT ");
2864                     switch (option = (unsigned char) buf[++i]) {
2865                       default:
2866                         if (appData.debugMode)
2867                           fprintf(debugFP, "%d ", option);
2868                         /* Whatever this is, we are already not doing
2869                            it, because we never agree to do anything
2870                            non-default, so according to the protocol
2871                            rules, we don't reply. */
2872                         break;
2873                     }
2874                     break;
2875                   case TN_IAC:
2876                     if (appData.debugMode)
2877                       fprintf(debugFP, "\n<IAC ");
2878                     /* Doubled IAC; pass it through */
2879                     i--;
2880                     break;
2881                   default:
2882                     if (appData.debugMode)
2883                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2884                     /* Drop all other telnet commands on the floor */
2885                     break;
2886                 }
2887                 if (oldi > next_out)
2888                   SendToPlayer(&buf[next_out], oldi - next_out);
2889                 if (++i > next_out)
2890                   next_out = i;
2891                 continue;
2892             }
2893
2894             /* OK, this at least will *usually* work */
2895             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2896                 loggedOn = TRUE;
2897             }
2898
2899             if (loggedOn && !intfSet) {
2900                 if (ics_type == ICS_ICC) {
2901                   snprintf(str, MSG_SIZ,
2902                           "/set-quietly interface %s\n/set-quietly style 12\n",
2903                           programVersion);
2904                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2905                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2906                 } else if (ics_type == ICS_CHESSNET) {
2907                   snprintf(str, MSG_SIZ, "/style 12\n");
2908                 } else {
2909                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2910                   strcat(str, programVersion);
2911                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2912                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2913                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2914 #ifdef WIN32
2915                   strcat(str, "$iset nohighlight 1\n");
2916 #endif
2917                   strcat(str, "$iset lock 1\n$style 12\n");
2918                 }
2919                 SendToICS(str);
2920                 NotifyFrontendLogin();
2921                 intfSet = TRUE;
2922             }
2923
2924             if (started == STARTED_COMMENT) {
2925                 /* Accumulate characters in comment */
2926                 parse[parse_pos++] = buf[i];
2927                 if (buf[i] == '\n') {
2928                     parse[parse_pos] = NULLCHAR;
2929                     if(chattingPartner>=0) {
2930                         char mess[MSG_SIZ];
2931                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2932                         OutputChatMessage(chattingPartner, mess);
2933                         chattingPartner = -1;
2934                         next_out = i+1; // [HGM] suppress printing in ICS window
2935                     } else
2936                     if(!suppressKibitz) // [HGM] kibitz
2937                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2938                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2939                         int nrDigit = 0, nrAlph = 0, j;
2940                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2941                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2942                         parse[parse_pos] = NULLCHAR;
2943                         // try to be smart: if it does not look like search info, it should go to
2944                         // ICS interaction window after all, not to engine-output window.
2945                         for(j=0; j<parse_pos; j++) { // count letters and digits
2946                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2947                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2948                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2949                         }
2950                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2951                             int depth=0; float score;
2952                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2953                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2954                                 pvInfoList[forwardMostMove-1].depth = depth;
2955                                 pvInfoList[forwardMostMove-1].score = 100*score;
2956                             }
2957                             OutputKibitz(suppressKibitz, parse);
2958                         } else {
2959                             char tmp[MSG_SIZ];
2960                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2961                             SendToPlayer(tmp, strlen(tmp));
2962                         }
2963                         next_out = i+1; // [HGM] suppress printing in ICS window
2964                     }
2965                     started = STARTED_NONE;
2966                 } else {
2967                     /* Don't match patterns against characters in comment */
2968                     i++;
2969                     continue;
2970                 }
2971             }
2972             if (started == STARTED_CHATTER) {
2973                 if (buf[i] != '\n') {
2974                     /* Don't match patterns against characters in chatter */
2975                     i++;
2976                     continue;
2977                 }
2978                 started = STARTED_NONE;
2979                 if(suppressKibitz) next_out = i+1;
2980             }
2981
2982             /* Kludge to deal with rcmd protocol */
2983             if (firstTime && looking_at(buf, &i, "\001*")) {
2984                 DisplayFatalError(&buf[1], 0, 1);
2985                 continue;
2986             } else {
2987                 firstTime = FALSE;
2988             }
2989
2990             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2991                 ics_type = ICS_ICC;
2992                 ics_prefix = "/";
2993                 if (appData.debugMode)
2994                   fprintf(debugFP, "ics_type %d\n", ics_type);
2995                 continue;
2996             }
2997             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2998                 ics_type = ICS_FICS;
2999                 ics_prefix = "$";
3000                 if (appData.debugMode)
3001                   fprintf(debugFP, "ics_type %d\n", ics_type);
3002                 continue;
3003             }
3004             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3005                 ics_type = ICS_CHESSNET;
3006                 ics_prefix = "/";
3007                 if (appData.debugMode)
3008                   fprintf(debugFP, "ics_type %d\n", ics_type);
3009                 continue;
3010             }
3011
3012             if (!loggedOn &&
3013                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3014                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3015                  looking_at(buf, &i, "will be \"*\""))) {
3016               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3017               continue;
3018             }
3019
3020             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3021               char buf[MSG_SIZ];
3022               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3023               DisplayIcsInteractionTitle(buf);
3024               have_set_title = TRUE;
3025             }
3026
3027             /* skip finger notes */
3028             if (started == STARTED_NONE &&
3029                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3030                  (buf[i] == '1' && buf[i+1] == '0')) &&
3031                 buf[i+2] == ':' && buf[i+3] == ' ') {
3032               started = STARTED_CHATTER;
3033               i += 3;
3034               continue;
3035             }
3036
3037             oldi = i;
3038             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3039             if(appData.seekGraph) {
3040                 if(soughtPending && MatchSoughtLine(buf+i)) {
3041                     i = strstr(buf+i, "rated") - buf;
3042                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3043                     next_out = leftover_start = i;
3044                     started = STARTED_CHATTER;
3045                     suppressKibitz = TRUE;
3046                     continue;
3047                 }
3048                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3049                         && looking_at(buf, &i, "* ads displayed")) {
3050                     soughtPending = FALSE;
3051                     seekGraphUp = TRUE;
3052                     DrawSeekGraph();
3053                     continue;
3054                 }
3055                 if(appData.autoRefresh) {
3056                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3057                         int s = (ics_type == ICS_ICC); // ICC format differs
3058                         if(seekGraphUp)
3059                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3060                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3061                         looking_at(buf, &i, "*% "); // eat prompt
3062                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3063                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3064                         next_out = i; // suppress
3065                         continue;
3066                     }
3067                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3068                         char *p = star_match[0];
3069                         while(*p) {
3070                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3071                             while(*p && *p++ != ' '); // next
3072                         }
3073                         looking_at(buf, &i, "*% "); // eat prompt
3074                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3075                         next_out = i;
3076                         continue;
3077                     }
3078                 }
3079             }
3080
3081             /* skip formula vars */
3082             if (started == STARTED_NONE &&
3083                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3084               started = STARTED_CHATTER;
3085               i += 3;
3086               continue;
3087             }
3088
3089             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3090             if (appData.autoKibitz && started == STARTED_NONE &&
3091                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3092                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3093                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3094                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3095                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3096                         suppressKibitz = TRUE;
3097                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3098                         next_out = i;
3099                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3100                                 && (gameMode == IcsPlayingWhite)) ||
3101                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3102                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3103                             started = STARTED_CHATTER; // own kibitz we simply discard
3104                         else {
3105                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3106                             parse_pos = 0; parse[0] = NULLCHAR;
3107                             savingComment = TRUE;
3108                             suppressKibitz = gameMode != IcsObserving ? 2 :
3109                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3110                         }
3111                         continue;
3112                 } else
3113                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3114                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3115                          && atoi(star_match[0])) {
3116                     // suppress the acknowledgements of our own autoKibitz
3117                     char *p;
3118                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3119                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3120                     SendToPlayer(star_match[0], strlen(star_match[0]));
3121                     if(looking_at(buf, &i, "*% ")) // eat prompt
3122                         suppressKibitz = FALSE;
3123                     next_out = i;
3124                     continue;
3125                 }
3126             } // [HGM] kibitz: end of patch
3127
3128             // [HGM] chat: intercept tells by users for which we have an open chat window
3129             channel = -1;
3130             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3131                                            looking_at(buf, &i, "* whispers:") ||
3132                                            looking_at(buf, &i, "* kibitzes:") ||
3133                                            looking_at(buf, &i, "* shouts:") ||
3134                                            looking_at(buf, &i, "* c-shouts:") ||
3135                                            looking_at(buf, &i, "--> * ") ||
3136                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3137                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3138                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3139                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3140                 int p;
3141                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3142                 chattingPartner = -1;
3143
3144                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3145                 for(p=0; p<MAX_CHAT; p++) {
3146                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3147                     talker[0] = '['; strcat(talker, "] ");
3148                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3149                     chattingPartner = p; break;
3150                     }
3151                 } else
3152                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3153                 for(p=0; p<MAX_CHAT; p++) {
3154                     if(!strcmp("kibitzes", chatPartner[p])) {
3155                         talker[0] = '['; strcat(talker, "] ");
3156                         chattingPartner = p; break;
3157                     }
3158                 } else
3159                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3160                 for(p=0; p<MAX_CHAT; p++) {
3161                     if(!strcmp("whispers", chatPartner[p])) {
3162                         talker[0] = '['; strcat(talker, "] ");
3163                         chattingPartner = p; break;
3164                     }
3165                 } else
3166                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3167                   if(buf[i-8] == '-' && buf[i-3] == 't')
3168                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3169                     if(!strcmp("c-shouts", chatPartner[p])) {
3170                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3171                         chattingPartner = p; break;
3172                     }
3173                   }
3174                   if(chattingPartner < 0)
3175                   for(p=0; p<MAX_CHAT; p++) {
3176                     if(!strcmp("shouts", chatPartner[p])) {
3177                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3178                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3179                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3180                         chattingPartner = p; break;
3181                     }
3182                   }
3183                 }
3184                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3185                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3186                     talker[0] = 0; Colorize(ColorTell, FALSE);
3187                     chattingPartner = p; break;
3188                 }
3189                 if(chattingPartner<0) i = oldi; else {
3190                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3191                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3192                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3193                     started = STARTED_COMMENT;
3194                     parse_pos = 0; parse[0] = NULLCHAR;
3195                     savingComment = 3 + chattingPartner; // counts as TRUE
3196                     suppressKibitz = TRUE;
3197                     continue;
3198                 }
3199             } // [HGM] chat: end of patch
3200
3201           backup = i;
3202             if (appData.zippyTalk || appData.zippyPlay) {
3203                 /* [DM] Backup address for color zippy lines */
3204 #if ZIPPY
3205                if (loggedOn == TRUE)
3206                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3207                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3208 #endif
3209             } // [DM] 'else { ' deleted
3210                 if (
3211                     /* Regular tells and says */
3212                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3213                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3214                     looking_at(buf, &i, "* says: ") ||
3215                     /* Don't color "message" or "messages" output */
3216                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3217                     looking_at(buf, &i, "*. * at *:*: ") ||
3218                     looking_at(buf, &i, "--* (*:*): ") ||
3219                     /* Message notifications (same color as tells) */
3220                     looking_at(buf, &i, "* has left a message ") ||
3221                     looking_at(buf, &i, "* just sent you a message:\n") ||
3222                     /* Whispers and kibitzes */
3223                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3224                     looking_at(buf, &i, "* kibitzes: ") ||
3225                     /* Channel tells */
3226                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3227
3228                   if (tkind == 1 && strchr(star_match[0], ':')) {
3229                       /* Avoid "tells you:" spoofs in channels */
3230                      tkind = 3;
3231                   }
3232                   if (star_match[0][0] == NULLCHAR ||
3233                       strchr(star_match[0], ' ') ||
3234                       (tkind == 3 && strchr(star_match[1], ' '))) {
3235                     /* Reject bogus matches */
3236                     i = oldi;
3237                   } else {
3238                     if (appData.colorize) {
3239                       if (oldi > next_out) {
3240                         SendToPlayer(&buf[next_out], oldi - next_out);
3241                         next_out = oldi;
3242                       }
3243                       switch (tkind) {
3244                       case 1:
3245                         Colorize(ColorTell, FALSE);
3246                         curColor = ColorTell;
3247                         break;
3248                       case 2:
3249                         Colorize(ColorKibitz, FALSE);
3250                         curColor = ColorKibitz;
3251                         break;
3252                       case 3:
3253                         p = strrchr(star_match[1], '(');
3254                         if (p == NULL) {
3255                           p = star_match[1];
3256                         } else {
3257                           p++;
3258                         }
3259                         if (atoi(p) == 1) {
3260                           Colorize(ColorChannel1, FALSE);
3261                           curColor = ColorChannel1;
3262                         } else {
3263                           Colorize(ColorChannel, FALSE);
3264                           curColor = ColorChannel;
3265                         }
3266                         break;
3267                       case 5:
3268                         curColor = ColorNormal;
3269                         break;
3270                       }
3271                     }
3272                     if (started == STARTED_NONE && appData.autoComment &&
3273                         (gameMode == IcsObserving ||
3274                          gameMode == IcsPlayingWhite ||
3275                          gameMode == IcsPlayingBlack)) {
3276                       parse_pos = i - oldi;
3277                       memcpy(parse, &buf[oldi], parse_pos);
3278                       parse[parse_pos] = NULLCHAR;
3279                       started = STARTED_COMMENT;
3280                       savingComment = TRUE;
3281                     } else {
3282                       started = STARTED_CHATTER;
3283                       savingComment = FALSE;
3284                     }
3285                     loggedOn = TRUE;
3286                     continue;
3287                   }
3288                 }
3289
3290                 if (looking_at(buf, &i, "* s-shouts: ") ||
3291                     looking_at(buf, &i, "* c-shouts: ")) {
3292                     if (appData.colorize) {
3293                         if (oldi > next_out) {
3294                             SendToPlayer(&buf[next_out], oldi - next_out);
3295                             next_out = oldi;
3296                         }
3297                         Colorize(ColorSShout, FALSE);
3298                         curColor = ColorSShout;
3299                     }
3300                     loggedOn = TRUE;
3301                     started = STARTED_CHATTER;
3302                     continue;
3303                 }
3304
3305                 if (looking_at(buf, &i, "--->")) {
3306                     loggedOn = TRUE;
3307                     continue;
3308                 }
3309
3310                 if (looking_at(buf, &i, "* shouts: ") ||
3311                     looking_at(buf, &i, "--> ")) {
3312                     if (appData.colorize) {
3313                         if (oldi > next_out) {
3314                             SendToPlayer(&buf[next_out], oldi - next_out);
3315                             next_out = oldi;
3316                         }
3317                         Colorize(ColorShout, FALSE);
3318                         curColor = ColorShout;
3319                     }
3320                     loggedOn = TRUE;
3321                     started = STARTED_CHATTER;
3322                     continue;
3323                 }
3324
3325                 if (looking_at( buf, &i, "Challenge:")) {
3326                     if (appData.colorize) {
3327                         if (oldi > next_out) {
3328                             SendToPlayer(&buf[next_out], oldi - next_out);
3329                             next_out = oldi;
3330                         }
3331                         Colorize(ColorChallenge, FALSE);
3332                         curColor = ColorChallenge;
3333                     }
3334                     loggedOn = TRUE;
3335                     continue;
3336                 }
3337
3338                 if (looking_at(buf, &i, "* offers you") ||
3339                     looking_at(buf, &i, "* offers to be") ||
3340                     looking_at(buf, &i, "* would like to") ||
3341                     looking_at(buf, &i, "* requests to") ||
3342                     looking_at(buf, &i, "Your opponent offers") ||
3343                     looking_at(buf, &i, "Your opponent requests")) {
3344
3345                     if (appData.colorize) {
3346                         if (oldi > next_out) {
3347                             SendToPlayer(&buf[next_out], oldi - next_out);
3348                             next_out = oldi;
3349                         }
3350                         Colorize(ColorRequest, FALSE);
3351                         curColor = ColorRequest;
3352                     }
3353                     continue;
3354                 }
3355
3356                 if (looking_at(buf, &i, "* (*) seeking")) {
3357                     if (appData.colorize) {
3358                         if (oldi > next_out) {
3359                             SendToPlayer(&buf[next_out], oldi - next_out);
3360                             next_out = oldi;
3361                         }
3362                         Colorize(ColorSeek, FALSE);
3363                         curColor = ColorSeek;
3364                     }
3365                     continue;
3366             }
3367
3368           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3369
3370             if (looking_at(buf, &i, "\\   ")) {
3371                 if (prevColor != ColorNormal) {
3372                     if (oldi > next_out) {
3373                         SendToPlayer(&buf[next_out], oldi - next_out);
3374                         next_out = oldi;
3375                     }
3376                     Colorize(prevColor, TRUE);
3377                     curColor = prevColor;
3378                 }
3379                 if (savingComment) {
3380                     parse_pos = i - oldi;
3381                     memcpy(parse, &buf[oldi], parse_pos);
3382                     parse[parse_pos] = NULLCHAR;
3383                     started = STARTED_COMMENT;
3384                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3385                         chattingPartner = savingComment - 3; // kludge to remember the box
3386                 } else {
3387                     started = STARTED_CHATTER;
3388                 }
3389                 continue;
3390             }
3391
3392             if (looking_at(buf, &i, "Black Strength :") ||
3393                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3394                 looking_at(buf, &i, "<10>") ||
3395                 looking_at(buf, &i, "#@#")) {
3396                 /* Wrong board style */
3397                 loggedOn = TRUE;
3398                 SendToICS(ics_prefix);
3399                 SendToICS("set style 12\n");
3400                 SendToICS(ics_prefix);
3401                 SendToICS("refresh\n");
3402                 continue;
3403             }
3404
3405             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3406                 ICSInitScript();
3407                 have_sent_ICS_logon = 1;
3408                 continue;
3409             }
3410
3411             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3412                 (looking_at(buf, &i, "\n<12> ") ||
3413                  looking_at(buf, &i, "<12> "))) {
3414                 loggedOn = TRUE;
3415                 if (oldi > next_out) {
3416                     SendToPlayer(&buf[next_out], oldi - next_out);
3417                 }
3418                 next_out = i;
3419                 started = STARTED_BOARD;
3420                 parse_pos = 0;
3421                 continue;
3422             }
3423
3424             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3425                 looking_at(buf, &i, "<b1> ")) {
3426                 if (oldi > next_out) {
3427                     SendToPlayer(&buf[next_out], oldi - next_out);
3428                 }
3429                 next_out = i;
3430                 started = STARTED_HOLDINGS;
3431                 parse_pos = 0;
3432                 continue;
3433             }
3434
3435             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3436                 loggedOn = TRUE;
3437                 /* Header for a move list -- first line */
3438
3439                 switch (ics_getting_history) {
3440                   case H_FALSE:
3441                     switch (gameMode) {
3442                       case IcsIdle:
3443                       case BeginningOfGame:
3444                         /* User typed "moves" or "oldmoves" while we
3445                            were idle.  Pretend we asked for these
3446                            moves and soak them up so user can step
3447                            through them and/or save them.
3448                            */
3449                         Reset(FALSE, TRUE);
3450                         gameMode = IcsObserving;
3451                         ModeHighlight();
3452                         ics_gamenum = -1;
3453                         ics_getting_history = H_GOT_UNREQ_HEADER;
3454                         break;
3455                       case EditGame: /*?*/
3456                       case EditPosition: /*?*/
3457                         /* Should above feature work in these modes too? */
3458                         /* For now it doesn't */
3459                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3460                         break;
3461                       default:
3462                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3463                         break;
3464                     }
3465                     break;
3466                   case H_REQUESTED:
3467                     /* Is this the right one? */
3468                     if (gameInfo.white && gameInfo.black &&
3469                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3470                         strcmp(gameInfo.black, star_match[2]) == 0) {
3471                         /* All is well */
3472                         ics_getting_history = H_GOT_REQ_HEADER;
3473                     }
3474                     break;
3475                   case H_GOT_REQ_HEADER:
3476                   case H_GOT_UNREQ_HEADER:
3477                   case H_GOT_UNWANTED_HEADER:
3478                   case H_GETTING_MOVES:
3479                     /* Should not happen */
3480                     DisplayError(_("Error gathering move list: two headers"), 0);
3481                     ics_getting_history = H_FALSE;
3482                     break;
3483                 }
3484
3485                 /* Save player ratings into gameInfo if needed */
3486                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3487                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3488                     (gameInfo.whiteRating == -1 ||
3489                      gameInfo.blackRating == -1)) {
3490
3491                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3492                     gameInfo.blackRating = string_to_rating(star_match[3]);
3493                     if (appData.debugMode)
3494                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3495                               gameInfo.whiteRating, gameInfo.blackRating);
3496                 }
3497                 continue;
3498             }
3499
3500             if (looking_at(buf, &i,
3501               "* * match, initial time: * minute*, increment: * second")) {
3502                 /* Header for a move list -- second line */
3503                 /* Initial board will follow if this is a wild game */
3504                 if (gameInfo.event != NULL) free(gameInfo.event);
3505                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3506                 gameInfo.event = StrSave(str);
3507                 /* [HGM] we switched variant. Translate boards if needed. */
3508                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3509                 continue;
3510             }
3511
3512             if (looking_at(buf, &i, "Move  ")) {
3513                 /* Beginning of a move list */
3514                 switch (ics_getting_history) {
3515                   case H_FALSE:
3516                     /* Normally should not happen */
3517                     /* Maybe user hit reset while we were parsing */
3518                     break;
3519                   case H_REQUESTED:
3520                     /* Happens if we are ignoring a move list that is not
3521                      * the one we just requested.  Common if the user
3522                      * tries to observe two games without turning off
3523                      * getMoveList */
3524                     break;
3525                   case H_GETTING_MOVES:
3526                     /* Should not happen */
3527                     DisplayError(_("Error gathering move list: nested"), 0);
3528                     ics_getting_history = H_FALSE;
3529                     break;
3530                   case H_GOT_REQ_HEADER:
3531                     ics_getting_history = H_GETTING_MOVES;
3532                     started = STARTED_MOVES;
3533                     parse_pos = 0;
3534                     if (oldi > next_out) {
3535                         SendToPlayer(&buf[next_out], oldi - next_out);
3536                     }
3537                     break;
3538                   case H_GOT_UNREQ_HEADER:
3539                     ics_getting_history = H_GETTING_MOVES;
3540                     started = STARTED_MOVES_NOHIDE;
3541                     parse_pos = 0;
3542                     break;
3543                   case H_GOT_UNWANTED_HEADER:
3544                     ics_getting_history = H_FALSE;
3545                     break;
3546                 }
3547                 continue;
3548             }
3549
3550             if (looking_at(buf, &i, "% ") ||
3551                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3552                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3553                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3554                     soughtPending = FALSE;
3555                     seekGraphUp = TRUE;
3556                     DrawSeekGraph();
3557                 }
3558                 if(suppressKibitz) next_out = i;
3559                 savingComment = FALSE;
3560                 suppressKibitz = 0;
3561                 switch (started) {
3562                   case STARTED_MOVES:
3563                   case STARTED_MOVES_NOHIDE:
3564                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3565                     parse[parse_pos + i - oldi] = NULLCHAR;
3566                     ParseGameHistory(parse);
3567 #if ZIPPY
3568                     if (appData.zippyPlay && first.initDone) {
3569                         FeedMovesToProgram(&first, forwardMostMove);
3570                         if (gameMode == IcsPlayingWhite) {
3571                             if (WhiteOnMove(forwardMostMove)) {
3572                                 if (first.sendTime) {
3573                                   if (first.useColors) {
3574                                     SendToProgram("black\n", &first);
3575                                   }
3576                                   SendTimeRemaining(&first, TRUE);
3577                                 }
3578                                 if (first.useColors) {
3579                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3580                                 }
3581                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3582                                 first.maybeThinking = TRUE;
3583                             } else {
3584                                 if (first.usePlayother) {
3585                                   if (first.sendTime) {
3586                                     SendTimeRemaining(&first, TRUE);
3587                                   }
3588                                   SendToProgram("playother\n", &first);
3589                                   firstMove = FALSE;
3590                                 } else {
3591                                   firstMove = TRUE;
3592                                 }
3593                             }
3594                         } else if (gameMode == IcsPlayingBlack) {
3595                             if (!WhiteOnMove(forwardMostMove)) {
3596                                 if (first.sendTime) {
3597                                   if (first.useColors) {
3598                                     SendToProgram("white\n", &first);
3599                                   }
3600                                   SendTimeRemaining(&first, FALSE);
3601                                 }
3602                                 if (first.useColors) {
3603                                   SendToProgram("black\n", &first);
3604                                 }
3605                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3606                                 first.maybeThinking = TRUE;
3607                             } else {
3608                                 if (first.usePlayother) {
3609                                   if (first.sendTime) {
3610                                     SendTimeRemaining(&first, FALSE);
3611                                   }
3612                                   SendToProgram("playother\n", &first);
3613                                   firstMove = FALSE;
3614                                 } else {
3615                                   firstMove = TRUE;
3616                                 }
3617                             }
3618                         }
3619                     }
3620 #endif
3621                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3622                         /* Moves came from oldmoves or moves command
3623                            while we weren't doing anything else.
3624                            */
3625                         currentMove = forwardMostMove;
3626                         ClearHighlights();/*!!could figure this out*/
3627                         flipView = appData.flipView;
3628                         DrawPosition(TRUE, boards[currentMove]);
3629                         DisplayBothClocks();
3630                         snprintf(str, MSG_SIZ, "%s vs. %s",
3631                                 gameInfo.white, gameInfo.black);
3632                         DisplayTitle(str);
3633                         gameMode = IcsIdle;
3634                     } else {
3635                         /* Moves were history of an active game */
3636                         if (gameInfo.resultDetails != NULL) {
3637                             free(gameInfo.resultDetails);
3638                             gameInfo.resultDetails = NULL;
3639                         }
3640                     }
3641                     HistorySet(parseList, backwardMostMove,
3642                                forwardMostMove, currentMove-1);
3643                     DisplayMove(currentMove - 1);
3644                     if (started == STARTED_MOVES) next_out = i;
3645                     started = STARTED_NONE;
3646                     ics_getting_history = H_FALSE;
3647                     break;
3648
3649                   case STARTED_OBSERVE:
3650                     started = STARTED_NONE;
3651                     SendToICS(ics_prefix);
3652                     SendToICS("refresh\n");
3653                     break;
3654
3655                   default:
3656                     break;
3657                 }
3658                 if(bookHit) { // [HGM] book: simulate book reply
3659                     static char bookMove[MSG_SIZ]; // a bit generous?
3660
3661                     programStats.nodes = programStats.depth = programStats.time =
3662                     programStats.score = programStats.got_only_move = 0;
3663                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3664
3665                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3666                     strcat(bookMove, bookHit);
3667                     HandleMachineMove(bookMove, &first);
3668                 }
3669                 continue;
3670             }
3671
3672             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3673                  started == STARTED_HOLDINGS ||
3674                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3675                 /* Accumulate characters in move list or board */
3676                 parse[parse_pos++] = buf[i];
3677             }
3678
3679             /* Start of game messages.  Mostly we detect start of game
3680                when the first board image arrives.  On some versions
3681                of the ICS, though, we need to do a "refresh" after starting
3682                to observe in order to get the current board right away. */
3683             if (looking_at(buf, &i, "Adding game * to observation list")) {
3684                 started = STARTED_OBSERVE;
3685                 continue;
3686             }
3687
3688             /* Handle auto-observe */
3689             if (appData.autoObserve &&
3690                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3691                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3692                 char *player;
3693                 /* Choose the player that was highlighted, if any. */
3694                 if (star_match[0][0] == '\033' ||
3695                     star_match[1][0] != '\033') {
3696                     player = star_match[0];
3697                 } else {
3698                     player = star_match[2];
3699                 }
3700                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3701                         ics_prefix, StripHighlightAndTitle(player));
3702                 SendToICS(str);
3703
3704                 /* Save ratings from notify string */
3705                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3706                 player1Rating = string_to_rating(star_match[1]);
3707                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3708                 player2Rating = string_to_rating(star_match[3]);
3709
3710                 if (appData.debugMode)
3711                   fprintf(debugFP,
3712                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3713                           player1Name, player1Rating,
3714                           player2Name, player2Rating);
3715
3716                 continue;
3717             }
3718
3719             /* Deal with automatic examine mode after a game,
3720                and with IcsObserving -> IcsExamining transition */
3721             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3722                 looking_at(buf, &i, "has made you an examiner of game *")) {
3723
3724                 int gamenum = atoi(star_match[0]);
3725                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3726                     gamenum == ics_gamenum) {
3727                     /* We were already playing or observing this game;
3728                        no need to refetch history */
3729                     gameMode = IcsExamining;
3730                     if (pausing) {
3731                         pauseExamForwardMostMove = forwardMostMove;
3732                     } else if (currentMove < forwardMostMove) {
3733                         ForwardInner(forwardMostMove);
3734                     }
3735                 } else {
3736                     /* I don't think this case really can happen */
3737                     SendToICS(ics_prefix);
3738                     SendToICS("refresh\n");
3739                 }
3740                 continue;
3741             }
3742
3743             /* Error messages */
3744 //          if (ics_user_moved) {
3745             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3746                 if (looking_at(buf, &i, "Illegal move") ||
3747                     looking_at(buf, &i, "Not a legal move") ||
3748                     looking_at(buf, &i, "Your king is in check") ||
3749                     looking_at(buf, &i, "It isn't your turn") ||
3750                     looking_at(buf, &i, "It is not your move")) {
3751                     /* Illegal move */
3752                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3753                         currentMove = forwardMostMove-1;
3754                         DisplayMove(currentMove - 1); /* before DMError */
3755                         DrawPosition(FALSE, boards[currentMove]);
3756                         SwitchClocks(forwardMostMove-1); // [HGM] race
3757                         DisplayBothClocks();
3758                     }
3759                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3760                     ics_user_moved = 0;
3761                     continue;
3762                 }
3763             }
3764
3765             if (looking_at(buf, &i, "still have time") ||
3766                 looking_at(buf, &i, "not out of time") ||
3767                 looking_at(buf, &i, "either player is out of time") ||
3768                 looking_at(buf, &i, "has timeseal; checking")) {
3769                 /* We must have called his flag a little too soon */
3770                 whiteFlag = blackFlag = FALSE;
3771                 continue;
3772             }
3773
3774             if (looking_at(buf, &i, "added * seconds to") ||
3775                 looking_at(buf, &i, "seconds were added to")) {
3776                 /* Update the clocks */
3777                 SendToICS(ics_prefix);
3778                 SendToICS("refresh\n");
3779                 continue;
3780             }
3781
3782             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3783                 ics_clock_paused = TRUE;
3784                 StopClocks();
3785                 continue;
3786             }
3787
3788             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3789                 ics_clock_paused = FALSE;
3790                 StartClocks();
3791                 continue;
3792             }
3793
3794             /* Grab player ratings from the Creating: message.
3795                Note we have to check for the special case when
3796                the ICS inserts things like [white] or [black]. */
3797             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3798                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3799                 /* star_matches:
3800                    0    player 1 name (not necessarily white)
3801                    1    player 1 rating
3802                    2    empty, white, or black (IGNORED)
3803                    3    player 2 name (not necessarily black)
3804                    4    player 2 rating
3805
3806                    The names/ratings are sorted out when the game
3807                    actually starts (below).
3808                 */
3809                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3810                 player1Rating = string_to_rating(star_match[1]);
3811                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3812                 player2Rating = string_to_rating(star_match[4]);
3813
3814                 if (appData.debugMode)
3815                   fprintf(debugFP,
3816                           "Ratings from 'Creating:' %s %d, %s %d\n",
3817                           player1Name, player1Rating,
3818                           player2Name, player2Rating);
3819
3820                 continue;
3821             }
3822
3823             /* Improved generic start/end-of-game messages */
3824             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3825                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3826                 /* If tkind == 0: */
3827                 /* star_match[0] is the game number */
3828                 /*           [1] is the white player's name */
3829                 /*           [2] is the black player's name */
3830                 /* For end-of-game: */
3831                 /*           [3] is the reason for the game end */
3832                 /*           [4] is a PGN end game-token, preceded by " " */
3833                 /* For start-of-game: */
3834                 /*           [3] begins with "Creating" or "Continuing" */
3835                 /*           [4] is " *" or empty (don't care). */
3836                 int gamenum = atoi(star_match[0]);
3837                 char *whitename, *blackname, *why, *endtoken;
3838                 ChessMove endtype = EndOfFile;
3839
3840                 if (tkind == 0) {
3841                   whitename = star_match[1];
3842                   blackname = star_match[2];
3843                   why = star_match[3];
3844                   endtoken = star_match[4];
3845                 } else {
3846                   whitename = star_match[1];
3847                   blackname = star_match[3];
3848                   why = star_match[5];
3849                   endtoken = star_match[6];
3850                 }
3851
3852                 /* Game start messages */
3853                 if (strncmp(why, "Creating ", 9) == 0 ||
3854                     strncmp(why, "Continuing ", 11) == 0) {
3855                     gs_gamenum = gamenum;
3856                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3857                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3858 #if ZIPPY
3859                     if (appData.zippyPlay) {
3860                         ZippyGameStart(whitename, blackname);
3861                     }
3862 #endif /*ZIPPY*/
3863                     partnerBoardValid = FALSE; // [HGM] bughouse
3864                     continue;
3865                 }
3866
3867                 /* Game end messages */
3868                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3869                     ics_gamenum != gamenum) {
3870                     continue;
3871                 }
3872                 while (endtoken[0] == ' ') endtoken++;
3873                 switch (endtoken[0]) {
3874                   case '*':
3875                   default:
3876                     endtype = GameUnfinished;
3877                     break;
3878                   case '0':
3879                     endtype = BlackWins;
3880                     break;
3881                   case '1':
3882                     if (endtoken[1] == '/')
3883                       endtype = GameIsDrawn;
3884                     else
3885                       endtype = WhiteWins;
3886                     break;
3887                 }
3888                 GameEnds(endtype, why, GE_ICS);
3889 #if ZIPPY
3890                 if (appData.zippyPlay && first.initDone) {
3891                     ZippyGameEnd(endtype, why);
3892                     if (first.pr == NULL) {
3893                       /* Start the next process early so that we'll
3894                          be ready for the next challenge */
3895                       StartChessProgram(&first);
3896                     }
3897                     /* Send "new" early, in case this command takes
3898                        a long time to finish, so that we'll be ready
3899                        for the next challenge. */
3900                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3901                     Reset(TRUE, TRUE);
3902                 }
3903 #endif /*ZIPPY*/
3904                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3905                 continue;
3906             }
3907
3908             if (looking_at(buf, &i, "Removing game * from observation") ||
3909                 looking_at(buf, &i, "no longer observing game *") ||
3910                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3911                 if (gameMode == IcsObserving &&
3912                     atoi(star_match[0]) == ics_gamenum)
3913                   {
3914                       /* icsEngineAnalyze */
3915                       if (appData.icsEngineAnalyze) {
3916                             ExitAnalyzeMode();
3917                             ModeHighlight();
3918                       }
3919                       StopClocks();
3920                       gameMode = IcsIdle;
3921                       ics_gamenum = -1;
3922                       ics_user_moved = FALSE;
3923                   }
3924                 continue;
3925             }
3926
3927             if (looking_at(buf, &i, "no longer examining game *")) {
3928                 if (gameMode == IcsExamining &&
3929                     atoi(star_match[0]) == ics_gamenum)
3930                   {
3931                       gameMode = IcsIdle;
3932                       ics_gamenum = -1;
3933                       ics_user_moved = FALSE;
3934                   }
3935                 continue;
3936             }
3937
3938             /* Advance leftover_start past any newlines we find,
3939                so only partial lines can get reparsed */
3940             if (looking_at(buf, &i, "\n")) {
3941                 prevColor = curColor;
3942                 if (curColor != ColorNormal) {
3943                     if (oldi > next_out) {
3944                         SendToPlayer(&buf[next_out], oldi - next_out);
3945                         next_out = oldi;
3946                     }
3947                     Colorize(ColorNormal, FALSE);
3948                     curColor = ColorNormal;
3949                 }
3950                 if (started == STARTED_BOARD) {
3951                     started = STARTED_NONE;
3952                     parse[parse_pos] = NULLCHAR;
3953                     ParseBoard12(parse);
3954                     ics_user_moved = 0;
3955
3956                     /* Send premove here */
3957                     if (appData.premove) {
3958                       char str[MSG_SIZ];
3959                       if (currentMove == 0 &&
3960                           gameMode == IcsPlayingWhite &&
3961                           appData.premoveWhite) {
3962                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3963                         if (appData.debugMode)
3964                           fprintf(debugFP, "Sending premove:\n");
3965                         SendToICS(str);
3966                       } else if (currentMove == 1 &&
3967                                  gameMode == IcsPlayingBlack &&
3968                                  appData.premoveBlack) {
3969                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3970                         if (appData.debugMode)
3971                           fprintf(debugFP, "Sending premove:\n");
3972                         SendToICS(str);
3973                       } else if (gotPremove) {
3974                         gotPremove = 0;
3975                         ClearPremoveHighlights();
3976                         if (appData.debugMode)
3977                           fprintf(debugFP, "Sending premove:\n");
3978                           UserMoveEvent(premoveFromX, premoveFromY,
3979                                         premoveToX, premoveToY,
3980                                         premovePromoChar);
3981                       }
3982                     }
3983
3984                     /* Usually suppress following prompt */
3985                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3986                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3987                         if (looking_at(buf, &i, "*% ")) {
3988                             savingComment = FALSE;
3989                             suppressKibitz = 0;
3990                         }
3991                     }
3992                     next_out = i;
3993                 } else if (started == STARTED_HOLDINGS) {
3994                     int gamenum;
3995                     char new_piece[MSG_SIZ];
3996                     started = STARTED_NONE;
3997                     parse[parse_pos] = NULLCHAR;
3998                     if (appData.debugMode)
3999                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4000                                                         parse, currentMove);
4001                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4002                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4003                         if (gameInfo.variant == VariantNormal) {
4004                           /* [HGM] We seem to switch variant during a game!
4005                            * Presumably no holdings were displayed, so we have
4006                            * to move the position two files to the right to
4007                            * create room for them!
4008                            */
4009                           VariantClass newVariant;
4010                           switch(gameInfo.boardWidth) { // base guess on board width
4011                                 case 9:  newVariant = VariantShogi; break;
4012                                 case 10: newVariant = VariantGreat; break;
4013                                 default: newVariant = VariantCrazyhouse; break;
4014                           }
4015                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4016                           /* Get a move list just to see the header, which
4017                              will tell us whether this is really bug or zh */
4018                           if (ics_getting_history == H_FALSE) {
4019                             ics_getting_history = H_REQUESTED;
4020                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4021                             SendToICS(str);
4022                           }
4023                         }
4024                         new_piece[0] = NULLCHAR;
4025                         sscanf(parse, "game %d white [%s black [%s <- %s",
4026                                &gamenum, white_holding, black_holding,
4027                                new_piece);
4028                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4029                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4030                         /* [HGM] copy holdings to board holdings area */
4031                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4032                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4033                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4034 #if ZIPPY
4035                         if (appData.zippyPlay && first.initDone) {
4036                             ZippyHoldings(white_holding, black_holding,
4037                                           new_piece);
4038                         }
4039 #endif /*ZIPPY*/
4040                         if (tinyLayout || smallLayout) {
4041                             char wh[16], bh[16];
4042                             PackHolding(wh, white_holding);
4043                             PackHolding(bh, black_holding);
4044                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4045                                     gameInfo.white, gameInfo.black);
4046                         } else {
4047                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4048                                     gameInfo.white, white_holding,
4049                                     gameInfo.black, black_holding);
4050                         }
4051                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4052                         DrawPosition(FALSE, boards[currentMove]);
4053                         DisplayTitle(str);
4054                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4055                         sscanf(parse, "game %d white [%s black [%s <- %s",
4056                                &gamenum, white_holding, black_holding,
4057                                new_piece);
4058                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4059                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4060                         /* [HGM] copy holdings to partner-board holdings area */
4061                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4062                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4063                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4064                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4065                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4066                       }
4067                     }
4068                     /* Suppress following prompt */
4069                     if (looking_at(buf, &i, "*% ")) {
4070                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4071                         savingComment = FALSE;
4072                         suppressKibitz = 0;
4073                     }
4074                     next_out = i;
4075                 }
4076                 continue;
4077             }
4078
4079             i++;                /* skip unparsed character and loop back */
4080         }
4081
4082         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4083 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4084 //          SendToPlayer(&buf[next_out], i - next_out);
4085             started != STARTED_HOLDINGS && leftover_start > next_out) {
4086             SendToPlayer(&buf[next_out], leftover_start - next_out);
4087             next_out = i;
4088         }
4089
4090         leftover_len = buf_len - leftover_start;
4091         /* if buffer ends with something we couldn't parse,
4092            reparse it after appending the next read */
4093
4094     } else if (count == 0) {
4095         RemoveInputSource(isr);
4096         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4097     } else {
4098         DisplayFatalError(_("Error reading from ICS"), error, 1);
4099     }
4100 }
4101
4102
4103 /* Board style 12 looks like this:
4104
4105    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4106
4107  * The "<12> " is stripped before it gets to this routine.  The two
4108  * trailing 0's (flip state and clock ticking) are later addition, and
4109  * some chess servers may not have them, or may have only the first.
4110  * Additional trailing fields may be added in the future.
4111  */
4112
4113 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4114
4115 #define RELATION_OBSERVING_PLAYED    0
4116 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4117 #define RELATION_PLAYING_MYMOVE      1
4118 #define RELATION_PLAYING_NOTMYMOVE  -1
4119 #define RELATION_EXAMINING           2
4120 #define RELATION_ISOLATED_BOARD     -3
4121 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4122
4123 void
4124 ParseBoard12(string)
4125      char *string;
4126 {
4127     GameMode newGameMode;
4128     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4129     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4130     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4131     char to_play, board_chars[200];
4132     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4133     char black[32], white[32];
4134     Board board;
4135     int prevMove = currentMove;
4136     int ticking = 2;
4137     ChessMove moveType;
4138     int fromX, fromY, toX, toY;
4139     char promoChar;
4140     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4141     char *bookHit = NULL; // [HGM] book
4142     Boolean weird = FALSE, reqFlag = FALSE;
4143
4144     fromX = fromY = toX = toY = -1;
4145
4146     newGame = FALSE;
4147
4148     if (appData.debugMode)
4149       fprintf(debugFP, _("Parsing board: %s\n"), string);
4150
4151     move_str[0] = NULLCHAR;
4152     elapsed_time[0] = NULLCHAR;
4153     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4154         int  i = 0, j;
4155         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4156             if(string[i] == ' ') { ranks++; files = 0; }
4157             else files++;
4158             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4159             i++;
4160         }
4161         for(j = 0; j <i; j++) board_chars[j] = string[j];
4162         board_chars[i] = '\0';
4163         string += i + 1;
4164     }
4165     n = sscanf(string, PATTERN, &to_play, &double_push,
4166                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4167                &gamenum, white, black, &relation, &basetime, &increment,
4168                &white_stren, &black_stren, &white_time, &black_time,
4169                &moveNum, str, elapsed_time, move_str, &ics_flip,
4170                &ticking);
4171
4172     if (n < 21) {
4173         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4174         DisplayError(str, 0);
4175         return;
4176     }
4177
4178     /* Convert the move number to internal form */
4179     moveNum = (moveNum - 1) * 2;
4180     if (to_play == 'B') moveNum++;
4181     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4182       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4183                         0, 1);
4184       return;
4185     }
4186
4187     switch (relation) {
4188       case RELATION_OBSERVING_PLAYED:
4189       case RELATION_OBSERVING_STATIC:
4190         if (gamenum == -1) {
4191             /* Old ICC buglet */
4192             relation = RELATION_OBSERVING_STATIC;
4193         }
4194         newGameMode = IcsObserving;
4195         break;
4196       case RELATION_PLAYING_MYMOVE:
4197       case RELATION_PLAYING_NOTMYMOVE:
4198         newGameMode =
4199           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4200             IcsPlayingWhite : IcsPlayingBlack;
4201         break;
4202       case RELATION_EXAMINING:
4203         newGameMode = IcsExamining;
4204         break;
4205       case RELATION_ISOLATED_BOARD:
4206       default:
4207         /* Just display this board.  If user was doing something else,
4208            we will forget about it until the next board comes. */
4209         newGameMode = IcsIdle;
4210         break;
4211       case RELATION_STARTING_POSITION:
4212         newGameMode = gameMode;
4213         break;
4214     }
4215
4216     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4217          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4218       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4219       char *toSqr;
4220       for (k = 0; k < ranks; k++) {
4221         for (j = 0; j < files; j++)
4222           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4223         if(gameInfo.holdingsWidth > 1) {
4224              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4225              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4226         }
4227       }
4228       CopyBoard(partnerBoard, board);
4229       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4230         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4231         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4232       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4233       if(toSqr = strchr(str, '-')) {
4234         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4235         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4236       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4237       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4238       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4239       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4240       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4241       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4242                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4243       DisplayMessage(partnerStatus, "");
4244         partnerBoardValid = TRUE;
4245       return;
4246     }
4247
4248     /* Modify behavior for initial board display on move listing
4249        of wild games.
4250        */
4251     switch (ics_getting_history) {
4252       case H_FALSE:
4253       case H_REQUESTED:
4254         break;
4255       case H_GOT_REQ_HEADER:
4256       case H_GOT_UNREQ_HEADER:
4257         /* This is the initial position of the current game */
4258         gamenum = ics_gamenum;
4259         moveNum = 0;            /* old ICS bug workaround */
4260         if (to_play == 'B') {
4261           startedFromSetupPosition = TRUE;
4262           blackPlaysFirst = TRUE;
4263           moveNum = 1;
4264           if (forwardMostMove == 0) forwardMostMove = 1;
4265           if (backwardMostMove == 0) backwardMostMove = 1;
4266           if (currentMove == 0) currentMove = 1;
4267         }
4268         newGameMode = gameMode;
4269         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4270         break;
4271       case H_GOT_UNWANTED_HEADER:
4272         /* This is an initial board that we don't want */
4273         return;
4274       case H_GETTING_MOVES:
4275         /* Should not happen */
4276         DisplayError(_("Error gathering move list: extra board"), 0);
4277         ics_getting_history = H_FALSE;
4278         return;
4279     }
4280
4281    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4282                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4283      /* [HGM] We seem to have switched variant unexpectedly
4284       * Try to guess new variant from board size
4285       */
4286           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4287           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4288           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4289           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4290           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4291           if(!weird) newVariant = VariantNormal;
4292           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4293           /* Get a move list just to see the header, which
4294              will tell us whether this is really bug or zh */
4295           if (ics_getting_history == H_FALSE) {
4296             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4297             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4298             SendToICS(str);
4299           }
4300     }
4301
4302     /* Take action if this is the first board of a new game, or of a
4303        different game than is currently being displayed.  */
4304     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4305         relation == RELATION_ISOLATED_BOARD) {
4306
4307         /* Forget the old game and get the history (if any) of the new one */
4308         if (gameMode != BeginningOfGame) {
4309           Reset(TRUE, TRUE);
4310         }
4311         newGame = TRUE;
4312         if (appData.autoRaiseBoard) BoardToTop();
4313         prevMove = -3;
4314         if (gamenum == -1) {
4315             newGameMode = IcsIdle;
4316         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4317                    appData.getMoveList && !reqFlag) {
4318             /* Need to get game history */
4319             ics_getting_history = H_REQUESTED;
4320             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4321             SendToICS(str);
4322         }
4323
4324         /* Initially flip the board to have black on the bottom if playing
4325            black or if the ICS flip flag is set, but let the user change
4326            it with the Flip View button. */
4327         flipView = appData.autoFlipView ?
4328           (newGameMode == IcsPlayingBlack) || ics_flip :
4329           appData.flipView;
4330
4331         /* Done with values from previous mode; copy in new ones */
4332         gameMode = newGameMode;
4333         ModeHighlight();
4334         ics_gamenum = gamenum;
4335         if (gamenum == gs_gamenum) {
4336             int klen = strlen(gs_kind);
4337             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4338             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4339             gameInfo.event = StrSave(str);
4340         } else {
4341             gameInfo.event = StrSave("ICS game");
4342         }
4343         gameInfo.site = StrSave(appData.icsHost);
4344         gameInfo.date = PGNDate();
4345         gameInfo.round = StrSave("-");
4346         gameInfo.white = StrSave(white);
4347         gameInfo.black = StrSave(black);
4348         timeControl = basetime * 60 * 1000;
4349         timeControl_2 = 0;
4350         timeIncrement = increment * 1000;
4351         movesPerSession = 0;
4352         gameInfo.timeControl = TimeControlTagValue();
4353         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4354   if (appData.debugMode) {
4355     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4356     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4357     setbuf(debugFP, NULL);
4358   }
4359
4360         gameInfo.outOfBook = NULL;
4361
4362         /* Do we have the ratings? */
4363         if (strcmp(player1Name, white) == 0 &&
4364             strcmp(player2Name, black) == 0) {
4365             if (appData.debugMode)
4366               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4367                       player1Rating, player2Rating);
4368             gameInfo.whiteRating = player1Rating;
4369             gameInfo.blackRating = player2Rating;
4370         } else if (strcmp(player2Name, white) == 0 &&
4371                    strcmp(player1Name, black) == 0) {
4372             if (appData.debugMode)
4373               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4374                       player2Rating, player1Rating);
4375             gameInfo.whiteRating = player2Rating;
4376             gameInfo.blackRating = player1Rating;
4377         }
4378         player1Name[0] = player2Name[0] = NULLCHAR;
4379
4380         /* Silence shouts if requested */
4381         if (appData.quietPlay &&
4382             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4383             SendToICS(ics_prefix);
4384             SendToICS("set shout 0\n");
4385         }
4386     }
4387
4388     /* Deal with midgame name changes */
4389     if (!newGame) {
4390         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4391             if (gameInfo.white) free(gameInfo.white);
4392             gameInfo.white = StrSave(white);
4393         }
4394         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4395             if (gameInfo.black) free(gameInfo.black);
4396             gameInfo.black = StrSave(black);
4397         }
4398     }
4399
4400     /* Throw away game result if anything actually changes in examine mode */
4401     if (gameMode == IcsExamining && !newGame) {
4402         gameInfo.result = GameUnfinished;
4403         if (gameInfo.resultDetails != NULL) {
4404             free(gameInfo.resultDetails);
4405             gameInfo.resultDetails = NULL;
4406         }
4407     }
4408
4409     /* In pausing && IcsExamining mode, we ignore boards coming
4410        in if they are in a different variation than we are. */
4411     if (pauseExamInvalid) return;
4412     if (pausing && gameMode == IcsExamining) {
4413         if (moveNum <= pauseExamForwardMostMove) {
4414             pauseExamInvalid = TRUE;
4415             forwardMostMove = pauseExamForwardMostMove;
4416             return;
4417         }
4418     }
4419
4420   if (appData.debugMode) {
4421     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4422   }
4423     /* Parse the board */
4424     for (k = 0; k < ranks; k++) {
4425       for (j = 0; j < files; j++)
4426         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4427       if(gameInfo.holdingsWidth > 1) {
4428            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4429            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4430       }
4431     }
4432     CopyBoard(boards[moveNum], board);
4433     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4434     if (moveNum == 0) {
4435         startedFromSetupPosition =
4436           !CompareBoards(board, initialPosition);
4437         if(startedFromSetupPosition)
4438             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4439     }
4440
4441     /* [HGM] Set castling rights. Take the outermost Rooks,
4442        to make it also work for FRC opening positions. Note that board12
4443        is really defective for later FRC positions, as it has no way to
4444        indicate which Rook can castle if they are on the same side of King.
4445        For the initial position we grant rights to the outermost Rooks,
4446        and remember thos rights, and we then copy them on positions
4447        later in an FRC game. This means WB might not recognize castlings with
4448        Rooks that have moved back to their original position as illegal,
4449        but in ICS mode that is not its job anyway.
4450     */
4451     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4452     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4453
4454         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4455             if(board[0][i] == WhiteRook) j = i;
4456         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4457         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4458             if(board[0][i] == WhiteRook) j = i;
4459         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4460         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4461             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4462         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4463         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4464             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4465         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4466
4467         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4468         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4469             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4470         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4471             if(board[BOARD_HEIGHT-1][k] == bKing)
4472                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4473         if(gameInfo.variant == VariantTwoKings) {
4474             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4475             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4476             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4477         }
4478     } else { int r;
4479         r = boards[moveNum][CASTLING][0] = initialRights[0];
4480         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4481         r = boards[moveNum][CASTLING][1] = initialRights[1];
4482         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4483         r = boards[moveNum][CASTLING][3] = initialRights[3];
4484         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4485         r = boards[moveNum][CASTLING][4] = initialRights[4];
4486         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4487         /* wildcastle kludge: always assume King has rights */
4488         r = boards[moveNum][CASTLING][2] = initialRights[2];
4489         r = boards[moveNum][CASTLING][5] = initialRights[5];
4490     }
4491     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4492     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4493
4494
4495     if (ics_getting_history == H_GOT_REQ_HEADER ||
4496         ics_getting_history == H_GOT_UNREQ_HEADER) {
4497         /* This was an initial position from a move list, not
4498            the current position */
4499         return;
4500     }
4501
4502     /* Update currentMove and known move number limits */
4503     newMove = newGame || moveNum > forwardMostMove;
4504
4505     if (newGame) {
4506         forwardMostMove = backwardMostMove = currentMove = moveNum;
4507         if (gameMode == IcsExamining && moveNum == 0) {
4508           /* Workaround for ICS limitation: we are not told the wild
4509              type when starting to examine a game.  But if we ask for
4510              the move list, the move list header will tell us */
4511             ics_getting_history = H_REQUESTED;
4512             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4513             SendToICS(str);
4514         }
4515     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4516                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4517 #if ZIPPY
4518         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4519         /* [HGM] applied this also to an engine that is silently watching        */
4520         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4521             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4522             gameInfo.variant == currentlyInitializedVariant) {
4523           takeback = forwardMostMove - moveNum;
4524           for (i = 0; i < takeback; i++) {
4525             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4526             SendToProgram("undo\n", &first);
4527           }
4528         }
4529 #endif
4530
4531         forwardMostMove = moveNum;
4532         if (!pausing || currentMove > forwardMostMove)
4533           currentMove = forwardMostMove;
4534     } else {
4535         /* New part of history that is not contiguous with old part */
4536         if (pausing && gameMode == IcsExamining) {
4537             pauseExamInvalid = TRUE;
4538             forwardMostMove = pauseExamForwardMostMove;
4539             return;
4540         }
4541         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4542 #if ZIPPY
4543             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4544                 // [HGM] when we will receive the move list we now request, it will be
4545                 // fed to the engine from the first move on. So if the engine is not
4546                 // in the initial position now, bring it there.
4547                 InitChessProgram(&first, 0);
4548             }
4549 #endif
4550             ics_getting_history = H_REQUESTED;
4551             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4552             SendToICS(str);
4553         }
4554         forwardMostMove = backwardMostMove = currentMove = moveNum;
4555     }
4556
4557     /* Update the clocks */
4558     if (strchr(elapsed_time, '.')) {
4559       /* Time is in ms */
4560       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4561       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4562     } else {
4563       /* Time is in seconds */
4564       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4565       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4566     }
4567
4568
4569 #if ZIPPY
4570     if (appData.zippyPlay && newGame &&
4571         gameMode != IcsObserving && gameMode != IcsIdle &&
4572         gameMode != IcsExamining)
4573       ZippyFirstBoard(moveNum, basetime, increment);
4574 #endif
4575
4576     /* Put the move on the move list, first converting
4577        to canonical algebraic form. */
4578     if (moveNum > 0) {
4579   if (appData.debugMode) {
4580     if (appData.debugMode) { int f = forwardMostMove;
4581         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4582                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4583                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4584     }
4585     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4586     fprintf(debugFP, "moveNum = %d\n", moveNum);
4587     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4588     setbuf(debugFP, NULL);
4589   }
4590         if (moveNum <= backwardMostMove) {
4591             /* We don't know what the board looked like before
4592                this move.  Punt. */
4593           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4594             strcat(parseList[moveNum - 1], " ");
4595             strcat(parseList[moveNum - 1], elapsed_time);
4596             moveList[moveNum - 1][0] = NULLCHAR;
4597         } else if (strcmp(move_str, "none") == 0) {
4598             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4599             /* Again, we don't know what the board looked like;
4600                this is really the start of the game. */
4601             parseList[moveNum - 1][0] = NULLCHAR;
4602             moveList[moveNum - 1][0] = NULLCHAR;
4603             backwardMostMove = moveNum;
4604             startedFromSetupPosition = TRUE;
4605             fromX = fromY = toX = toY = -1;
4606         } else {
4607           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4608           //                 So we parse the long-algebraic move string in stead of the SAN move
4609           int valid; char buf[MSG_SIZ], *prom;
4610
4611           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4612                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4613           // str looks something like "Q/a1-a2"; kill the slash
4614           if(str[1] == '/')
4615             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4616           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4617           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4618                 strcat(buf, prom); // long move lacks promo specification!
4619           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4620                 if(appData.debugMode)
4621                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4622                 safeStrCpy(move_str, buf, MSG_SIZ);
4623           }
4624           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4625                                 &fromX, &fromY, &toX, &toY, &promoChar)
4626                || ParseOneMove(buf, moveNum - 1, &moveType,
4627                                 &fromX, &fromY, &toX, &toY, &promoChar);
4628           // end of long SAN patch
4629           if (valid) {
4630             (void) CoordsToAlgebraic(boards[moveNum - 1],
4631                                      PosFlags(moveNum - 1),
4632                                      fromY, fromX, toY, toX, promoChar,
4633                                      parseList[moveNum-1]);
4634             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4635               case MT_NONE:
4636               case MT_STALEMATE:
4637               default:
4638                 break;
4639               case MT_CHECK:
4640                 if(gameInfo.variant != VariantShogi)
4641                     strcat(parseList[moveNum - 1], "+");
4642                 break;
4643               case MT_CHECKMATE:
4644               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4645                 strcat(parseList[moveNum - 1], "#");
4646                 break;
4647             }
4648             strcat(parseList[moveNum - 1], " ");
4649             strcat(parseList[moveNum - 1], elapsed_time);
4650             /* currentMoveString is set as a side-effect of ParseOneMove */
4651             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4652             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4653             strcat(moveList[moveNum - 1], "\n");
4654
4655             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4656                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4657               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4658                 ChessSquare old, new = boards[moveNum][k][j];
4659                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4660                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4661                   if(old == new) continue;
4662                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4663                   else if(new == WhiteWazir || new == BlackWazir) {
4664                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4665                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4666                       else boards[moveNum][k][j] = old; // preserve type of Gold
4667                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4668                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4669               }
4670           } else {
4671             /* Move from ICS was illegal!?  Punt. */
4672             if (appData.debugMode) {
4673               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4674               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4675             }
4676             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4677             strcat(parseList[moveNum - 1], " ");
4678             strcat(parseList[moveNum - 1], elapsed_time);
4679             moveList[moveNum - 1][0] = NULLCHAR;
4680             fromX = fromY = toX = toY = -1;
4681           }
4682         }
4683   if (appData.debugMode) {
4684     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4685     setbuf(debugFP, NULL);
4686   }
4687
4688 #if ZIPPY
4689         /* Send move to chess program (BEFORE animating it). */
4690         if (appData.zippyPlay && !newGame && newMove &&
4691            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4692
4693             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4694                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4695                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4696                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4697                             move_str);
4698                     DisplayError(str, 0);
4699                 } else {
4700                     if (first.sendTime) {
4701                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4702                     }
4703                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4704                     if (firstMove && !bookHit) {
4705                         firstMove = FALSE;
4706                         if (first.useColors) {
4707                           SendToProgram(gameMode == IcsPlayingWhite ?
4708                                         "white\ngo\n" :
4709                                         "black\ngo\n", &first);
4710                         } else {
4711                           SendToProgram("go\n", &first);
4712                         }
4713                         first.maybeThinking = TRUE;
4714                     }
4715                 }
4716             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4717               if (moveList[moveNum - 1][0] == NULLCHAR) {
4718                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4719                 DisplayError(str, 0);
4720               } else {
4721                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4722                 SendMoveToProgram(moveNum - 1, &first);
4723               }
4724             }
4725         }
4726 #endif
4727     }
4728
4729     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4730         /* If move comes from a remote source, animate it.  If it
4731            isn't remote, it will have already been animated. */
4732         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4733             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4734         }
4735         if (!pausing && appData.highlightLastMove) {
4736             SetHighlights(fromX, fromY, toX, toY);
4737         }
4738     }
4739
4740     /* Start the clocks */
4741     whiteFlag = blackFlag = FALSE;
4742     appData.clockMode = !(basetime == 0 && increment == 0);
4743     if (ticking == 0) {
4744       ics_clock_paused = TRUE;
4745       StopClocks();
4746     } else if (ticking == 1) {
4747       ics_clock_paused = FALSE;
4748     }
4749     if (gameMode == IcsIdle ||
4750         relation == RELATION_OBSERVING_STATIC ||
4751         relation == RELATION_EXAMINING ||
4752         ics_clock_paused)
4753       DisplayBothClocks();
4754     else
4755       StartClocks();
4756
4757     /* Display opponents and material strengths */
4758     if (gameInfo.variant != VariantBughouse &&
4759         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4760         if (tinyLayout || smallLayout) {
4761             if(gameInfo.variant == VariantNormal)
4762               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4763                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4764                     basetime, increment);
4765             else
4766               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4767                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4768                     basetime, increment, (int) gameInfo.variant);
4769         } else {
4770             if(gameInfo.variant == VariantNormal)
4771               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4772                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4773                     basetime, increment);
4774             else
4775               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4776                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4777                     basetime, increment, VariantName(gameInfo.variant));
4778         }
4779         DisplayTitle(str);
4780   if (appData.debugMode) {
4781     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4782   }
4783     }
4784
4785
4786     /* Display the board */
4787     if (!pausing && !appData.noGUI) {
4788
4789       if (appData.premove)
4790           if (!gotPremove ||
4791              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4792              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4793               ClearPremoveHighlights();
4794
4795       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4796         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4797       DrawPosition(j, boards[currentMove]);
4798
4799       DisplayMove(moveNum - 1);
4800       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4801             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4802               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4803         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4804       }
4805     }
4806
4807     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4808 #if ZIPPY
4809     if(bookHit) { // [HGM] book: simulate book reply
4810         static char bookMove[MSG_SIZ]; // a bit generous?
4811
4812         programStats.nodes = programStats.depth = programStats.time =
4813         programStats.score = programStats.got_only_move = 0;
4814         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4815
4816         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4817         strcat(bookMove, bookHit);
4818         HandleMachineMove(bookMove, &first);
4819     }
4820 #endif
4821 }
4822
4823 void
4824 GetMoveListEvent()
4825 {
4826     char buf[MSG_SIZ];
4827     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4828         ics_getting_history = H_REQUESTED;
4829         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4830         SendToICS(buf);
4831     }
4832 }
4833
4834 void
4835 AnalysisPeriodicEvent(force)
4836      int force;
4837 {
4838     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4839          && !force) || !appData.periodicUpdates)
4840       return;
4841
4842     /* Send . command to Crafty to collect stats */
4843     SendToProgram(".\n", &first);
4844
4845     /* Don't send another until we get a response (this makes
4846        us stop sending to old Crafty's which don't understand
4847        the "." command (sending illegal cmds resets node count & time,
4848        which looks bad)) */
4849     programStats.ok_to_send = 0;
4850 }
4851
4852 void ics_update_width(new_width)
4853         int new_width;
4854 {
4855         ics_printf("set width %d\n", new_width);
4856 }
4857
4858 void
4859 SendMoveToProgram(moveNum, cps)
4860      int moveNum;
4861      ChessProgramState *cps;
4862 {
4863     char buf[MSG_SIZ];
4864
4865     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4866         // null move in variant where engine does not understand it (for analysis purposes)
4867         SendBoard(cps, moveNum + 1); // send position after move in stead.
4868         return;
4869     }
4870     if (cps->useUsermove) {
4871       SendToProgram("usermove ", cps);
4872     }
4873     if (cps->useSAN) {
4874       char *space;
4875       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4876         int len = space - parseList[moveNum];
4877         memcpy(buf, parseList[moveNum], len);
4878         buf[len++] = '\n';
4879         buf[len] = NULLCHAR;
4880       } else {
4881         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4882       }
4883       SendToProgram(buf, cps);
4884     } else {
4885       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4886         AlphaRank(moveList[moveNum], 4);
4887         SendToProgram(moveList[moveNum], cps);
4888         AlphaRank(moveList[moveNum], 4); // and back
4889       } else
4890       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4891        * the engine. It would be nice to have a better way to identify castle
4892        * moves here. */
4893       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4894                                                                          && cps->useOOCastle) {
4895         int fromX = moveList[moveNum][0] - AAA;
4896         int fromY = moveList[moveNum][1] - ONE;
4897         int toX = moveList[moveNum][2] - AAA;
4898         int toY = moveList[moveNum][3] - ONE;
4899         if((boards[moveNum][fromY][fromX] == WhiteKing
4900             && boards[moveNum][toY][toX] == WhiteRook)
4901            || (boards[moveNum][fromY][fromX] == BlackKing
4902                && boards[moveNum][toY][toX] == BlackRook)) {
4903           if(toX > fromX) SendToProgram("O-O\n", cps);
4904           else SendToProgram("O-O-O\n", cps);
4905         }
4906         else SendToProgram(moveList[moveNum], cps);
4907       } else
4908       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4909         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4910           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4911           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4912                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4913         } else
4914           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4915                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4916         SendToProgram(buf, cps);
4917       }
4918       else SendToProgram(moveList[moveNum], cps);
4919       /* End of additions by Tord */
4920     }
4921
4922     /* [HGM] setting up the opening has brought engine in force mode! */
4923     /*       Send 'go' if we are in a mode where machine should play. */
4924     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4925         (gameMode == TwoMachinesPlay   ||
4926 #if ZIPPY
4927          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4928 #endif
4929          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4930         SendToProgram("go\n", cps);
4931   if (appData.debugMode) {
4932     fprintf(debugFP, "(extra)\n");
4933   }
4934     }
4935     setboardSpoiledMachineBlack = 0;
4936 }
4937
4938 void
4939 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4940      ChessMove moveType;
4941      int fromX, fromY, toX, toY;
4942      char promoChar;
4943 {
4944     char user_move[MSG_SIZ];
4945
4946     switch (moveType) {
4947       default:
4948         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4949                 (int)moveType, fromX, fromY, toX, toY);
4950         DisplayError(user_move + strlen("say "), 0);
4951         break;
4952       case WhiteKingSideCastle:
4953       case BlackKingSideCastle:
4954       case WhiteQueenSideCastleWild:
4955       case BlackQueenSideCastleWild:
4956       /* PUSH Fabien */
4957       case WhiteHSideCastleFR:
4958       case BlackHSideCastleFR:
4959       /* POP Fabien */
4960         snprintf(user_move, MSG_SIZ, "o-o\n");
4961         break;
4962       case WhiteQueenSideCastle:
4963       case BlackQueenSideCastle:
4964       case WhiteKingSideCastleWild:
4965       case BlackKingSideCastleWild:
4966       /* PUSH Fabien */
4967       case WhiteASideCastleFR:
4968       case BlackASideCastleFR:
4969       /* POP Fabien */
4970         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4971         break;
4972       case WhiteNonPromotion:
4973       case BlackNonPromotion:
4974         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4975         break;
4976       case WhitePromotion:
4977       case BlackPromotion:
4978         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4979           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4980                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4981                 PieceToChar(WhiteFerz));
4982         else if(gameInfo.variant == VariantGreat)
4983           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4984                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4985                 PieceToChar(WhiteMan));
4986         else
4987           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4988                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4989                 promoChar);
4990         break;
4991       case WhiteDrop:
4992       case BlackDrop:
4993       drop:
4994         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4995                  ToUpper(PieceToChar((ChessSquare) fromX)),
4996                  AAA + toX, ONE + toY);
4997         break;
4998       case IllegalMove:  /* could be a variant we don't quite understand */
4999         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5000       case NormalMove:
5001       case WhiteCapturesEnPassant:
5002       case BlackCapturesEnPassant:
5003         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5004                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5005         break;
5006     }
5007     SendToICS(user_move);
5008     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5009         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5010 }
5011
5012 void
5013 UploadGameEvent()
5014 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5015     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5016     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5017     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5018         DisplayError("You cannot do this while you are playing or observing", 0);
5019         return;
5020     }
5021     if(gameMode != IcsExamining) { // is this ever not the case?
5022         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5023
5024         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5025           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5026         } else { // on FICS we must first go to general examine mode
5027           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5028         }
5029         if(gameInfo.variant != VariantNormal) {
5030             // try figure out wild number, as xboard names are not always valid on ICS
5031             for(i=1; i<=36; i++) {
5032               snprintf(buf, MSG_SIZ, "wild/%d", i);
5033                 if(StringToVariant(buf) == gameInfo.variant) break;
5034             }
5035             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5036             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5037             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5038         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5039         SendToICS(ics_prefix);
5040         SendToICS(buf);
5041         if(startedFromSetupPosition || backwardMostMove != 0) {
5042           fen = PositionToFEN(backwardMostMove, NULL);
5043           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5044             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5045             SendToICS(buf);
5046           } else { // FICS: everything has to set by separate bsetup commands
5047             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5048             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5049             SendToICS(buf);
5050             if(!WhiteOnMove(backwardMostMove)) {
5051                 SendToICS("bsetup tomove black\n");
5052             }
5053             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5054             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5055             SendToICS(buf);
5056             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5057             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5058             SendToICS(buf);
5059             i = boards[backwardMostMove][EP_STATUS];
5060             if(i >= 0) { // set e.p.
5061               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5062                 SendToICS(buf);
5063             }
5064             bsetup++;
5065           }
5066         }
5067       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5068             SendToICS("bsetup done\n"); // switch to normal examining.
5069     }
5070     for(i = backwardMostMove; i<last; i++) {
5071         char buf[20];
5072         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5073         SendToICS(buf);
5074     }
5075     SendToICS(ics_prefix);
5076     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5077 }
5078
5079 void
5080 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5081      int rf, ff, rt, ft;
5082      char promoChar;
5083      char move[7];
5084 {
5085     if (rf == DROP_RANK) {
5086       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5087       sprintf(move, "%c@%c%c\n",
5088                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5089     } else {
5090         if (promoChar == 'x' || promoChar == NULLCHAR) {
5091           sprintf(move, "%c%c%c%c\n",
5092                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5093         } else {
5094             sprintf(move, "%c%c%c%c%c\n",
5095                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5096         }
5097     }
5098 }
5099
5100 void
5101 ProcessICSInitScript(f)
5102      FILE *f;
5103 {
5104     char buf[MSG_SIZ];
5105
5106     while (fgets(buf, MSG_SIZ, f)) {
5107         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5108     }
5109
5110     fclose(f);
5111 }
5112
5113
5114 static int lastX, lastY, selectFlag, dragging;
5115
5116 void
5117 Sweep(int step)
5118 {
5119     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5120     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5121     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5122     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5123     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5124     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5125     do {
5126         promoSweep -= step;
5127         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5128         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5129         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5130         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5131         if(!step) step = 1;
5132     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5133             appData.testLegality && (promoSweep == king ||
5134             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5135     ChangeDragPiece(promoSweep);
5136 }
5137
5138 int PromoScroll(int x, int y)
5139 {
5140   int step = 0;
5141
5142   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5143   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5144   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5145   if(!step) return FALSE;
5146   lastX = x; lastY = y;
5147   if((promoSweep < BlackPawn) == flipView) step = -step;
5148   if(step > 0) selectFlag = 1;
5149   if(!selectFlag) Sweep(step);
5150   return FALSE;
5151 }
5152
5153 void
5154 NextPiece(int step)
5155 {
5156     ChessSquare piece = boards[currentMove][toY][toX];
5157     do {
5158         pieceSweep -= step;
5159         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5160         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5161         if(!step) step = -1;
5162     } while(PieceToChar(pieceSweep) == '.');
5163     boards[currentMove][toY][toX] = pieceSweep;
5164     DrawPosition(FALSE, boards[currentMove]);
5165     boards[currentMove][toY][toX] = piece;
5166 }
5167 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5168 void
5169 AlphaRank(char *move, int n)
5170 {
5171 //    char *p = move, c; int x, y;
5172
5173     if (appData.debugMode) {
5174         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5175     }
5176
5177     if(move[1]=='*' &&
5178        move[2]>='0' && move[2]<='9' &&
5179        move[3]>='a' && move[3]<='x'    ) {
5180         move[1] = '@';
5181         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5182         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5183     } else
5184     if(move[0]>='0' && move[0]<='9' &&
5185        move[1]>='a' && move[1]<='x' &&
5186        move[2]>='0' && move[2]<='9' &&
5187        move[3]>='a' && move[3]<='x'    ) {
5188         /* input move, Shogi -> normal */
5189         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5190         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5191         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5192         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5193     } else
5194     if(move[1]=='@' &&
5195        move[3]>='0' && move[3]<='9' &&
5196        move[2]>='a' && move[2]<='x'    ) {
5197         move[1] = '*';
5198         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5199         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5200     } else
5201     if(
5202        move[0]>='a' && move[0]<='x' &&
5203        move[3]>='0' && move[3]<='9' &&
5204        move[2]>='a' && move[2]<='x'    ) {
5205          /* output move, normal -> Shogi */
5206         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5207         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5208         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5209         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5210         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5211     }
5212     if (appData.debugMode) {
5213         fprintf(debugFP, "   out = '%s'\n", move);
5214     }
5215 }
5216
5217 char yy_textstr[8000];
5218
5219 /* Parser for moves from gnuchess, ICS, or user typein box */
5220 Boolean
5221 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5222      char *move;
5223      int moveNum;
5224      ChessMove *moveType;
5225      int *fromX, *fromY, *toX, *toY;
5226      char *promoChar;
5227 {
5228     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5229
5230     switch (*moveType) {
5231       case WhitePromotion:
5232       case BlackPromotion:
5233       case WhiteNonPromotion:
5234       case BlackNonPromotion:
5235       case NormalMove:
5236       case WhiteCapturesEnPassant:
5237       case BlackCapturesEnPassant:
5238       case WhiteKingSideCastle:
5239       case WhiteQueenSideCastle:
5240       case BlackKingSideCastle:
5241       case BlackQueenSideCastle:
5242       case WhiteKingSideCastleWild:
5243       case WhiteQueenSideCastleWild:
5244       case BlackKingSideCastleWild:
5245       case BlackQueenSideCastleWild:
5246       /* Code added by Tord: */
5247       case WhiteHSideCastleFR:
5248       case WhiteASideCastleFR:
5249       case BlackHSideCastleFR:
5250       case BlackASideCastleFR:
5251       /* End of code added by Tord */
5252       case IllegalMove:         /* bug or odd chess variant */
5253         *fromX = currentMoveString[0] - AAA;
5254         *fromY = currentMoveString[1] - ONE;
5255         *toX = currentMoveString[2] - AAA;
5256         *toY = currentMoveString[3] - ONE;
5257         *promoChar = currentMoveString[4];
5258         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5259             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5260     if (appData.debugMode) {
5261         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5262     }
5263             *fromX = *fromY = *toX = *toY = 0;
5264             return FALSE;
5265         }
5266         if (appData.testLegality) {
5267           return (*moveType != IllegalMove);
5268         } else {
5269           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5270                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5271         }
5272
5273       case WhiteDrop:
5274       case BlackDrop:
5275         *fromX = *moveType == WhiteDrop ?
5276           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5277           (int) CharToPiece(ToLower(currentMoveString[0]));
5278         *fromY = DROP_RANK;
5279         *toX = currentMoveString[2] - AAA;
5280         *toY = currentMoveString[3] - ONE;
5281         *promoChar = NULLCHAR;
5282         return TRUE;
5283
5284       case AmbiguousMove:
5285       case ImpossibleMove:
5286       case EndOfFile:
5287       case ElapsedTime:
5288       case Comment:
5289       case PGNTag:
5290       case NAG:
5291       case WhiteWins:
5292       case BlackWins:
5293       case GameIsDrawn:
5294       default:
5295     if (appData.debugMode) {
5296         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5297     }
5298         /* bug? */
5299         *fromX = *fromY = *toX = *toY = 0;
5300         *promoChar = NULLCHAR;
5301         return FALSE;
5302     }
5303 }
5304
5305 Boolean pushed = FALSE;
5306 char *lastParseAttempt;
5307
5308 void
5309 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5310 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5311   int fromX, fromY, toX, toY; char promoChar;
5312   ChessMove moveType;
5313   Boolean valid;
5314   int nr = 0;
5315
5316   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5317     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5318     pushed = TRUE;
5319   }
5320   endPV = forwardMostMove;
5321   do {
5322     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5323     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5324     lastParseAttempt = pv;
5325     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5326 if(appData.debugMode){
5327 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5328 }
5329     if(!valid && nr == 0 &&
5330        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5331         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5332         // Hande case where played move is different from leading PV move
5333         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5334         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5335         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5336         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5337           endPV += 2; // if position different, keep this
5338           moveList[endPV-1][0] = fromX + AAA;
5339           moveList[endPV-1][1] = fromY + ONE;
5340           moveList[endPV-1][2] = toX + AAA;
5341           moveList[endPV-1][3] = toY + ONE;
5342           parseList[endPV-1][0] = NULLCHAR;
5343           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5344         }
5345       }
5346     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5347     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5348     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5349     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5350         valid++; // allow comments in PV
5351         continue;
5352     }
5353     nr++;
5354     if(endPV+1 > framePtr) break; // no space, truncate
5355     if(!valid) break;
5356     endPV++;
5357     CopyBoard(boards[endPV], boards[endPV-1]);
5358     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5359     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5360     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5361     CoordsToAlgebraic(boards[endPV - 1],
5362                              PosFlags(endPV - 1),
5363                              fromY, fromX, toY, toX, promoChar,
5364                              parseList[endPV - 1]);
5365   } while(valid);
5366   if(atEnd == 2) return; // used hidden, for PV conversion
5367   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5368   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5369   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5370                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5371   DrawPosition(TRUE, boards[currentMove]);
5372 }
5373
5374 int
5375 MultiPV(ChessProgramState *cps)
5376 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5377         int i;
5378         for(i=0; i<cps->nrOptions; i++)
5379             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5380                 return i;
5381         return -1;
5382 }
5383
5384 Boolean
5385 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5386 {
5387         int startPV, multi, lineStart, origIndex = index;
5388         char *p, buf2[MSG_SIZ];
5389
5390         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5391         lastX = x; lastY = y;
5392         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5393         lineStart = startPV = index;
5394         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5395         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5396         index = startPV;
5397         do{ while(buf[index] && buf[index] != '\n') index++;
5398         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5399         buf[index] = 0;
5400         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5401                 int n = first.option[multi].value;
5402                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5403                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5404                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5405                 first.option[multi].value = n;
5406                 *start = *end = 0;
5407                 return FALSE;
5408         }
5409         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5410         *start = startPV; *end = index-1;
5411         return TRUE;
5412 }
5413
5414 char *
5415 PvToSAN(char *pv)
5416 {
5417         static char buf[10*MSG_SIZ];
5418         int i, k=0, savedEnd=endPV;
5419         *buf = NULLCHAR;
5420         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5421         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5422         for(i = forwardMostMove; i<endPV; i++){
5423             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5424             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5425             k += strlen(buf+k);
5426         }
5427         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5428         if(forwardMostMove < savedEnd) PopInner(0);
5429         endPV = savedEnd;
5430         return buf;
5431 }
5432
5433 Boolean
5434 LoadPV(int x, int y)
5435 { // called on right mouse click to load PV
5436   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5437   lastX = x; lastY = y;
5438   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5439   return TRUE;
5440 }
5441
5442 void
5443 UnLoadPV()
5444 {
5445   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5446   if(endPV < 0) return;
5447   endPV = -1;
5448   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5449         Boolean saveAnimate = appData.animate;
5450         if(pushed) {
5451             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5452                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5453             } else storedGames--; // abandon shelved tail of original game
5454         }
5455         pushed = FALSE;
5456         forwardMostMove = currentMove;
5457         currentMove = oldFMM;
5458         appData.animate = FALSE;
5459         ToNrEvent(forwardMostMove);
5460         appData.animate = saveAnimate;
5461   }
5462   currentMove = forwardMostMove;
5463   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5464   ClearPremoveHighlights();
5465   DrawPosition(TRUE, boards[currentMove]);
5466 }
5467
5468 void
5469 MovePV(int x, int y, int h)
5470 { // step through PV based on mouse coordinates (called on mouse move)
5471   int margin = h>>3, step = 0;
5472
5473   // we must somehow check if right button is still down (might be released off board!)
5474   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5475   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5476   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5477   if(!step) return;
5478   lastX = x; lastY = y;
5479
5480   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5481   if(endPV < 0) return;
5482   if(y < margin) step = 1; else
5483   if(y > h - margin) step = -1;
5484   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5485   currentMove += step;
5486   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5487   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5488                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5489   DrawPosition(FALSE, boards[currentMove]);
5490 }
5491
5492
5493 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5494 // All positions will have equal probability, but the current method will not provide a unique
5495 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5496 #define DARK 1
5497 #define LITE 2
5498 #define ANY 3
5499
5500 int squaresLeft[4];
5501 int piecesLeft[(int)BlackPawn];
5502 int seed, nrOfShuffles;
5503
5504 void GetPositionNumber()
5505 {       // sets global variable seed
5506         int i;
5507
5508         seed = appData.defaultFrcPosition;
5509         if(seed < 0) { // randomize based on time for negative FRC position numbers
5510                 for(i=0; i<50; i++) seed += random();
5511                 seed = random() ^ random() >> 8 ^ random() << 8;
5512                 if(seed<0) seed = -seed;
5513         }
5514 }
5515
5516 int put(Board board, int pieceType, int rank, int n, int shade)
5517 // put the piece on the (n-1)-th empty squares of the given shade
5518 {
5519         int i;
5520
5521         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5522                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5523                         board[rank][i] = (ChessSquare) pieceType;
5524                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5525                         squaresLeft[ANY]--;
5526                         piecesLeft[pieceType]--;
5527                         return i;
5528                 }
5529         }
5530         return -1;
5531 }
5532
5533
5534 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5535 // calculate where the next piece goes, (any empty square), and put it there
5536 {
5537         int i;
5538
5539         i = seed % squaresLeft[shade];
5540         nrOfShuffles *= squaresLeft[shade];
5541         seed /= squaresLeft[shade];
5542         put(board, pieceType, rank, i, shade);
5543 }
5544
5545 void AddTwoPieces(Board board, int pieceType, int rank)
5546 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5547 {
5548         int i, n=squaresLeft[ANY], j=n-1, k;
5549
5550         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5551         i = seed % k;  // pick one
5552         nrOfShuffles *= k;
5553         seed /= k;
5554         while(i >= j) i -= j--;
5555         j = n - 1 - j; i += j;
5556         put(board, pieceType, rank, j, ANY);
5557         put(board, pieceType, rank, i, ANY);
5558 }
5559
5560 void SetUpShuffle(Board board, int number)
5561 {
5562         int i, p, first=1;
5563
5564         GetPositionNumber(); nrOfShuffles = 1;
5565
5566         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5567         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5568         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5569
5570         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5571
5572         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5573             p = (int) board[0][i];
5574             if(p < (int) BlackPawn) piecesLeft[p] ++;
5575             board[0][i] = EmptySquare;
5576         }
5577
5578         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5579             // shuffles restricted to allow normal castling put KRR first
5580             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5581                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5582             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5583                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5584             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5585                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5586             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5587                 put(board, WhiteRook, 0, 0, ANY);
5588             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5589         }
5590
5591         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5592             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5593             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5594                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5595                 while(piecesLeft[p] >= 2) {
5596                     AddOnePiece(board, p, 0, LITE);
5597                     AddOnePiece(board, p, 0, DARK);
5598                 }
5599                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5600             }
5601
5602         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5603             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5604             // but we leave King and Rooks for last, to possibly obey FRC restriction
5605             if(p == (int)WhiteRook) continue;
5606             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5607             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5608         }
5609
5610         // now everything is placed, except perhaps King (Unicorn) and Rooks
5611
5612         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5613             // Last King gets castling rights
5614             while(piecesLeft[(int)WhiteUnicorn]) {
5615                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5616                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5617             }
5618
5619             while(piecesLeft[(int)WhiteKing]) {
5620                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5621                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5622             }
5623
5624
5625         } else {
5626             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5627             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5628         }
5629
5630         // Only Rooks can be left; simply place them all
5631         while(piecesLeft[(int)WhiteRook]) {
5632                 i = put(board, WhiteRook, 0, 0, ANY);
5633                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5634                         if(first) {
5635                                 first=0;
5636                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5637                         }
5638                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5639                 }
5640         }
5641         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5642             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5643         }
5644
5645         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5646 }
5647
5648 int SetCharTable( char *table, const char * map )
5649 /* [HGM] moved here from winboard.c because of its general usefulness */
5650 /*       Basically a safe strcpy that uses the last character as King */
5651 {
5652     int result = FALSE; int NrPieces;
5653
5654     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5655                     && NrPieces >= 12 && !(NrPieces&1)) {
5656         int i; /* [HGM] Accept even length from 12 to 34 */
5657
5658         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5659         for( i=0; i<NrPieces/2-1; i++ ) {
5660             table[i] = map[i];
5661             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5662         }
5663         table[(int) WhiteKing]  = map[NrPieces/2-1];
5664         table[(int) BlackKing]  = map[NrPieces-1];
5665
5666         result = TRUE;
5667     }
5668
5669     return result;
5670 }
5671
5672 void Prelude(Board board)
5673 {       // [HGM] superchess: random selection of exo-pieces
5674         int i, j, k; ChessSquare p;
5675         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5676
5677         GetPositionNumber(); // use FRC position number
5678
5679         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5680             SetCharTable(pieceToChar, appData.pieceToCharTable);
5681             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5682                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5683         }
5684
5685         j = seed%4;                 seed /= 4;
5686         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5687         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5688         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5689         j = seed%3 + (seed%3 >= j); seed /= 3;
5690         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5691         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5692         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5693         j = seed%3;                 seed /= 3;
5694         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5695         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5696         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5697         j = seed%2 + (seed%2 >= j); seed /= 2;
5698         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5699         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5700         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5701         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5702         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5703         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5704         put(board, exoPieces[0],    0, 0, ANY);
5705         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5706 }
5707
5708 void
5709 InitPosition(redraw)
5710      int redraw;
5711 {
5712     ChessSquare (* pieces)[BOARD_FILES];
5713     int i, j, pawnRow, overrule,
5714     oldx = gameInfo.boardWidth,
5715     oldy = gameInfo.boardHeight,
5716     oldh = gameInfo.holdingsWidth;
5717     static int oldv;
5718
5719     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5720
5721     /* [AS] Initialize pv info list [HGM] and game status */
5722     {
5723         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5724             pvInfoList[i].depth = 0;
5725             boards[i][EP_STATUS] = EP_NONE;
5726             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5727         }
5728
5729         initialRulePlies = 0; /* 50-move counter start */
5730
5731         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5732         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5733     }
5734
5735
5736     /* [HGM] logic here is completely changed. In stead of full positions */
5737     /* the initialized data only consist of the two backranks. The switch */
5738     /* selects which one we will use, which is than copied to the Board   */
5739     /* initialPosition, which for the rest is initialized by Pawns and    */
5740     /* empty squares. This initial position is then copied to boards[0],  */
5741     /* possibly after shuffling, so that it remains available.            */
5742
5743     gameInfo.holdingsWidth = 0; /* default board sizes */
5744     gameInfo.boardWidth    = 8;
5745     gameInfo.boardHeight   = 8;
5746     gameInfo.holdingsSize  = 0;
5747     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5748     for(i=0; i<BOARD_FILES-2; i++)
5749       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5750     initialPosition[EP_STATUS] = EP_NONE;
5751     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5752     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5753          SetCharTable(pieceNickName, appData.pieceNickNames);
5754     else SetCharTable(pieceNickName, "............");
5755     pieces = FIDEArray;
5756
5757     switch (gameInfo.variant) {
5758     case VariantFischeRandom:
5759       shuffleOpenings = TRUE;
5760     default:
5761       break;
5762     case VariantShatranj:
5763       pieces = ShatranjArray;
5764       nrCastlingRights = 0;
5765       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5766       break;
5767     case VariantMakruk:
5768       pieces = makrukArray;
5769       nrCastlingRights = 0;
5770       startedFromSetupPosition = TRUE;
5771       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5772       break;
5773     case VariantTwoKings:
5774       pieces = twoKingsArray;
5775       break;
5776     case VariantGrand:
5777       pieces = GrandArray;
5778       nrCastlingRights = 0;
5779       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5780       gameInfo.boardWidth = 10;
5781       gameInfo.boardHeight = 10;
5782       gameInfo.holdingsSize = 7;
5783       break;
5784     case VariantCapaRandom:
5785       shuffleOpenings = TRUE;
5786     case VariantCapablanca:
5787       pieces = CapablancaArray;
5788       gameInfo.boardWidth = 10;
5789       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5790       break;
5791     case VariantGothic:
5792       pieces = GothicArray;
5793       gameInfo.boardWidth = 10;
5794       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5795       break;
5796     case VariantSChess:
5797       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5798       gameInfo.holdingsSize = 7;
5799       break;
5800     case VariantJanus:
5801       pieces = JanusArray;
5802       gameInfo.boardWidth = 10;
5803       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5804       nrCastlingRights = 6;
5805         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5806         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5807         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5808         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5809         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5810         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5811       break;
5812     case VariantFalcon:
5813       pieces = FalconArray;
5814       gameInfo.boardWidth = 10;
5815       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5816       break;
5817     case VariantXiangqi:
5818       pieces = XiangqiArray;
5819       gameInfo.boardWidth  = 9;
5820       gameInfo.boardHeight = 10;
5821       nrCastlingRights = 0;
5822       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5823       break;
5824     case VariantShogi:
5825       pieces = ShogiArray;
5826       gameInfo.boardWidth  = 9;
5827       gameInfo.boardHeight = 9;
5828       gameInfo.holdingsSize = 7;
5829       nrCastlingRights = 0;
5830       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5831       break;
5832     case VariantCourier:
5833       pieces = CourierArray;
5834       gameInfo.boardWidth  = 12;
5835       nrCastlingRights = 0;
5836       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5837       break;
5838     case VariantKnightmate:
5839       pieces = KnightmateArray;
5840       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5841       break;
5842     case VariantSpartan:
5843       pieces = SpartanArray;
5844       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5845       break;
5846     case VariantFairy:
5847       pieces = fairyArray;
5848       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5849       break;
5850     case VariantGreat:
5851       pieces = GreatArray;
5852       gameInfo.boardWidth = 10;
5853       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5854       gameInfo.holdingsSize = 8;
5855       break;
5856     case VariantSuper:
5857       pieces = FIDEArray;
5858       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5859       gameInfo.holdingsSize = 8;
5860       startedFromSetupPosition = TRUE;
5861       break;
5862     case VariantCrazyhouse:
5863     case VariantBughouse:
5864       pieces = FIDEArray;
5865       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5866       gameInfo.holdingsSize = 5;
5867       break;
5868     case VariantWildCastle:
5869       pieces = FIDEArray;
5870       /* !!?shuffle with kings guaranteed to be on d or e file */
5871       shuffleOpenings = 1;
5872       break;
5873     case VariantNoCastle:
5874       pieces = FIDEArray;
5875       nrCastlingRights = 0;
5876       /* !!?unconstrained back-rank shuffle */
5877       shuffleOpenings = 1;
5878       break;
5879     }
5880
5881     overrule = 0;
5882     if(appData.NrFiles >= 0) {
5883         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5884         gameInfo.boardWidth = appData.NrFiles;
5885     }
5886     if(appData.NrRanks >= 0) {
5887         gameInfo.boardHeight = appData.NrRanks;
5888     }
5889     if(appData.holdingsSize >= 0) {
5890         i = appData.holdingsSize;
5891         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5892         gameInfo.holdingsSize = i;
5893     }
5894     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5895     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5896         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5897
5898     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5899     if(pawnRow < 1) pawnRow = 1;
5900     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5901
5902     /* User pieceToChar list overrules defaults */
5903     if(appData.pieceToCharTable != NULL)
5904         SetCharTable(pieceToChar, appData.pieceToCharTable);
5905
5906     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5907
5908         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5909             s = (ChessSquare) 0; /* account holding counts in guard band */
5910         for( i=0; i<BOARD_HEIGHT; i++ )
5911             initialPosition[i][j] = s;
5912
5913         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5914         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5915         initialPosition[pawnRow][j] = WhitePawn;
5916         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5917         if(gameInfo.variant == VariantXiangqi) {
5918             if(j&1) {
5919                 initialPosition[pawnRow][j] =
5920                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5921                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5922                    initialPosition[2][j] = WhiteCannon;
5923                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5924                 }
5925             }
5926         }
5927         if(gameInfo.variant == VariantGrand) {
5928             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5929                initialPosition[0][j] = WhiteRook;
5930                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5931             }
5932         }
5933         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5934     }
5935     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5936
5937             j=BOARD_LEFT+1;
5938             initialPosition[1][j] = WhiteBishop;
5939             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5940             j=BOARD_RGHT-2;
5941             initialPosition[1][j] = WhiteRook;
5942             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5943     }
5944
5945     if( nrCastlingRights == -1) {
5946         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5947         /*       This sets default castling rights from none to normal corners   */
5948         /* Variants with other castling rights must set them themselves above    */
5949         nrCastlingRights = 6;
5950
5951         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5952         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5953         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5954         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5955         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5956         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5957      }
5958
5959      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5960      if(gameInfo.variant == VariantGreat) { // promotion commoners
5961         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5962         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5963         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5964         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5965      }
5966      if( gameInfo.variant == VariantSChess ) {
5967       initialPosition[1][0] = BlackMarshall;
5968       initialPosition[2][0] = BlackAngel;
5969       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5970       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5971       initialPosition[1][1] = initialPosition[2][1] = 
5972       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5973      }
5974   if (appData.debugMode) {
5975     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5976   }
5977     if(shuffleOpenings) {
5978         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5979         startedFromSetupPosition = TRUE;
5980     }
5981     if(startedFromPositionFile) {
5982       /* [HGM] loadPos: use PositionFile for every new game */
5983       CopyBoard(initialPosition, filePosition);
5984       for(i=0; i<nrCastlingRights; i++)
5985           initialRights[i] = filePosition[CASTLING][i];
5986       startedFromSetupPosition = TRUE;
5987     }
5988
5989     CopyBoard(boards[0], initialPosition);
5990
5991     if(oldx != gameInfo.boardWidth ||
5992        oldy != gameInfo.boardHeight ||
5993        oldv != gameInfo.variant ||
5994        oldh != gameInfo.holdingsWidth
5995                                          )
5996             InitDrawingSizes(-2 ,0);
5997
5998     oldv = gameInfo.variant;
5999     if (redraw)
6000       DrawPosition(TRUE, boards[currentMove]);
6001 }
6002
6003 void
6004 SendBoard(cps, moveNum)
6005      ChessProgramState *cps;
6006      int moveNum;
6007 {
6008     char message[MSG_SIZ];
6009
6010     if (cps->useSetboard) {
6011       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6012       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6013       SendToProgram(message, cps);
6014       free(fen);
6015
6016     } else {
6017       ChessSquare *bp;
6018       int i, j;
6019       /* Kludge to set black to move, avoiding the troublesome and now
6020        * deprecated "black" command.
6021        */
6022       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6023         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6024
6025       SendToProgram("edit\n", cps);
6026       SendToProgram("#\n", cps);
6027       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6028         bp = &boards[moveNum][i][BOARD_LEFT];
6029         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6030           if ((int) *bp < (int) BlackPawn) {
6031             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6032                     AAA + j, ONE + i);
6033             if(message[0] == '+' || message[0] == '~') {
6034               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6035                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6036                         AAA + j, ONE + i);
6037             }
6038             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6039                 message[1] = BOARD_RGHT   - 1 - j + '1';
6040                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6041             }
6042             SendToProgram(message, cps);
6043           }
6044         }
6045       }
6046
6047       SendToProgram("c\n", cps);
6048       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6049         bp = &boards[moveNum][i][BOARD_LEFT];
6050         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6051           if (((int) *bp != (int) EmptySquare)
6052               && ((int) *bp >= (int) BlackPawn)) {
6053             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6054                     AAA + j, ONE + i);
6055             if(message[0] == '+' || message[0] == '~') {
6056               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6057                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6058                         AAA + j, ONE + i);
6059             }
6060             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6061                 message[1] = BOARD_RGHT   - 1 - j + '1';
6062                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6063             }
6064             SendToProgram(message, cps);
6065           }
6066         }
6067       }
6068
6069       SendToProgram(".\n", cps);
6070     }
6071     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6072 }
6073
6074 ChessSquare
6075 DefaultPromoChoice(int white)
6076 {
6077     ChessSquare result;
6078     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6079         result = WhiteFerz; // no choice
6080     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6081         result= WhiteKing; // in Suicide Q is the last thing we want
6082     else if(gameInfo.variant == VariantSpartan)
6083         result = white ? WhiteQueen : WhiteAngel;
6084     else result = WhiteQueen;
6085     if(!white) result = WHITE_TO_BLACK result;
6086     return result;
6087 }
6088
6089 static int autoQueen; // [HGM] oneclick
6090
6091 int
6092 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6093 {
6094     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6095     /* [HGM] add Shogi promotions */
6096     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6097     ChessSquare piece;
6098     ChessMove moveType;
6099     Boolean premove;
6100
6101     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6102     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6103
6104     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6105       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6106         return FALSE;
6107
6108     piece = boards[currentMove][fromY][fromX];
6109     if(gameInfo.variant == VariantShogi) {
6110         promotionZoneSize = BOARD_HEIGHT/3;
6111         highestPromotingPiece = (int)WhiteFerz;
6112     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6113         promotionZoneSize = 3;
6114     }
6115
6116     // Treat Lance as Pawn when it is not representing Amazon
6117     if(gameInfo.variant != VariantSuper) {
6118         if(piece == WhiteLance) piece = WhitePawn; else
6119         if(piece == BlackLance) piece = BlackPawn;
6120     }
6121
6122     // next weed out all moves that do not touch the promotion zone at all
6123     if((int)piece >= BlackPawn) {
6124         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6125              return FALSE;
6126         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6127     } else {
6128         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6129            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6130     }
6131
6132     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6133
6134     // weed out mandatory Shogi promotions
6135     if(gameInfo.variant == VariantShogi) {
6136         if(piece >= BlackPawn) {
6137             if(toY == 0 && piece == BlackPawn ||
6138                toY == 0 && piece == BlackQueen ||
6139                toY <= 1 && piece == BlackKnight) {
6140                 *promoChoice = '+';
6141                 return FALSE;
6142             }
6143         } else {
6144             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6145                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6146                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6147                 *promoChoice = '+';
6148                 return FALSE;
6149             }
6150         }
6151     }
6152
6153     // weed out obviously illegal Pawn moves
6154     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6155         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6156         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6157         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6158         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6159         // note we are not allowed to test for valid (non-)capture, due to premove
6160     }
6161
6162     // we either have a choice what to promote to, or (in Shogi) whether to promote
6163     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6164         *promoChoice = PieceToChar(BlackFerz);  // no choice
6165         return FALSE;
6166     }
6167     // no sense asking what we must promote to if it is going to explode...
6168     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6169         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6170         return FALSE;
6171     }
6172     // give caller the default choice even if we will not make it
6173     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6174     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6175     if(        sweepSelect && gameInfo.variant != VariantGreat
6176                            && gameInfo.variant != VariantGrand
6177                            && gameInfo.variant != VariantSuper) return FALSE;
6178     if(autoQueen) return FALSE; // predetermined
6179
6180     // suppress promotion popup on illegal moves that are not premoves
6181     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6182               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6183     if(appData.testLegality && !premove) {
6184         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6185                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6186         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6187             return FALSE;
6188     }
6189
6190     return TRUE;
6191 }
6192
6193 int
6194 InPalace(row, column)
6195      int row, column;
6196 {   /* [HGM] for Xiangqi */
6197     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6198          column < (BOARD_WIDTH + 4)/2 &&
6199          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6200     return FALSE;
6201 }
6202
6203 int
6204 PieceForSquare (x, y)
6205      int x;
6206      int y;
6207 {
6208   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6209      return -1;
6210   else
6211      return boards[currentMove][y][x];
6212 }
6213
6214 int
6215 OKToStartUserMove(x, y)
6216      int x, y;
6217 {
6218     ChessSquare from_piece;
6219     int white_piece;
6220
6221     if (matchMode) return FALSE;
6222     if (gameMode == EditPosition) return TRUE;
6223
6224     if (x >= 0 && y >= 0)
6225       from_piece = boards[currentMove][y][x];
6226     else
6227       from_piece = EmptySquare;
6228
6229     if (from_piece == EmptySquare) return FALSE;
6230
6231     white_piece = (int)from_piece >= (int)WhitePawn &&
6232       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6233
6234     switch (gameMode) {
6235       case AnalyzeFile:
6236       case TwoMachinesPlay:
6237       case EndOfGame:
6238         return FALSE;
6239
6240       case IcsObserving:
6241       case IcsIdle:
6242         return FALSE;
6243
6244       case MachinePlaysWhite:
6245       case IcsPlayingBlack:
6246         if (appData.zippyPlay) return FALSE;
6247         if (white_piece) {
6248             DisplayMoveError(_("You are playing Black"));
6249             return FALSE;
6250         }
6251         break;
6252
6253       case MachinePlaysBlack:
6254       case IcsPlayingWhite:
6255         if (appData.zippyPlay) return FALSE;
6256         if (!white_piece) {
6257             DisplayMoveError(_("You are playing White"));
6258             return FALSE;
6259         }
6260         break;
6261
6262       case PlayFromGameFile:
6263             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6264       case EditGame:
6265         if (!white_piece && WhiteOnMove(currentMove)) {
6266             DisplayMoveError(_("It is White's turn"));
6267             return FALSE;
6268         }
6269         if (white_piece && !WhiteOnMove(currentMove)) {
6270             DisplayMoveError(_("It is Black's turn"));
6271             return FALSE;
6272         }
6273         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6274             /* Editing correspondence game history */
6275             /* Could disallow this or prompt for confirmation */
6276             cmailOldMove = -1;
6277         }
6278         break;
6279
6280       case BeginningOfGame:
6281         if (appData.icsActive) return FALSE;
6282         if (!appData.noChessProgram) {
6283             if (!white_piece) {
6284                 DisplayMoveError(_("You are playing White"));
6285                 return FALSE;
6286             }
6287         }
6288         break;
6289
6290       case Training:
6291         if (!white_piece && WhiteOnMove(currentMove)) {
6292             DisplayMoveError(_("It is White's turn"));
6293             return FALSE;
6294         }
6295         if (white_piece && !WhiteOnMove(currentMove)) {
6296             DisplayMoveError(_("It is Black's turn"));
6297             return FALSE;
6298         }
6299         break;
6300
6301       default:
6302       case IcsExamining:
6303         break;
6304     }
6305     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6306         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6307         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6308         && gameMode != AnalyzeFile && gameMode != Training) {
6309         DisplayMoveError(_("Displayed position is not current"));
6310         return FALSE;
6311     }
6312     return TRUE;
6313 }
6314
6315 Boolean
6316 OnlyMove(int *x, int *y, Boolean captures) {
6317     DisambiguateClosure cl;
6318     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6319     switch(gameMode) {
6320       case MachinePlaysBlack:
6321       case IcsPlayingWhite:
6322       case BeginningOfGame:
6323         if(!WhiteOnMove(currentMove)) return FALSE;
6324         break;
6325       case MachinePlaysWhite:
6326       case IcsPlayingBlack:
6327         if(WhiteOnMove(currentMove)) return FALSE;
6328         break;
6329       case EditGame:
6330         break;
6331       default:
6332         return FALSE;
6333     }
6334     cl.pieceIn = EmptySquare;
6335     cl.rfIn = *y;
6336     cl.ffIn = *x;
6337     cl.rtIn = -1;
6338     cl.ftIn = -1;
6339     cl.promoCharIn = NULLCHAR;
6340     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6341     if( cl.kind == NormalMove ||
6342         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6343         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6344         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6345       fromX = cl.ff;
6346       fromY = cl.rf;
6347       *x = cl.ft;
6348       *y = cl.rt;
6349       return TRUE;
6350     }
6351     if(cl.kind != ImpossibleMove) return FALSE;
6352     cl.pieceIn = EmptySquare;
6353     cl.rfIn = -1;
6354     cl.ffIn = -1;
6355     cl.rtIn = *y;
6356     cl.ftIn = *x;
6357     cl.promoCharIn = NULLCHAR;
6358     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6359     if( cl.kind == NormalMove ||
6360         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6361         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6362         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6363       fromX = cl.ff;
6364       fromY = cl.rf;
6365       *x = cl.ft;
6366       *y = cl.rt;
6367       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6368       return TRUE;
6369     }
6370     return FALSE;
6371 }
6372
6373 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6374 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6375 int lastLoadGameUseList = FALSE;
6376 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6377 ChessMove lastLoadGameStart = EndOfFile;
6378
6379 void
6380 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6381      int fromX, fromY, toX, toY;
6382      int promoChar;
6383 {
6384     ChessMove moveType;
6385     ChessSquare pdown, pup;
6386
6387     /* Check if the user is playing in turn.  This is complicated because we
6388        let the user "pick up" a piece before it is his turn.  So the piece he
6389        tried to pick up may have been captured by the time he puts it down!
6390        Therefore we use the color the user is supposed to be playing in this
6391        test, not the color of the piece that is currently on the starting
6392        square---except in EditGame mode, where the user is playing both
6393        sides; fortunately there the capture race can't happen.  (It can
6394        now happen in IcsExamining mode, but that's just too bad.  The user
6395        will get a somewhat confusing message in that case.)
6396        */
6397
6398     switch (gameMode) {
6399       case AnalyzeFile:
6400       case TwoMachinesPlay:
6401       case EndOfGame:
6402       case IcsObserving:
6403       case IcsIdle:
6404         /* We switched into a game mode where moves are not accepted,
6405            perhaps while the mouse button was down. */
6406         return;
6407
6408       case MachinePlaysWhite:
6409         /* User is moving for Black */
6410         if (WhiteOnMove(currentMove)) {
6411             DisplayMoveError(_("It is White's turn"));
6412             return;
6413         }
6414         break;
6415
6416       case MachinePlaysBlack:
6417         /* User is moving for White */
6418         if (!WhiteOnMove(currentMove)) {
6419             DisplayMoveError(_("It is Black's turn"));
6420             return;
6421         }
6422         break;
6423
6424       case PlayFromGameFile:
6425             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6426       case EditGame:
6427       case IcsExamining:
6428       case BeginningOfGame:
6429       case AnalyzeMode:
6430       case Training:
6431         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6432         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6433             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6434             /* User is moving for Black */
6435             if (WhiteOnMove(currentMove)) {
6436                 DisplayMoveError(_("It is White's turn"));
6437                 return;
6438             }
6439         } else {
6440             /* User is moving for White */
6441             if (!WhiteOnMove(currentMove)) {
6442                 DisplayMoveError(_("It is Black's turn"));
6443                 return;
6444             }
6445         }
6446         break;
6447
6448       case IcsPlayingBlack:
6449         /* User is moving for Black */
6450         if (WhiteOnMove(currentMove)) {
6451             if (!appData.premove) {
6452                 DisplayMoveError(_("It is White's turn"));
6453             } else if (toX >= 0 && toY >= 0) {
6454                 premoveToX = toX;
6455                 premoveToY = toY;
6456                 premoveFromX = fromX;
6457                 premoveFromY = fromY;
6458                 premovePromoChar = promoChar;
6459                 gotPremove = 1;
6460                 if (appData.debugMode)
6461                     fprintf(debugFP, "Got premove: fromX %d,"
6462                             "fromY %d, toX %d, toY %d\n",
6463                             fromX, fromY, toX, toY);
6464             }
6465             return;
6466         }
6467         break;
6468
6469       case IcsPlayingWhite:
6470         /* User is moving for White */
6471         if (!WhiteOnMove(currentMove)) {
6472             if (!appData.premove) {
6473                 DisplayMoveError(_("It is Black's turn"));
6474             } else if (toX >= 0 && toY >= 0) {
6475                 premoveToX = toX;
6476                 premoveToY = toY;
6477                 premoveFromX = fromX;
6478                 premoveFromY = fromY;
6479                 premovePromoChar = promoChar;
6480                 gotPremove = 1;
6481                 if (appData.debugMode)
6482                     fprintf(debugFP, "Got premove: fromX %d,"
6483                             "fromY %d, toX %d, toY %d\n",
6484                             fromX, fromY, toX, toY);
6485             }
6486             return;
6487         }
6488         break;
6489
6490       default:
6491         break;
6492
6493       case EditPosition:
6494         /* EditPosition, empty square, or different color piece;
6495            click-click move is possible */
6496         if (toX == -2 || toY == -2) {
6497             boards[0][fromY][fromX] = EmptySquare;
6498             DrawPosition(FALSE, boards[currentMove]);
6499             return;
6500         } else if (toX >= 0 && toY >= 0) {
6501             boards[0][toY][toX] = boards[0][fromY][fromX];
6502             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6503                 if(boards[0][fromY][0] != EmptySquare) {
6504                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6505                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6506                 }
6507             } else
6508             if(fromX == BOARD_RGHT+1) {
6509                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6510                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6511                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6512                 }
6513             } else
6514             boards[0][fromY][fromX] = EmptySquare;
6515             DrawPosition(FALSE, boards[currentMove]);
6516             return;
6517         }
6518         return;
6519     }
6520
6521     if(toX < 0 || toY < 0) return;
6522     pdown = boards[currentMove][fromY][fromX];
6523     pup = boards[currentMove][toY][toX];
6524
6525     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6526     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6527          if( pup != EmptySquare ) return;
6528          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6529            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6530                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6531            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6532            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6533            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6534            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6535          fromY = DROP_RANK;
6536     }
6537
6538     /* [HGM] always test for legality, to get promotion info */
6539     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6540                                          fromY, fromX, toY, toX, promoChar);
6541
6542     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6543
6544     /* [HGM] but possibly ignore an IllegalMove result */
6545     if (appData.testLegality) {
6546         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6547             DisplayMoveError(_("Illegal move"));
6548             return;
6549         }
6550     }
6551
6552     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6553 }
6554
6555 /* Common tail of UserMoveEvent and DropMenuEvent */
6556 int
6557 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6558      ChessMove moveType;
6559      int fromX, fromY, toX, toY;
6560      /*char*/int promoChar;
6561 {
6562     char *bookHit = 0;
6563
6564     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6565         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6566         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6567         if(WhiteOnMove(currentMove)) {
6568             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6569         } else {
6570             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6571         }
6572     }
6573
6574     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6575        move type in caller when we know the move is a legal promotion */
6576     if(moveType == NormalMove && promoChar)
6577         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6578
6579     /* [HGM] <popupFix> The following if has been moved here from
6580        UserMoveEvent(). Because it seemed to belong here (why not allow
6581        piece drops in training games?), and because it can only be
6582        performed after it is known to what we promote. */
6583     if (gameMode == Training) {
6584       /* compare the move played on the board to the next move in the
6585        * game. If they match, display the move and the opponent's response.
6586        * If they don't match, display an error message.
6587        */
6588       int saveAnimate;
6589       Board testBoard;
6590       CopyBoard(testBoard, boards[currentMove]);
6591       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6592
6593       if (CompareBoards(testBoard, boards[currentMove+1])) {
6594         ForwardInner(currentMove+1);
6595
6596         /* Autoplay the opponent's response.
6597          * if appData.animate was TRUE when Training mode was entered,
6598          * the response will be animated.
6599          */
6600         saveAnimate = appData.animate;
6601         appData.animate = animateTraining;
6602         ForwardInner(currentMove+1);
6603         appData.animate = saveAnimate;
6604
6605         /* check for the end of the game */
6606         if (currentMove >= forwardMostMove) {
6607           gameMode = PlayFromGameFile;
6608           ModeHighlight();
6609           SetTrainingModeOff();
6610           DisplayInformation(_("End of game"));
6611         }
6612       } else {
6613         DisplayError(_("Incorrect move"), 0);
6614       }
6615       return 1;
6616     }
6617
6618   /* Ok, now we know that the move is good, so we can kill
6619      the previous line in Analysis Mode */
6620   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6621                                 && currentMove < forwardMostMove) {
6622     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6623     else forwardMostMove = currentMove;
6624   }
6625
6626   /* If we need the chess program but it's dead, restart it */
6627   ResurrectChessProgram();
6628
6629   /* A user move restarts a paused game*/
6630   if (pausing)
6631     PauseEvent();
6632
6633   thinkOutput[0] = NULLCHAR;
6634
6635   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6636
6637   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6638     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6639     return 1;
6640   }
6641
6642   if (gameMode == BeginningOfGame) {
6643     if (appData.noChessProgram) {
6644       gameMode = EditGame;
6645       SetGameInfo();
6646     } else {
6647       char buf[MSG_SIZ];
6648       gameMode = MachinePlaysBlack;
6649       StartClocks();
6650       SetGameInfo();
6651       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6652       DisplayTitle(buf);
6653       if (first.sendName) {
6654         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6655         SendToProgram(buf, &first);
6656       }
6657       StartClocks();
6658     }
6659     ModeHighlight();
6660   }
6661
6662   /* Relay move to ICS or chess engine */
6663   if (appData.icsActive) {
6664     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6665         gameMode == IcsExamining) {
6666       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6667         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6668         SendToICS("draw ");
6669         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6670       }
6671       // also send plain move, in case ICS does not understand atomic claims
6672       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6673       ics_user_moved = 1;
6674     }
6675   } else {
6676     if (first.sendTime && (gameMode == BeginningOfGame ||
6677                            gameMode == MachinePlaysWhite ||
6678                            gameMode == MachinePlaysBlack)) {
6679       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6680     }
6681     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6682          // [HGM] book: if program might be playing, let it use book
6683         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6684         first.maybeThinking = TRUE;
6685     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6686         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6687         SendBoard(&first, currentMove+1);
6688     } else SendMoveToProgram(forwardMostMove-1, &first);
6689     if (currentMove == cmailOldMove + 1) {
6690       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6691     }
6692   }
6693
6694   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6695
6696   switch (gameMode) {
6697   case EditGame:
6698     if(appData.testLegality)
6699     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6700     case MT_NONE:
6701     case MT_CHECK:
6702       break;
6703     case MT_CHECKMATE:
6704     case MT_STAINMATE:
6705       if (WhiteOnMove(currentMove)) {
6706         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6707       } else {
6708         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6709       }
6710       break;
6711     case MT_STALEMATE:
6712       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6713       break;
6714     }
6715     break;
6716
6717   case MachinePlaysBlack:
6718   case MachinePlaysWhite:
6719     /* disable certain menu options while machine is thinking */
6720     SetMachineThinkingEnables();
6721     break;
6722
6723   default:
6724     break;
6725   }
6726
6727   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6728   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6729
6730   if(bookHit) { // [HGM] book: simulate book reply
6731         static char bookMove[MSG_SIZ]; // a bit generous?
6732
6733         programStats.nodes = programStats.depth = programStats.time =
6734         programStats.score = programStats.got_only_move = 0;
6735         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6736
6737         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6738         strcat(bookMove, bookHit);
6739         HandleMachineMove(bookMove, &first);
6740   }
6741   return 1;
6742 }
6743
6744 void
6745 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6746      Board board;
6747      int flags;
6748      ChessMove kind;
6749      int rf, ff, rt, ft;
6750      VOIDSTAR closure;
6751 {
6752     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6753     Markers *m = (Markers *) closure;
6754     if(rf == fromY && ff == fromX)
6755         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6756                          || kind == WhiteCapturesEnPassant
6757                          || kind == BlackCapturesEnPassant);
6758     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6759 }
6760
6761 void
6762 MarkTargetSquares(int clear)
6763 {
6764   int x, y;
6765   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6766      !appData.testLegality || gameMode == EditPosition) return;
6767   if(clear) {
6768     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6769   } else {
6770     int capt = 0;
6771     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6772     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6773       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6774       if(capt)
6775       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6776     }
6777   }
6778   DrawPosition(TRUE, NULL);
6779 }
6780
6781 int
6782 Explode(Board board, int fromX, int fromY, int toX, int toY)
6783 {
6784     if(gameInfo.variant == VariantAtomic &&
6785        (board[toY][toX] != EmptySquare ||                     // capture?
6786         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6787                          board[fromY][fromX] == BlackPawn   )
6788       )) {
6789         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6790         return TRUE;
6791     }
6792     return FALSE;
6793 }
6794
6795 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6796
6797 int CanPromote(ChessSquare piece, int y)
6798 {
6799         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6800         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6801         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6802            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6803            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6804                                                   gameInfo.variant == VariantMakruk) return FALSE;
6805         return (piece == BlackPawn && y == 1 ||
6806                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6807                 piece == BlackLance && y == 1 ||
6808                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6809 }
6810
6811 void LeftClick(ClickType clickType, int xPix, int yPix)
6812 {
6813     int x, y;
6814     Boolean saveAnimate;
6815     static int second = 0, promotionChoice = 0, clearFlag = 0;
6816     char promoChoice = NULLCHAR;
6817     ChessSquare piece;
6818
6819     if(appData.seekGraph && appData.icsActive && loggedOn &&
6820         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6821         SeekGraphClick(clickType, xPix, yPix, 0);
6822         return;
6823     }
6824
6825     if (clickType == Press) ErrorPopDown();
6826
6827     x = EventToSquare(xPix, BOARD_WIDTH);
6828     y = EventToSquare(yPix, BOARD_HEIGHT);
6829     if (!flipView && y >= 0) {
6830         y = BOARD_HEIGHT - 1 - y;
6831     }
6832     if (flipView && x >= 0) {
6833         x = BOARD_WIDTH - 1 - x;
6834     }
6835
6836     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6837         defaultPromoChoice = promoSweep;
6838         promoSweep = EmptySquare;   // terminate sweep
6839         promoDefaultAltered = TRUE;
6840         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6841     }
6842
6843     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6844         if(clickType == Release) return; // ignore upclick of click-click destination
6845         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6846         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6847         if(gameInfo.holdingsWidth &&
6848                 (WhiteOnMove(currentMove)
6849                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6850                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6851             // click in right holdings, for determining promotion piece
6852             ChessSquare p = boards[currentMove][y][x];
6853             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6854             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6855             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6856                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6857                 fromX = fromY = -1;
6858                 return;
6859             }
6860         }
6861         DrawPosition(FALSE, boards[currentMove]);
6862         return;
6863     }
6864
6865     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6866     if(clickType == Press
6867             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6868               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6869               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6870         return;
6871
6872     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6873         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6874
6875     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6876         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6877                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6878         defaultPromoChoice = DefaultPromoChoice(side);
6879     }
6880
6881     autoQueen = appData.alwaysPromoteToQueen;
6882
6883     if (fromX == -1) {
6884       int originalY = y;
6885       gatingPiece = EmptySquare;
6886       if (clickType != Press) {
6887         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6888             DragPieceEnd(xPix, yPix); dragging = 0;
6889             DrawPosition(FALSE, NULL);
6890         }
6891         return;
6892       }
6893       fromX = x; fromY = y; toX = toY = -1;
6894       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6895          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6896          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6897             /* First square */
6898             if (OKToStartUserMove(fromX, fromY)) {
6899                 second = 0;
6900                 MarkTargetSquares(0);
6901                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6902                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6903                     promoSweep = defaultPromoChoice;
6904                     selectFlag = 0; lastX = xPix; lastY = yPix;
6905                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6906                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6907                 }
6908                 if (appData.highlightDragging) {
6909                     SetHighlights(fromX, fromY, -1, -1);
6910                 }
6911             } else fromX = fromY = -1;
6912             return;
6913         }
6914     }
6915
6916     /* fromX != -1 */
6917     if (clickType == Press && gameMode != EditPosition) {
6918         ChessSquare fromP;
6919         ChessSquare toP;
6920         int frc;
6921
6922         // ignore off-board to clicks
6923         if(y < 0 || x < 0) return;
6924
6925         /* Check if clicking again on the same color piece */
6926         fromP = boards[currentMove][fromY][fromX];
6927         toP = boards[currentMove][y][x];
6928         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6929         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6930              WhitePawn <= toP && toP <= WhiteKing &&
6931              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6932              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6933             (BlackPawn <= fromP && fromP <= BlackKing &&
6934              BlackPawn <= toP && toP <= BlackKing &&
6935              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6936              !(fromP == BlackKing && toP == BlackRook && frc))) {
6937             /* Clicked again on same color piece -- changed his mind */
6938             second = (x == fromX && y == fromY);
6939             promoDefaultAltered = FALSE;
6940             MarkTargetSquares(1);
6941            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6942             if (appData.highlightDragging) {
6943                 SetHighlights(x, y, -1, -1);
6944             } else {
6945                 ClearHighlights();
6946             }
6947             if (OKToStartUserMove(x, y)) {
6948                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6949                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6950                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6951                  gatingPiece = boards[currentMove][fromY][fromX];
6952                 else gatingPiece = EmptySquare;
6953                 fromX = x;
6954                 fromY = y; dragging = 1;
6955                 MarkTargetSquares(0);
6956                 DragPieceBegin(xPix, yPix, FALSE);
6957                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6958                     promoSweep = defaultPromoChoice;
6959                     selectFlag = 0; lastX = xPix; lastY = yPix;
6960                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6961                 }
6962             }
6963            }
6964            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6965            second = FALSE; 
6966         }
6967         // ignore clicks on holdings
6968         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6969     }
6970
6971     if (clickType == Release && x == fromX && y == fromY) {
6972         DragPieceEnd(xPix, yPix); dragging = 0;
6973         if(clearFlag) {
6974             // a deferred attempt to click-click move an empty square on top of a piece
6975             boards[currentMove][y][x] = EmptySquare;
6976             ClearHighlights();
6977             DrawPosition(FALSE, boards[currentMove]);
6978             fromX = fromY = -1; clearFlag = 0;
6979             return;
6980         }
6981         if (appData.animateDragging) {
6982             /* Undo animation damage if any */
6983             DrawPosition(FALSE, NULL);
6984         }
6985         if (second) {
6986             /* Second up/down in same square; just abort move */
6987             second = 0;
6988             fromX = fromY = -1;
6989             gatingPiece = EmptySquare;
6990             ClearHighlights();
6991             gotPremove = 0;
6992             ClearPremoveHighlights();
6993         } else {
6994             /* First upclick in same square; start click-click mode */
6995             SetHighlights(x, y, -1, -1);
6996         }
6997         return;
6998     }
6999
7000     clearFlag = 0;
7001
7002     /* we now have a different from- and (possibly off-board) to-square */
7003     /* Completed move */
7004     toX = x;
7005     toY = y;
7006     saveAnimate = appData.animate;
7007     MarkTargetSquares(1);
7008     if (clickType == Press) {
7009         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7010             // must be Edit Position mode with empty-square selected
7011             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7012             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7013             return;
7014         }
7015         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7016             ChessSquare piece = boards[currentMove][fromY][fromX];
7017             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7018             promoSweep = defaultPromoChoice;
7019             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7020             selectFlag = 0; lastX = xPix; lastY = yPix;
7021             Sweep(0); // Pawn that is going to promote: preview promotion piece
7022             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7023             DrawPosition(FALSE, boards[currentMove]);
7024             return;
7025         }
7026         /* Finish clickclick move */
7027         if (appData.animate || appData.highlightLastMove) {
7028             SetHighlights(fromX, fromY, toX, toY);
7029         } else {
7030             ClearHighlights();
7031         }
7032     } else {
7033         /* Finish drag move */
7034         if (appData.highlightLastMove) {
7035             SetHighlights(fromX, fromY, toX, toY);
7036         } else {
7037             ClearHighlights();
7038         }
7039         DragPieceEnd(xPix, yPix); dragging = 0;
7040         /* Don't animate move and drag both */
7041         appData.animate = FALSE;
7042     }
7043
7044     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7045     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7046         ChessSquare piece = boards[currentMove][fromY][fromX];
7047         if(gameMode == EditPosition && piece != EmptySquare &&
7048            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7049             int n;
7050
7051             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7052                 n = PieceToNumber(piece - (int)BlackPawn);
7053                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7054                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7055                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7056             } else
7057             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7058                 n = PieceToNumber(piece);
7059                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7060                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7061                 boards[currentMove][n][BOARD_WIDTH-2]++;
7062             }
7063             boards[currentMove][fromY][fromX] = EmptySquare;
7064         }
7065         ClearHighlights();
7066         fromX = fromY = -1;
7067         DrawPosition(TRUE, boards[currentMove]);
7068         return;
7069     }
7070
7071     // off-board moves should not be highlighted
7072     if(x < 0 || y < 0) ClearHighlights();
7073
7074     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7075
7076     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7077         SetHighlights(fromX, fromY, toX, toY);
7078         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7079             // [HGM] super: promotion to captured piece selected from holdings
7080             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7081             promotionChoice = TRUE;
7082             // kludge follows to temporarily execute move on display, without promoting yet
7083             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7084             boards[currentMove][toY][toX] = p;
7085             DrawPosition(FALSE, boards[currentMove]);
7086             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7087             boards[currentMove][toY][toX] = q;
7088             DisplayMessage("Click in holdings to choose piece", "");
7089             return;
7090         }
7091         PromotionPopUp();
7092     } else {
7093         int oldMove = currentMove;
7094         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7095         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7096         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7097         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7098            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7099             DrawPosition(TRUE, boards[currentMove]);
7100         fromX = fromY = -1;
7101     }
7102     appData.animate = saveAnimate;
7103     if (appData.animate || appData.animateDragging) {
7104         /* Undo animation damage if needed */
7105         DrawPosition(FALSE, NULL);
7106     }
7107 }
7108
7109 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7110 {   // front-end-free part taken out of PieceMenuPopup
7111     int whichMenu; int xSqr, ySqr;
7112
7113     if(seekGraphUp) { // [HGM] seekgraph
7114         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7115         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7116         return -2;
7117     }
7118
7119     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7120          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7121         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7122         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7123         if(action == Press)   {
7124             originalFlip = flipView;
7125             flipView = !flipView; // temporarily flip board to see game from partners perspective
7126             DrawPosition(TRUE, partnerBoard);
7127             DisplayMessage(partnerStatus, "");
7128             partnerUp = TRUE;
7129         } else if(action == Release) {
7130             flipView = originalFlip;
7131             DrawPosition(TRUE, boards[currentMove]);
7132             partnerUp = FALSE;
7133         }
7134         return -2;
7135     }
7136
7137     xSqr = EventToSquare(x, BOARD_WIDTH);
7138     ySqr = EventToSquare(y, BOARD_HEIGHT);
7139     if (action == Release) {
7140         if(pieceSweep != EmptySquare) {
7141             EditPositionMenuEvent(pieceSweep, toX, toY);
7142             pieceSweep = EmptySquare;
7143         } else UnLoadPV(); // [HGM] pv
7144     }
7145     if (action != Press) return -2; // return code to be ignored
7146     switch (gameMode) {
7147       case IcsExamining:
7148         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7149       case EditPosition:
7150         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7151         if (xSqr < 0 || ySqr < 0) return -1;
7152         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7153         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7154         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7155         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7156         NextPiece(0);
7157         return 2; // grab
7158       case IcsObserving:
7159         if(!appData.icsEngineAnalyze) return -1;
7160       case IcsPlayingWhite:
7161       case IcsPlayingBlack:
7162         if(!appData.zippyPlay) goto noZip;
7163       case AnalyzeMode:
7164       case AnalyzeFile:
7165       case MachinePlaysWhite:
7166       case MachinePlaysBlack:
7167       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7168         if (!appData.dropMenu) {
7169           LoadPV(x, y);
7170           return 2; // flag front-end to grab mouse events
7171         }
7172         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7173            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7174       case EditGame:
7175       noZip:
7176         if (xSqr < 0 || ySqr < 0) return -1;
7177         if (!appData.dropMenu || appData.testLegality &&
7178             gameInfo.variant != VariantBughouse &&
7179             gameInfo.variant != VariantCrazyhouse) return -1;
7180         whichMenu = 1; // drop menu
7181         break;
7182       default:
7183         return -1;
7184     }
7185
7186     if (((*fromX = xSqr) < 0) ||
7187         ((*fromY = ySqr) < 0)) {
7188         *fromX = *fromY = -1;
7189         return -1;
7190     }
7191     if (flipView)
7192       *fromX = BOARD_WIDTH - 1 - *fromX;
7193     else
7194       *fromY = BOARD_HEIGHT - 1 - *fromY;
7195
7196     return whichMenu;
7197 }
7198
7199 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7200 {
7201 //    char * hint = lastHint;
7202     FrontEndProgramStats stats;
7203
7204     stats.which = cps == &first ? 0 : 1;
7205     stats.depth = cpstats->depth;
7206     stats.nodes = cpstats->nodes;
7207     stats.score = cpstats->score;
7208     stats.time = cpstats->time;
7209     stats.pv = cpstats->movelist;
7210     stats.hint = lastHint;
7211     stats.an_move_index = 0;
7212     stats.an_move_count = 0;
7213
7214     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7215         stats.hint = cpstats->move_name;
7216         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7217         stats.an_move_count = cpstats->nr_moves;
7218     }
7219
7220     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7221
7222     SetProgramStats( &stats );
7223 }
7224
7225 void
7226 ClearEngineOutputPane(int which)
7227 {
7228     static FrontEndProgramStats dummyStats;
7229     dummyStats.which = which;
7230     dummyStats.pv = "#";
7231     SetProgramStats( &dummyStats );
7232 }
7233
7234 #define MAXPLAYERS 500
7235
7236 char *
7237 TourneyStandings(int display)
7238 {
7239     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7240     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7241     char result, *p, *names[MAXPLAYERS];
7242
7243     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7244         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7245     names[0] = p = strdup(appData.participants);
7246     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7247
7248     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7249
7250     while(result = appData.results[nr]) {
7251         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7252         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7253         wScore = bScore = 0;
7254         switch(result) {
7255           case '+': wScore = 2; break;
7256           case '-': bScore = 2; break;
7257           case '=': wScore = bScore = 1; break;
7258           case ' ':
7259           case '*': return strdup("busy"); // tourney not finished
7260         }
7261         score[w] += wScore;
7262         score[b] += bScore;
7263         games[w]++;
7264         games[b]++;
7265         nr++;
7266     }
7267     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7268     for(w=0; w<nPlayers; w++) {
7269         bScore = -1;
7270         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7271         ranking[w] = b; points[w] = bScore; score[b] = -2;
7272     }
7273     p = malloc(nPlayers*34+1);
7274     for(w=0; w<nPlayers && w<display; w++)
7275         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7276     free(names[0]);
7277     return p;
7278 }
7279
7280 void
7281 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7282 {       // count all piece types
7283         int p, f, r;
7284         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7285         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7286         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7287                 p = board[r][f];
7288                 pCnt[p]++;
7289                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7290                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7291                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7292                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7293                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7294                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7295         }
7296 }
7297
7298 int
7299 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7300 {
7301         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7302         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7303
7304         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7305         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7306         if(myPawns == 2 && nMine == 3) // KPP
7307             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7308         if(myPawns == 1 && nMine == 2) // KP
7309             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7310         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7311             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7312         if(myPawns) return FALSE;
7313         if(pCnt[WhiteRook+side])
7314             return pCnt[BlackRook-side] ||
7315                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7316                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7317                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7318         if(pCnt[WhiteCannon+side]) {
7319             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7320             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7321         }
7322         if(pCnt[WhiteKnight+side])
7323             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7324         return FALSE;
7325 }
7326
7327 int
7328 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7329 {
7330         VariantClass v = gameInfo.variant;
7331
7332         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7333         if(v == VariantShatranj) return TRUE; // always winnable through baring
7334         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7335         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7336
7337         if(v == VariantXiangqi) {
7338                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7339
7340                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7341                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7342                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7343                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7344                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7345                 if(stale) // we have at least one last-rank P plus perhaps C
7346                     return majors // KPKX
7347                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7348                 else // KCA*E*
7349                     return pCnt[WhiteFerz+side] // KCAK
7350                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7351                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7352                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7353
7354         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7355                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7356
7357                 if(nMine == 1) return FALSE; // bare King
7358                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
7359                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7360                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7361                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7362                 if(pCnt[WhiteKnight+side])
7363                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7364                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7365                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7366                 if(nBishops)
7367                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7368                 if(pCnt[WhiteAlfil+side])
7369                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7370                 if(pCnt[WhiteWazir+side])
7371                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7372         }
7373
7374         return TRUE;
7375 }
7376
7377 int
7378 CompareWithRights(Board b1, Board b2)
7379 {
7380     int rights = 0;
7381     if(!CompareBoards(b1, b2)) return FALSE;
7382     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7383     /* compare castling rights */
7384     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7385            rights++; /* King lost rights, while rook still had them */
7386     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7387         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7388            rights++; /* but at least one rook lost them */
7389     }
7390     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7391            rights++;
7392     if( b1[CASTLING][5] != NoRights ) {
7393         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7394            rights++;
7395     }
7396     return rights == 0;
7397 }
7398
7399 int
7400 Adjudicate(ChessProgramState *cps)
7401 {       // [HGM] some adjudications useful with buggy engines
7402         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7403         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7404         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7405         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7406         int k, count = 0; static int bare = 1;
7407         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7408         Boolean canAdjudicate = !appData.icsActive;
7409
7410         // most tests only when we understand the game, i.e. legality-checking on
7411             if( appData.testLegality )
7412             {   /* [HGM] Some more adjudications for obstinate engines */
7413                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7414                 static int moveCount = 6;
7415                 ChessMove result;
7416                 char *reason = NULL;
7417
7418                 /* Count what is on board. */
7419                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7420
7421                 /* Some material-based adjudications that have to be made before stalemate test */
7422                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7423                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7424                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7425                      if(canAdjudicate && appData.checkMates) {
7426                          if(engineOpponent)
7427                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7428                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7429                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7430                          return 1;
7431                      }
7432                 }
7433
7434                 /* Bare King in Shatranj (loses) or Losers (wins) */
7435                 if( nrW == 1 || nrB == 1) {
7436                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7437                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7438                      if(canAdjudicate && appData.checkMates) {
7439                          if(engineOpponent)
7440                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7441                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7442                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7443                          return 1;
7444                      }
7445                   } else
7446                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7447                   {    /* bare King */
7448                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7449                         if(canAdjudicate && appData.checkMates) {
7450                             /* but only adjudicate if adjudication enabled */
7451                             if(engineOpponent)
7452                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7453                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7454                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7455                             return 1;
7456                         }
7457                   }
7458                 } else bare = 1;
7459
7460
7461             // don't wait for engine to announce game end if we can judge ourselves
7462             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7463               case MT_CHECK:
7464                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7465                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7466                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7467                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7468                             checkCnt++;
7469                         if(checkCnt >= 2) {
7470                             reason = "Xboard adjudication: 3rd check";
7471                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7472                             break;
7473                         }
7474                     }
7475                 }
7476               case MT_NONE:
7477               default:
7478                 break;
7479               case MT_STALEMATE:
7480               case MT_STAINMATE:
7481                 reason = "Xboard adjudication: Stalemate";
7482                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7483                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7484                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7485                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7486                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7487                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7488                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7489                                                                         EP_CHECKMATE : EP_WINS);
7490                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7491                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7492                 }
7493                 break;
7494               case MT_CHECKMATE:
7495                 reason = "Xboard adjudication: Checkmate";
7496                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7497                 break;
7498             }
7499
7500                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7501                     case EP_STALEMATE:
7502                         result = GameIsDrawn; break;
7503                     case EP_CHECKMATE:
7504                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7505                     case EP_WINS:
7506                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7507                     default:
7508                         result = EndOfFile;
7509                 }
7510                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7511                     if(engineOpponent)
7512                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7513                     GameEnds( result, reason, GE_XBOARD );
7514                     return 1;
7515                 }
7516
7517                 /* Next absolutely insufficient mating material. */
7518                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7519                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7520                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7521
7522                      /* always flag draws, for judging claims */
7523                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7524
7525                      if(canAdjudicate && appData.materialDraws) {
7526                          /* but only adjudicate them if adjudication enabled */
7527                          if(engineOpponent) {
7528                            SendToProgram("force\n", engineOpponent); // suppress reply
7529                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7530                          }
7531                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7532                          return 1;
7533                      }
7534                 }
7535
7536                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7537                 if(gameInfo.variant == VariantXiangqi ?
7538                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7539                  : nrW + nrB == 4 &&
7540                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7541                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7542                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7543                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7544                    ) ) {
7545                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7546                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7547                           if(engineOpponent) {
7548                             SendToProgram("force\n", engineOpponent); // suppress reply
7549                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7550                           }
7551                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7552                           return 1;
7553                      }
7554                 } else moveCount = 6;
7555             }
7556         if (appData.debugMode) { int i;
7557             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7558                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7559                     appData.drawRepeats);
7560             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7561               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7562
7563         }
7564
7565         // Repetition draws and 50-move rule can be applied independently of legality testing
7566
7567                 /* Check for rep-draws */
7568                 count = 0;
7569                 for(k = forwardMostMove-2;
7570                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7571                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7572                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7573                     k-=2)
7574                 {   int rights=0;
7575                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7576                         /* compare castling rights */
7577                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7578                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7579                                 rights++; /* King lost rights, while rook still had them */
7580                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7581                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7582                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7583                                    rights++; /* but at least one rook lost them */
7584                         }
7585                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7586                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7587                                 rights++;
7588                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7589                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7590                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7591                                    rights++;
7592                         }
7593                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7594                             && appData.drawRepeats > 1) {
7595                              /* adjudicate after user-specified nr of repeats */
7596                              int result = GameIsDrawn;
7597                              char *details = "XBoard adjudication: repetition draw";
7598                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7599                                 // [HGM] xiangqi: check for forbidden perpetuals
7600                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7601                                 for(m=forwardMostMove; m>k; m-=2) {
7602                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7603                                         ourPerpetual = 0; // the current mover did not always check
7604                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7605                                         hisPerpetual = 0; // the opponent did not always check
7606                                 }
7607                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7608                                                                         ourPerpetual, hisPerpetual);
7609                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7610                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7611                                     details = "Xboard adjudication: perpetual checking";
7612                                 } else
7613                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7614                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7615                                 } else
7616                                 // Now check for perpetual chases
7617                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7618                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7619                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7620                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7621                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7622                                         details = "Xboard adjudication: perpetual chasing";
7623                                     } else
7624                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7625                                         break; // Abort repetition-checking loop.
7626                                 }
7627                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7628                              }
7629                              if(engineOpponent) {
7630                                SendToProgram("force\n", engineOpponent); // suppress reply
7631                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7632                              }
7633                              GameEnds( result, details, GE_XBOARD );
7634                              return 1;
7635                         }
7636                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7637                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7638                     }
7639                 }
7640
7641                 /* Now we test for 50-move draws. Determine ply count */
7642                 count = forwardMostMove;
7643                 /* look for last irreversble move */
7644                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7645                     count--;
7646                 /* if we hit starting position, add initial plies */
7647                 if( count == backwardMostMove )
7648                     count -= initialRulePlies;
7649                 count = forwardMostMove - count;
7650                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7651                         // adjust reversible move counter for checks in Xiangqi
7652                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7653                         if(i < backwardMostMove) i = backwardMostMove;
7654                         while(i <= forwardMostMove) {
7655                                 lastCheck = inCheck; // check evasion does not count
7656                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7657                                 if(inCheck || lastCheck) count--; // check does not count
7658                                 i++;
7659                         }
7660                 }
7661                 if( count >= 100)
7662                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7663                          /* this is used to judge if draw claims are legal */
7664                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7665                          if(engineOpponent) {
7666                            SendToProgram("force\n", engineOpponent); // suppress reply
7667                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7668                          }
7669                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7670                          return 1;
7671                 }
7672
7673                 /* if draw offer is pending, treat it as a draw claim
7674                  * when draw condition present, to allow engines a way to
7675                  * claim draws before making their move to avoid a race
7676                  * condition occurring after their move
7677                  */
7678                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7679                          char *p = NULL;
7680                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7681                              p = "Draw claim: 50-move rule";
7682                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7683                              p = "Draw claim: 3-fold repetition";
7684                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7685                              p = "Draw claim: insufficient mating material";
7686                          if( p != NULL && canAdjudicate) {
7687                              if(engineOpponent) {
7688                                SendToProgram("force\n", engineOpponent); // suppress reply
7689                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7690                              }
7691                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7692                              return 1;
7693                          }
7694                 }
7695
7696                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7697                     if(engineOpponent) {
7698                       SendToProgram("force\n", engineOpponent); // suppress reply
7699                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7700                     }
7701                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7702                     return 1;
7703                 }
7704         return 0;
7705 }
7706
7707 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7708 {   // [HGM] book: this routine intercepts moves to simulate book replies
7709     char *bookHit = NULL;
7710
7711     //first determine if the incoming move brings opponent into his book
7712     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7713         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7714     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7715     if(bookHit != NULL && !cps->bookSuspend) {
7716         // make sure opponent is not going to reply after receiving move to book position
7717         SendToProgram("force\n", cps);
7718         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7719     }
7720     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7721     // now arrange restart after book miss
7722     if(bookHit) {
7723         // after a book hit we never send 'go', and the code after the call to this routine
7724         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7725         char buf[MSG_SIZ], *move = bookHit;
7726         if(cps->useSAN) {
7727             int fromX, fromY, toX, toY;
7728             char promoChar;
7729             ChessMove moveType;
7730             move = buf + 30;
7731             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7732                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7733                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7734                                     PosFlags(forwardMostMove),
7735                                     fromY, fromX, toY, toX, promoChar, move);
7736             } else {
7737                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7738                 bookHit = NULL;
7739             }
7740         }
7741         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7742         SendToProgram(buf, cps);
7743         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7744     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7745         SendToProgram("go\n", cps);
7746         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7747     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7748         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7749             SendToProgram("go\n", cps);
7750         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7751     }
7752     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7753 }
7754
7755 char *savedMessage;
7756 ChessProgramState *savedState;
7757 void DeferredBookMove(void)
7758 {
7759         if(savedState->lastPing != savedState->lastPong)
7760                     ScheduleDelayedEvent(DeferredBookMove, 10);
7761         else
7762         HandleMachineMove(savedMessage, savedState);
7763 }
7764
7765 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7766
7767 void
7768 HandleMachineMove(message, cps)
7769      char *message;
7770      ChessProgramState *cps;
7771 {
7772     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7773     char realname[MSG_SIZ];
7774     int fromX, fromY, toX, toY;
7775     ChessMove moveType;
7776     char promoChar;
7777     char *p, *pv=buf1;
7778     int machineWhite;
7779     char *bookHit;
7780
7781     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7782         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7783         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7784             DisplayError(_("Invalid pairing from pairing engine"), 0);
7785             return;
7786         }
7787         pairingReceived = 1;
7788         NextMatchGame();
7789         return; // Skim the pairing messages here.
7790     }
7791
7792     cps->userError = 0;
7793
7794 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7795     /*
7796      * Kludge to ignore BEL characters
7797      */
7798     while (*message == '\007') message++;
7799
7800     /*
7801      * [HGM] engine debug message: ignore lines starting with '#' character
7802      */
7803     if(cps->debug && *message == '#') return;
7804
7805     /*
7806      * Look for book output
7807      */
7808     if (cps == &first && bookRequested) {
7809         if (message[0] == '\t' || message[0] == ' ') {
7810             /* Part of the book output is here; append it */
7811             strcat(bookOutput, message);
7812             strcat(bookOutput, "  \n");
7813             return;
7814         } else if (bookOutput[0] != NULLCHAR) {
7815             /* All of book output has arrived; display it */
7816             char *p = bookOutput;
7817             while (*p != NULLCHAR) {
7818                 if (*p == '\t') *p = ' ';
7819                 p++;
7820             }
7821             DisplayInformation(bookOutput);
7822             bookRequested = FALSE;
7823             /* Fall through to parse the current output */
7824         }
7825     }
7826
7827     /*
7828      * Look for machine move.
7829      */
7830     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7831         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7832     {
7833         /* This method is only useful on engines that support ping */
7834         if (cps->lastPing != cps->lastPong) {
7835           if (gameMode == BeginningOfGame) {
7836             /* Extra move from before last new; ignore */
7837             if (appData.debugMode) {
7838                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7839             }
7840           } else {
7841             if (appData.debugMode) {
7842                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7843                         cps->which, gameMode);
7844             }
7845
7846             SendToProgram("undo\n", cps);
7847           }
7848           return;
7849         }
7850
7851         switch (gameMode) {
7852           case BeginningOfGame:
7853             /* Extra move from before last reset; ignore */
7854             if (appData.debugMode) {
7855                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7856             }
7857             return;
7858
7859           case EndOfGame:
7860           case IcsIdle:
7861           default:
7862             /* Extra move after we tried to stop.  The mode test is
7863                not a reliable way of detecting this problem, but it's
7864                the best we can do on engines that don't support ping.
7865             */
7866             if (appData.debugMode) {
7867                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7868                         cps->which, gameMode);
7869             }
7870             SendToProgram("undo\n", cps);
7871             return;
7872
7873           case MachinePlaysWhite:
7874           case IcsPlayingWhite:
7875             machineWhite = TRUE;
7876             break;
7877
7878           case MachinePlaysBlack:
7879           case IcsPlayingBlack:
7880             machineWhite = FALSE;
7881             break;
7882
7883           case TwoMachinesPlay:
7884             machineWhite = (cps->twoMachinesColor[0] == 'w');
7885             break;
7886         }
7887         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7888             if (appData.debugMode) {
7889                 fprintf(debugFP,
7890                         "Ignoring move out of turn by %s, gameMode %d"
7891                         ", forwardMost %d\n",
7892                         cps->which, gameMode, forwardMostMove);
7893             }
7894             return;
7895         }
7896
7897     if (appData.debugMode) { int f = forwardMostMove;
7898         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7899                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7900                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7901     }
7902         if(cps->alphaRank) AlphaRank(machineMove, 4);
7903         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7904                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7905             /* Machine move could not be parsed; ignore it. */
7906           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7907                     machineMove, _(cps->which));
7908             DisplayError(buf1, 0);
7909             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7910                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7911             if (gameMode == TwoMachinesPlay) {
7912               GameEnds(machineWhite ? BlackWins : WhiteWins,
7913                        buf1, GE_XBOARD);
7914             }
7915             return;
7916         }
7917
7918         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7919         /* So we have to redo legality test with true e.p. status here,  */
7920         /* to make sure an illegal e.p. capture does not slip through,   */
7921         /* to cause a forfeit on a justified illegal-move complaint      */
7922         /* of the opponent.                                              */
7923         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7924            ChessMove moveType;
7925            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7926                              fromY, fromX, toY, toX, promoChar);
7927             if (appData.debugMode) {
7928                 int i;
7929                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7930                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7931                 fprintf(debugFP, "castling rights\n");
7932             }
7933             if(moveType == IllegalMove) {
7934               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7935                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7936                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7937                            buf1, GE_XBOARD);
7938                 return;
7939            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7940            /* [HGM] Kludge to handle engines that send FRC-style castling
7941               when they shouldn't (like TSCP-Gothic) */
7942            switch(moveType) {
7943              case WhiteASideCastleFR:
7944              case BlackASideCastleFR:
7945                toX+=2;
7946                currentMoveString[2]++;
7947                break;
7948              case WhiteHSideCastleFR:
7949              case BlackHSideCastleFR:
7950                toX--;
7951                currentMoveString[2]--;
7952                break;
7953              default: ; // nothing to do, but suppresses warning of pedantic compilers
7954            }
7955         }
7956         hintRequested = FALSE;
7957         lastHint[0] = NULLCHAR;
7958         bookRequested = FALSE;
7959         /* Program may be pondering now */
7960         cps->maybeThinking = TRUE;
7961         if (cps->sendTime == 2) cps->sendTime = 1;
7962         if (cps->offeredDraw) cps->offeredDraw--;
7963
7964         /* [AS] Save move info*/
7965         pvInfoList[ forwardMostMove ].score = programStats.score;
7966         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7967         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7968
7969         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7970
7971         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7972         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7973             int count = 0;
7974
7975             while( count < adjudicateLossPlies ) {
7976                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7977
7978                 if( count & 1 ) {
7979                     score = -score; /* Flip score for winning side */
7980                 }
7981
7982                 if( score > adjudicateLossThreshold ) {
7983                     break;
7984                 }
7985
7986                 count++;
7987             }
7988
7989             if( count >= adjudicateLossPlies ) {
7990                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7991
7992                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7993                     "Xboard adjudication",
7994                     GE_XBOARD );
7995
7996                 return;
7997             }
7998         }
7999
8000         if(Adjudicate(cps)) {
8001             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8002             return; // [HGM] adjudicate: for all automatic game ends
8003         }
8004
8005 #if ZIPPY
8006         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8007             first.initDone) {
8008           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8009                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8010                 SendToICS("draw ");
8011                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8012           }
8013           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8014           ics_user_moved = 1;
8015           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8016                 char buf[3*MSG_SIZ];
8017
8018                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8019                         programStats.score / 100.,
8020                         programStats.depth,
8021                         programStats.time / 100.,
8022                         (unsigned int)programStats.nodes,
8023                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8024                         programStats.movelist);
8025                 SendToICS(buf);
8026 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8027           }
8028         }
8029 #endif
8030
8031         /* [AS] Clear stats for next move */
8032         ClearProgramStats();
8033         thinkOutput[0] = NULLCHAR;
8034         hiddenThinkOutputState = 0;
8035
8036         bookHit = NULL;
8037         if (gameMode == TwoMachinesPlay) {
8038             /* [HGM] relaying draw offers moved to after reception of move */
8039             /* and interpreting offer as claim if it brings draw condition */
8040             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8041                 SendToProgram("draw\n", cps->other);
8042             }
8043             if (cps->other->sendTime) {
8044                 SendTimeRemaining(cps->other,
8045                                   cps->other->twoMachinesColor[0] == 'w');
8046             }
8047             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8048             if (firstMove && !bookHit) {
8049                 firstMove = FALSE;
8050                 if (cps->other->useColors) {
8051                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8052                 }
8053                 SendToProgram("go\n", cps->other);
8054             }
8055             cps->other->maybeThinking = TRUE;
8056         }
8057
8058         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8059
8060         if (!pausing && appData.ringBellAfterMoves) {
8061             RingBell();
8062         }
8063
8064         /*
8065          * Reenable menu items that were disabled while
8066          * machine was thinking
8067          */
8068         if (gameMode != TwoMachinesPlay)
8069             SetUserThinkingEnables();
8070
8071         // [HGM] book: after book hit opponent has received move and is now in force mode
8072         // force the book reply into it, and then fake that it outputted this move by jumping
8073         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8074         if(bookHit) {
8075                 static char bookMove[MSG_SIZ]; // a bit generous?
8076
8077                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8078                 strcat(bookMove, bookHit);
8079                 message = bookMove;
8080                 cps = cps->other;
8081                 programStats.nodes = programStats.depth = programStats.time =
8082                 programStats.score = programStats.got_only_move = 0;
8083                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8084
8085                 if(cps->lastPing != cps->lastPong) {
8086                     savedMessage = message; // args for deferred call
8087                     savedState = cps;
8088                     ScheduleDelayedEvent(DeferredBookMove, 10);
8089                     return;
8090                 }
8091                 goto FakeBookMove;
8092         }
8093
8094         return;
8095     }
8096
8097     /* Set special modes for chess engines.  Later something general
8098      *  could be added here; for now there is just one kludge feature,
8099      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8100      *  when "xboard" is given as an interactive command.
8101      */
8102     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8103         cps->useSigint = FALSE;
8104         cps->useSigterm = FALSE;
8105     }
8106     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8107       ParseFeatures(message+8, cps);
8108       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8109     }
8110
8111     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8112       int dummy, s=6; char buf[MSG_SIZ];
8113       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8114       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8115       ParseFEN(boards[0], &dummy, message+s);
8116       DrawPosition(TRUE, boards[0]);
8117       startedFromSetupPosition = TRUE;
8118       return;
8119     }
8120     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8121      * want this, I was asked to put it in, and obliged.
8122      */
8123     if (!strncmp(message, "setboard ", 9)) {
8124         Board initial_position;
8125
8126         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8127
8128         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8129             DisplayError(_("Bad FEN received from engine"), 0);
8130             return ;
8131         } else {
8132            Reset(TRUE, FALSE);
8133            CopyBoard(boards[0], initial_position);
8134            initialRulePlies = FENrulePlies;
8135            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8136            else gameMode = MachinePlaysBlack;
8137            DrawPosition(FALSE, boards[currentMove]);
8138         }
8139         return;
8140     }
8141
8142     /*
8143      * Look for communication commands
8144      */
8145     if (!strncmp(message, "telluser ", 9)) {
8146         if(message[9] == '\\' && message[10] == '\\')
8147             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8148         PlayTellSound();
8149         DisplayNote(message + 9);
8150         return;
8151     }
8152     if (!strncmp(message, "tellusererror ", 14)) {
8153         cps->userError = 1;
8154         if(message[14] == '\\' && message[15] == '\\')
8155             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8156         PlayTellSound();
8157         DisplayError(message + 14, 0);
8158         return;
8159     }
8160     if (!strncmp(message, "tellopponent ", 13)) {
8161       if (appData.icsActive) {
8162         if (loggedOn) {
8163           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8164           SendToICS(buf1);
8165         }
8166       } else {
8167         DisplayNote(message + 13);
8168       }
8169       return;
8170     }
8171     if (!strncmp(message, "tellothers ", 11)) {
8172       if (appData.icsActive) {
8173         if (loggedOn) {
8174           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8175           SendToICS(buf1);
8176         }
8177       }
8178       return;
8179     }
8180     if (!strncmp(message, "tellall ", 8)) {
8181       if (appData.icsActive) {
8182         if (loggedOn) {
8183           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8184           SendToICS(buf1);
8185         }
8186       } else {
8187         DisplayNote(message + 8);
8188       }
8189       return;
8190     }
8191     if (strncmp(message, "warning", 7) == 0) {
8192         /* Undocumented feature, use tellusererror in new code */
8193         DisplayError(message, 0);
8194         return;
8195     }
8196     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8197         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8198         strcat(realname, " query");
8199         AskQuestion(realname, buf2, buf1, cps->pr);
8200         return;
8201     }
8202     /* Commands from the engine directly to ICS.  We don't allow these to be
8203      *  sent until we are logged on. Crafty kibitzes have been known to
8204      *  interfere with the login process.
8205      */
8206     if (loggedOn) {
8207         if (!strncmp(message, "tellics ", 8)) {
8208             SendToICS(message + 8);
8209             SendToICS("\n");
8210             return;
8211         }
8212         if (!strncmp(message, "tellicsnoalias ", 15)) {
8213             SendToICS(ics_prefix);
8214             SendToICS(message + 15);
8215             SendToICS("\n");
8216             return;
8217         }
8218         /* The following are for backward compatibility only */
8219         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8220             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8221             SendToICS(ics_prefix);
8222             SendToICS(message);
8223             SendToICS("\n");
8224             return;
8225         }
8226     }
8227     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8228         return;
8229     }
8230     /*
8231      * If the move is illegal, cancel it and redraw the board.
8232      * Also deal with other error cases.  Matching is rather loose
8233      * here to accommodate engines written before the spec.
8234      */
8235     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8236         strncmp(message, "Error", 5) == 0) {
8237         if (StrStr(message, "name") ||
8238             StrStr(message, "rating") || StrStr(message, "?") ||
8239             StrStr(message, "result") || StrStr(message, "board") ||
8240             StrStr(message, "bk") || StrStr(message, "computer") ||
8241             StrStr(message, "variant") || StrStr(message, "hint") ||
8242             StrStr(message, "random") || StrStr(message, "depth") ||
8243             StrStr(message, "accepted")) {
8244             return;
8245         }
8246         if (StrStr(message, "protover")) {
8247           /* Program is responding to input, so it's apparently done
8248              initializing, and this error message indicates it is
8249              protocol version 1.  So we don't need to wait any longer
8250              for it to initialize and send feature commands. */
8251           FeatureDone(cps, 1);
8252           cps->protocolVersion = 1;
8253           return;
8254         }
8255         cps->maybeThinking = FALSE;
8256
8257         if (StrStr(message, "draw")) {
8258             /* Program doesn't have "draw" command */
8259             cps->sendDrawOffers = 0;
8260             return;
8261         }
8262         if (cps->sendTime != 1 &&
8263             (StrStr(message, "time") || StrStr(message, "otim"))) {
8264           /* Program apparently doesn't have "time" or "otim" command */
8265           cps->sendTime = 0;
8266           return;
8267         }
8268         if (StrStr(message, "analyze")) {
8269             cps->analysisSupport = FALSE;
8270             cps->analyzing = FALSE;
8271             Reset(FALSE, TRUE);
8272             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8273             DisplayError(buf2, 0);
8274             return;
8275         }
8276         if (StrStr(message, "(no matching move)st")) {
8277           /* Special kludge for GNU Chess 4 only */
8278           cps->stKludge = TRUE;
8279           SendTimeControl(cps, movesPerSession, timeControl,
8280                           timeIncrement, appData.searchDepth,
8281                           searchTime);
8282           return;
8283         }
8284         if (StrStr(message, "(no matching move)sd")) {
8285           /* Special kludge for GNU Chess 4 only */
8286           cps->sdKludge = TRUE;
8287           SendTimeControl(cps, movesPerSession, timeControl,
8288                           timeIncrement, appData.searchDepth,
8289                           searchTime);
8290           return;
8291         }
8292         if (!StrStr(message, "llegal")) {
8293             return;
8294         }
8295         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8296             gameMode == IcsIdle) return;
8297         if (forwardMostMove <= backwardMostMove) return;
8298         if (pausing) PauseEvent();
8299       if(appData.forceIllegal) {
8300             // [HGM] illegal: machine refused move; force position after move into it
8301           SendToProgram("force\n", cps);
8302           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8303                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8304                 // when black is to move, while there might be nothing on a2 or black
8305                 // might already have the move. So send the board as if white has the move.
8306                 // But first we must change the stm of the engine, as it refused the last move
8307                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8308                 if(WhiteOnMove(forwardMostMove)) {
8309                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8310                     SendBoard(cps, forwardMostMove); // kludgeless board
8311                 } else {
8312                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8313                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8314                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8315                 }
8316           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8317             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8318                  gameMode == TwoMachinesPlay)
8319               SendToProgram("go\n", cps);
8320             return;
8321       } else
8322         if (gameMode == PlayFromGameFile) {
8323             /* Stop reading this game file */
8324             gameMode = EditGame;
8325             ModeHighlight();
8326         }
8327         /* [HGM] illegal-move claim should forfeit game when Xboard */
8328         /* only passes fully legal moves                            */
8329         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8330             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8331                                 "False illegal-move claim", GE_XBOARD );
8332             return; // do not take back move we tested as valid
8333         }
8334         currentMove = forwardMostMove-1;
8335         DisplayMove(currentMove-1); /* before DisplayMoveError */
8336         SwitchClocks(forwardMostMove-1); // [HGM] race
8337         DisplayBothClocks();
8338         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8339                 parseList[currentMove], _(cps->which));
8340         DisplayMoveError(buf1);
8341         DrawPosition(FALSE, boards[currentMove]);
8342         return;
8343     }
8344     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8345         /* Program has a broken "time" command that
8346            outputs a string not ending in newline.
8347            Don't use it. */
8348         cps->sendTime = 0;
8349     }
8350
8351     /*
8352      * If chess program startup fails, exit with an error message.
8353      * Attempts to recover here are futile.
8354      */
8355     if ((StrStr(message, "unknown host") != NULL)
8356         || (StrStr(message, "No remote directory") != NULL)
8357         || (StrStr(message, "not found") != NULL)
8358         || (StrStr(message, "No such file") != NULL)
8359         || (StrStr(message, "can't alloc") != NULL)
8360         || (StrStr(message, "Permission denied") != NULL)) {
8361
8362         cps->maybeThinking = FALSE;
8363         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8364                 _(cps->which), cps->program, cps->host, message);
8365         RemoveInputSource(cps->isr);
8366         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8367             if(cps == &first) appData.noChessProgram = TRUE;
8368             DisplayError(buf1, 0);
8369         }
8370         return;
8371     }
8372
8373     /*
8374      * Look for hint output
8375      */
8376     if (sscanf(message, "Hint: %s", buf1) == 1) {
8377         if (cps == &first && hintRequested) {
8378             hintRequested = FALSE;
8379             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8380                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8381                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8382                                     PosFlags(forwardMostMove),
8383                                     fromY, fromX, toY, toX, promoChar, buf1);
8384                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8385                 DisplayInformation(buf2);
8386             } else {
8387                 /* Hint move could not be parsed!? */
8388               snprintf(buf2, sizeof(buf2),
8389                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8390                         buf1, _(cps->which));
8391                 DisplayError(buf2, 0);
8392             }
8393         } else {
8394           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8395         }
8396         return;
8397     }
8398
8399     /*
8400      * Ignore other messages if game is not in progress
8401      */
8402     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8403         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8404
8405     /*
8406      * look for win, lose, draw, or draw offer
8407      */
8408     if (strncmp(message, "1-0", 3) == 0) {
8409         char *p, *q, *r = "";
8410         p = strchr(message, '{');
8411         if (p) {
8412             q = strchr(p, '}');
8413             if (q) {
8414                 *q = NULLCHAR;
8415                 r = p + 1;
8416             }
8417         }
8418         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8419         return;
8420     } else if (strncmp(message, "0-1", 3) == 0) {
8421         char *p, *q, *r = "";
8422         p = strchr(message, '{');
8423         if (p) {
8424             q = strchr(p, '}');
8425             if (q) {
8426                 *q = NULLCHAR;
8427                 r = p + 1;
8428             }
8429         }
8430         /* Kludge for Arasan 4.1 bug */
8431         if (strcmp(r, "Black resigns") == 0) {
8432             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8433             return;
8434         }
8435         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8436         return;
8437     } else if (strncmp(message, "1/2", 3) == 0) {
8438         char *p, *q, *r = "";
8439         p = strchr(message, '{');
8440         if (p) {
8441             q = strchr(p, '}');
8442             if (q) {
8443                 *q = NULLCHAR;
8444                 r = p + 1;
8445             }
8446         }
8447
8448         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8449         return;
8450
8451     } else if (strncmp(message, "White resign", 12) == 0) {
8452         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8453         return;
8454     } else if (strncmp(message, "Black resign", 12) == 0) {
8455         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8456         return;
8457     } else if (strncmp(message, "White matches", 13) == 0 ||
8458                strncmp(message, "Black matches", 13) == 0   ) {
8459         /* [HGM] ignore GNUShogi noises */
8460         return;
8461     } else if (strncmp(message, "White", 5) == 0 &&
8462                message[5] != '(' &&
8463                StrStr(message, "Black") == NULL) {
8464         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8465         return;
8466     } else if (strncmp(message, "Black", 5) == 0 &&
8467                message[5] != '(') {
8468         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8469         return;
8470     } else if (strcmp(message, "resign") == 0 ||
8471                strcmp(message, "computer resigns") == 0) {
8472         switch (gameMode) {
8473           case MachinePlaysBlack:
8474           case IcsPlayingBlack:
8475             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8476             break;
8477           case MachinePlaysWhite:
8478           case IcsPlayingWhite:
8479             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8480             break;
8481           case TwoMachinesPlay:
8482             if (cps->twoMachinesColor[0] == 'w')
8483               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8484             else
8485               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8486             break;
8487           default:
8488             /* can't happen */
8489             break;
8490         }
8491         return;
8492     } else if (strncmp(message, "opponent mates", 14) == 0) {
8493         switch (gameMode) {
8494           case MachinePlaysBlack:
8495           case IcsPlayingBlack:
8496             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8497             break;
8498           case MachinePlaysWhite:
8499           case IcsPlayingWhite:
8500             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8501             break;
8502           case TwoMachinesPlay:
8503             if (cps->twoMachinesColor[0] == 'w')
8504               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8505             else
8506               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8507             break;
8508           default:
8509             /* can't happen */
8510             break;
8511         }
8512         return;
8513     } else if (strncmp(message, "computer mates", 14) == 0) {
8514         switch (gameMode) {
8515           case MachinePlaysBlack:
8516           case IcsPlayingBlack:
8517             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8518             break;
8519           case MachinePlaysWhite:
8520           case IcsPlayingWhite:
8521             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8522             break;
8523           case TwoMachinesPlay:
8524             if (cps->twoMachinesColor[0] == 'w')
8525               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8526             else
8527               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8528             break;
8529           default:
8530             /* can't happen */
8531             break;
8532         }
8533         return;
8534     } else if (strncmp(message, "checkmate", 9) == 0) {
8535         if (WhiteOnMove(forwardMostMove)) {
8536             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8537         } else {
8538             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8539         }
8540         return;
8541     } else if (strstr(message, "Draw") != NULL ||
8542                strstr(message, "game is a draw") != NULL) {
8543         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8544         return;
8545     } else if (strstr(message, "offer") != NULL &&
8546                strstr(message, "draw") != NULL) {
8547 #if ZIPPY
8548         if (appData.zippyPlay && first.initDone) {
8549             /* Relay offer to ICS */
8550             SendToICS(ics_prefix);
8551             SendToICS("draw\n");
8552         }
8553 #endif
8554         cps->offeredDraw = 2; /* valid until this engine moves twice */
8555         if (gameMode == TwoMachinesPlay) {
8556             if (cps->other->offeredDraw) {
8557                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8558             /* [HGM] in two-machine mode we delay relaying draw offer      */
8559             /* until after we also have move, to see if it is really claim */
8560             }
8561         } else if (gameMode == MachinePlaysWhite ||
8562                    gameMode == MachinePlaysBlack) {
8563           if (userOfferedDraw) {
8564             DisplayInformation(_("Machine accepts your draw offer"));
8565             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8566           } else {
8567             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8568           }
8569         }
8570     }
8571
8572
8573     /*
8574      * Look for thinking output
8575      */
8576     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8577           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8578                                 ) {
8579         int plylev, mvleft, mvtot, curscore, time;
8580         char mvname[MOVE_LEN];
8581         u64 nodes; // [DM]
8582         char plyext;
8583         int ignore = FALSE;
8584         int prefixHint = FALSE;
8585         mvname[0] = NULLCHAR;
8586
8587         switch (gameMode) {
8588           case MachinePlaysBlack:
8589           case IcsPlayingBlack:
8590             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8591             break;
8592           case MachinePlaysWhite:
8593           case IcsPlayingWhite:
8594             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8595             break;
8596           case AnalyzeMode:
8597           case AnalyzeFile:
8598             break;
8599           case IcsObserving: /* [DM] icsEngineAnalyze */
8600             if (!appData.icsEngineAnalyze) ignore = TRUE;
8601             break;
8602           case TwoMachinesPlay:
8603             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8604                 ignore = TRUE;
8605             }
8606             break;
8607           default:
8608             ignore = TRUE;
8609             break;
8610         }
8611
8612         if (!ignore) {
8613             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8614             buf1[0] = NULLCHAR;
8615             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8616                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8617
8618                 if (plyext != ' ' && plyext != '\t') {
8619                     time *= 100;
8620                 }
8621
8622                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8623                 if( cps->scoreIsAbsolute &&
8624                     ( gameMode == MachinePlaysBlack ||
8625                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8626                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8627                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8628                      !WhiteOnMove(currentMove)
8629                     ) )
8630                 {
8631                     curscore = -curscore;
8632                 }
8633
8634                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8635
8636                 tempStats.depth = plylev;
8637                 tempStats.nodes = nodes;
8638                 tempStats.time = time;
8639                 tempStats.score = curscore;
8640                 tempStats.got_only_move = 0;
8641
8642                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8643                         int ticklen;
8644
8645                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8646                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8647                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8648                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8649                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8650                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8651                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8652                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8653                 }
8654
8655                 /* Buffer overflow protection */
8656                 if (pv[0] != NULLCHAR) {
8657                     if (strlen(pv) >= sizeof(tempStats.movelist)
8658                         && appData.debugMode) {
8659                         fprintf(debugFP,
8660                                 "PV is too long; using the first %u bytes.\n",
8661                                 (unsigned) sizeof(tempStats.movelist) - 1);
8662                     }
8663
8664                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8665                 } else {
8666                     sprintf(tempStats.movelist, " no PV\n");
8667                 }
8668
8669                 if (tempStats.seen_stat) {
8670                     tempStats.ok_to_send = 1;
8671                 }
8672
8673                 if (strchr(tempStats.movelist, '(') != NULL) {
8674                     tempStats.line_is_book = 1;
8675                     tempStats.nr_moves = 0;
8676                     tempStats.moves_left = 0;
8677                 } else {
8678                     tempStats.line_is_book = 0;
8679                 }
8680
8681                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8682                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8683
8684                 SendProgramStatsToFrontend( cps, &tempStats );
8685
8686                 /*
8687                     [AS] Protect the thinkOutput buffer from overflow... this
8688                     is only useful if buf1 hasn't overflowed first!
8689                 */
8690                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8691                          plylev,
8692                          (gameMode == TwoMachinesPlay ?
8693                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8694                          ((double) curscore) / 100.0,
8695                          prefixHint ? lastHint : "",
8696                          prefixHint ? " " : "" );
8697
8698                 if( buf1[0] != NULLCHAR ) {
8699                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8700
8701                     if( strlen(pv) > max_len ) {
8702                         if( appData.debugMode) {
8703                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8704                         }
8705                         pv[max_len+1] = '\0';
8706                     }
8707
8708                     strcat( thinkOutput, pv);
8709                 }
8710
8711                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8712                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8713                     DisplayMove(currentMove - 1);
8714                 }
8715                 return;
8716
8717             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8718                 /* crafty (9.25+) says "(only move) <move>"
8719                  * if there is only 1 legal move
8720                  */
8721                 sscanf(p, "(only move) %s", buf1);
8722                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8723                 sprintf(programStats.movelist, "%s (only move)", buf1);
8724                 programStats.depth = 1;
8725                 programStats.nr_moves = 1;
8726                 programStats.moves_left = 1;
8727                 programStats.nodes = 1;
8728                 programStats.time = 1;
8729                 programStats.got_only_move = 1;
8730
8731                 /* Not really, but we also use this member to
8732                    mean "line isn't going to change" (Crafty
8733                    isn't searching, so stats won't change) */
8734                 programStats.line_is_book = 1;
8735
8736                 SendProgramStatsToFrontend( cps, &programStats );
8737
8738                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8739                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8740                     DisplayMove(currentMove - 1);
8741                 }
8742                 return;
8743             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8744                               &time, &nodes, &plylev, &mvleft,
8745                               &mvtot, mvname) >= 5) {
8746                 /* The stat01: line is from Crafty (9.29+) in response
8747                    to the "." command */
8748                 programStats.seen_stat = 1;
8749                 cps->maybeThinking = TRUE;
8750
8751                 if (programStats.got_only_move || !appData.periodicUpdates)
8752                   return;
8753
8754                 programStats.depth = plylev;
8755                 programStats.time = time;
8756                 programStats.nodes = nodes;
8757                 programStats.moves_left = mvleft;
8758                 programStats.nr_moves = mvtot;
8759                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8760                 programStats.ok_to_send = 1;
8761                 programStats.movelist[0] = '\0';
8762
8763                 SendProgramStatsToFrontend( cps, &programStats );
8764
8765                 return;
8766
8767             } else if (strncmp(message,"++",2) == 0) {
8768                 /* Crafty 9.29+ outputs this */
8769                 programStats.got_fail = 2;
8770                 return;
8771
8772             } else if (strncmp(message,"--",2) == 0) {
8773                 /* Crafty 9.29+ outputs this */
8774                 programStats.got_fail = 1;
8775                 return;
8776
8777             } else if (thinkOutput[0] != NULLCHAR &&
8778                        strncmp(message, "    ", 4) == 0) {
8779                 unsigned message_len;
8780
8781                 p = message;
8782                 while (*p && *p == ' ') p++;
8783
8784                 message_len = strlen( p );
8785
8786                 /* [AS] Avoid buffer overflow */
8787                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8788                     strcat(thinkOutput, " ");
8789                     strcat(thinkOutput, p);
8790                 }
8791
8792                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8793                     strcat(programStats.movelist, " ");
8794                     strcat(programStats.movelist, p);
8795                 }
8796
8797                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8798                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8799                     DisplayMove(currentMove - 1);
8800                 }
8801                 return;
8802             }
8803         }
8804         else {
8805             buf1[0] = NULLCHAR;
8806
8807             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8808                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8809             {
8810                 ChessProgramStats cpstats;
8811
8812                 if (plyext != ' ' && plyext != '\t') {
8813                     time *= 100;
8814                 }
8815
8816                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8817                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8818                     curscore = -curscore;
8819                 }
8820
8821                 cpstats.depth = plylev;
8822                 cpstats.nodes = nodes;
8823                 cpstats.time = time;
8824                 cpstats.score = curscore;
8825                 cpstats.got_only_move = 0;
8826                 cpstats.movelist[0] = '\0';
8827
8828                 if (buf1[0] != NULLCHAR) {
8829                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8830                 }
8831
8832                 cpstats.ok_to_send = 0;
8833                 cpstats.line_is_book = 0;
8834                 cpstats.nr_moves = 0;
8835                 cpstats.moves_left = 0;
8836
8837                 SendProgramStatsToFrontend( cps, &cpstats );
8838             }
8839         }
8840     }
8841 }
8842
8843
8844 /* Parse a game score from the character string "game", and
8845    record it as the history of the current game.  The game
8846    score is NOT assumed to start from the standard position.
8847    The display is not updated in any way.
8848    */
8849 void
8850 ParseGameHistory(game)
8851      char *game;
8852 {
8853     ChessMove moveType;
8854     int fromX, fromY, toX, toY, boardIndex;
8855     char promoChar;
8856     char *p, *q;
8857     char buf[MSG_SIZ];
8858
8859     if (appData.debugMode)
8860       fprintf(debugFP, "Parsing game history: %s\n", game);
8861
8862     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8863     gameInfo.site = StrSave(appData.icsHost);
8864     gameInfo.date = PGNDate();
8865     gameInfo.round = StrSave("-");
8866
8867     /* Parse out names of players */
8868     while (*game == ' ') game++;
8869     p = buf;
8870     while (*game != ' ') *p++ = *game++;
8871     *p = NULLCHAR;
8872     gameInfo.white = StrSave(buf);
8873     while (*game == ' ') game++;
8874     p = buf;
8875     while (*game != ' ' && *game != '\n') *p++ = *game++;
8876     *p = NULLCHAR;
8877     gameInfo.black = StrSave(buf);
8878
8879     /* Parse moves */
8880     boardIndex = blackPlaysFirst ? 1 : 0;
8881     yynewstr(game);
8882     for (;;) {
8883         yyboardindex = boardIndex;
8884         moveType = (ChessMove) Myylex();
8885         switch (moveType) {
8886           case IllegalMove:             /* maybe suicide chess, etc. */
8887   if (appData.debugMode) {
8888     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8889     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8890     setbuf(debugFP, NULL);
8891   }
8892           case WhitePromotion:
8893           case BlackPromotion:
8894           case WhiteNonPromotion:
8895           case BlackNonPromotion:
8896           case NormalMove:
8897           case WhiteCapturesEnPassant:
8898           case BlackCapturesEnPassant:
8899           case WhiteKingSideCastle:
8900           case WhiteQueenSideCastle:
8901           case BlackKingSideCastle:
8902           case BlackQueenSideCastle:
8903           case WhiteKingSideCastleWild:
8904           case WhiteQueenSideCastleWild:
8905           case BlackKingSideCastleWild:
8906           case BlackQueenSideCastleWild:
8907           /* PUSH Fabien */
8908           case WhiteHSideCastleFR:
8909           case WhiteASideCastleFR:
8910           case BlackHSideCastleFR:
8911           case BlackASideCastleFR:
8912           /* POP Fabien */
8913             fromX = currentMoveString[0] - AAA;
8914             fromY = currentMoveString[1] - ONE;
8915             toX = currentMoveString[2] - AAA;
8916             toY = currentMoveString[3] - ONE;
8917             promoChar = currentMoveString[4];
8918             break;
8919           case WhiteDrop:
8920           case BlackDrop:
8921             fromX = moveType == WhiteDrop ?
8922               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8923             (int) CharToPiece(ToLower(currentMoveString[0]));
8924             fromY = DROP_RANK;
8925             toX = currentMoveString[2] - AAA;
8926             toY = currentMoveString[3] - ONE;
8927             promoChar = NULLCHAR;
8928             break;
8929           case AmbiguousMove:
8930             /* bug? */
8931             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8932   if (appData.debugMode) {
8933     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8934     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8935     setbuf(debugFP, NULL);
8936   }
8937             DisplayError(buf, 0);
8938             return;
8939           case ImpossibleMove:
8940             /* bug? */
8941             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8942   if (appData.debugMode) {
8943     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8944     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8945     setbuf(debugFP, NULL);
8946   }
8947             DisplayError(buf, 0);
8948             return;
8949           case EndOfFile:
8950             if (boardIndex < backwardMostMove) {
8951                 /* Oops, gap.  How did that happen? */
8952                 DisplayError(_("Gap in move list"), 0);
8953                 return;
8954             }
8955             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8956             if (boardIndex > forwardMostMove) {
8957                 forwardMostMove = boardIndex;
8958             }
8959             return;
8960           case ElapsedTime:
8961             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8962                 strcat(parseList[boardIndex-1], " ");
8963                 strcat(parseList[boardIndex-1], yy_text);
8964             }
8965             continue;
8966           case Comment:
8967           case PGNTag:
8968           case NAG:
8969           default:
8970             /* ignore */
8971             continue;
8972           case WhiteWins:
8973           case BlackWins:
8974           case GameIsDrawn:
8975           case GameUnfinished:
8976             if (gameMode == IcsExamining) {
8977                 if (boardIndex < backwardMostMove) {
8978                     /* Oops, gap.  How did that happen? */
8979                     return;
8980                 }
8981                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8982                 return;
8983             }
8984             gameInfo.result = moveType;
8985             p = strchr(yy_text, '{');
8986             if (p == NULL) p = strchr(yy_text, '(');
8987             if (p == NULL) {
8988                 p = yy_text;
8989                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8990             } else {
8991                 q = strchr(p, *p == '{' ? '}' : ')');
8992                 if (q != NULL) *q = NULLCHAR;
8993                 p++;
8994             }
8995             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8996             gameInfo.resultDetails = StrSave(p);
8997             continue;
8998         }
8999         if (boardIndex >= forwardMostMove &&
9000             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9001             backwardMostMove = blackPlaysFirst ? 1 : 0;
9002             return;
9003         }
9004         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9005                                  fromY, fromX, toY, toX, promoChar,
9006                                  parseList[boardIndex]);
9007         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9008         /* currentMoveString is set as a side-effect of yylex */
9009         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9010         strcat(moveList[boardIndex], "\n");
9011         boardIndex++;
9012         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9013         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9014           case MT_NONE:
9015           case MT_STALEMATE:
9016           default:
9017             break;
9018           case MT_CHECK:
9019             if(gameInfo.variant != VariantShogi)
9020                 strcat(parseList[boardIndex - 1], "+");
9021             break;
9022           case MT_CHECKMATE:
9023           case MT_STAINMATE:
9024             strcat(parseList[boardIndex - 1], "#");
9025             break;
9026         }
9027     }
9028 }
9029
9030
9031 /* Apply a move to the given board  */
9032 void
9033 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9034      int fromX, fromY, toX, toY;
9035      int promoChar;
9036      Board board;
9037 {
9038   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9039   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9040
9041     /* [HGM] compute & store e.p. status and castling rights for new position */
9042     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9043
9044       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9045       oldEP = (signed char)board[EP_STATUS];
9046       board[EP_STATUS] = EP_NONE;
9047
9048   if (fromY == DROP_RANK) {
9049         /* must be first */
9050         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9051             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9052             return;
9053         }
9054         piece = board[toY][toX] = (ChessSquare) fromX;
9055   } else {
9056       int i;
9057
9058       if( board[toY][toX] != EmptySquare )
9059            board[EP_STATUS] = EP_CAPTURE;
9060
9061       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9062            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9063                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9064       } else
9065       if( board[fromY][fromX] == WhitePawn ) {
9066            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9067                board[EP_STATUS] = EP_PAWN_MOVE;
9068            if( toY-fromY==2) {
9069                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9070                         gameInfo.variant != VariantBerolina || toX < fromX)
9071                       board[EP_STATUS] = toX | berolina;
9072                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9073                         gameInfo.variant != VariantBerolina || toX > fromX)
9074                       board[EP_STATUS] = toX;
9075            }
9076       } else
9077       if( board[fromY][fromX] == BlackPawn ) {
9078            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9079                board[EP_STATUS] = EP_PAWN_MOVE;
9080            if( toY-fromY== -2) {
9081                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9082                         gameInfo.variant != VariantBerolina || toX < fromX)
9083                       board[EP_STATUS] = toX | berolina;
9084                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9085                         gameInfo.variant != VariantBerolina || toX > fromX)
9086                       board[EP_STATUS] = toX;
9087            }
9088        }
9089
9090        for(i=0; i<nrCastlingRights; i++) {
9091            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9092               board[CASTLING][i] == toX   && castlingRank[i] == toY
9093              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9094        }
9095
9096      if (fromX == toX && fromY == toY) return;
9097
9098      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9099      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9100      if(gameInfo.variant == VariantKnightmate)
9101          king += (int) WhiteUnicorn - (int) WhiteKing;
9102
9103     /* Code added by Tord: */
9104     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9105     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9106         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9107       board[fromY][fromX] = EmptySquare;
9108       board[toY][toX] = EmptySquare;
9109       if((toX > fromX) != (piece == WhiteRook)) {
9110         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9111       } else {
9112         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9113       }
9114     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9115                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9116       board[fromY][fromX] = EmptySquare;
9117       board[toY][toX] = EmptySquare;
9118       if((toX > fromX) != (piece == BlackRook)) {
9119         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9120       } else {
9121         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9122       }
9123     /* End of code added by Tord */
9124
9125     } else if (board[fromY][fromX] == king
9126         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9127         && toY == fromY && toX > fromX+1) {
9128         board[fromY][fromX] = EmptySquare;
9129         board[toY][toX] = king;
9130         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9131         board[fromY][BOARD_RGHT-1] = EmptySquare;
9132     } else if (board[fromY][fromX] == king
9133         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9134                && toY == fromY && toX < fromX-1) {
9135         board[fromY][fromX] = EmptySquare;
9136         board[toY][toX] = king;
9137         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9138         board[fromY][BOARD_LEFT] = EmptySquare;
9139     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9140                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9141                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9142                ) {
9143         /* white pawn promotion */
9144         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9145         if(gameInfo.variant==VariantBughouse ||
9146            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9147             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9148         board[fromY][fromX] = EmptySquare;
9149     } else if ((fromY >= BOARD_HEIGHT>>1)
9150                && (toX != fromX)
9151                && gameInfo.variant != VariantXiangqi
9152                && gameInfo.variant != VariantBerolina
9153                && (board[fromY][fromX] == WhitePawn)
9154                && (board[toY][toX] == EmptySquare)) {
9155         board[fromY][fromX] = EmptySquare;
9156         board[toY][toX] = WhitePawn;
9157         captured = board[toY - 1][toX];
9158         board[toY - 1][toX] = EmptySquare;
9159     } else if ((fromY == BOARD_HEIGHT-4)
9160                && (toX == fromX)
9161                && gameInfo.variant == VariantBerolina
9162                && (board[fromY][fromX] == WhitePawn)
9163                && (board[toY][toX] == EmptySquare)) {
9164         board[fromY][fromX] = EmptySquare;
9165         board[toY][toX] = WhitePawn;
9166         if(oldEP & EP_BEROLIN_A) {
9167                 captured = board[fromY][fromX-1];
9168                 board[fromY][fromX-1] = EmptySquare;
9169         }else{  captured = board[fromY][fromX+1];
9170                 board[fromY][fromX+1] = EmptySquare;
9171         }
9172     } else if (board[fromY][fromX] == king
9173         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9174                && toY == fromY && toX > fromX+1) {
9175         board[fromY][fromX] = EmptySquare;
9176         board[toY][toX] = king;
9177         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9178         board[fromY][BOARD_RGHT-1] = EmptySquare;
9179     } else if (board[fromY][fromX] == king
9180         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9181                && toY == fromY && toX < fromX-1) {
9182         board[fromY][fromX] = EmptySquare;
9183         board[toY][toX] = king;
9184         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9185         board[fromY][BOARD_LEFT] = EmptySquare;
9186     } else if (fromY == 7 && fromX == 3
9187                && board[fromY][fromX] == BlackKing
9188                && toY == 7 && toX == 5) {
9189         board[fromY][fromX] = EmptySquare;
9190         board[toY][toX] = BlackKing;
9191         board[fromY][7] = EmptySquare;
9192         board[toY][4] = BlackRook;
9193     } else if (fromY == 7 && fromX == 3
9194                && board[fromY][fromX] == BlackKing
9195                && toY == 7 && toX == 1) {
9196         board[fromY][fromX] = EmptySquare;
9197         board[toY][toX] = BlackKing;
9198         board[fromY][0] = EmptySquare;
9199         board[toY][2] = BlackRook;
9200     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9201                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9202                && toY < promoRank && promoChar
9203                ) {
9204         /* black pawn promotion */
9205         board[toY][toX] = CharToPiece(ToLower(promoChar));
9206         if(gameInfo.variant==VariantBughouse ||
9207            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9208             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9209         board[fromY][fromX] = EmptySquare;
9210     } else if ((fromY < BOARD_HEIGHT>>1)
9211                && (toX != fromX)
9212                && gameInfo.variant != VariantXiangqi
9213                && gameInfo.variant != VariantBerolina
9214                && (board[fromY][fromX] == BlackPawn)
9215                && (board[toY][toX] == EmptySquare)) {
9216         board[fromY][fromX] = EmptySquare;
9217         board[toY][toX] = BlackPawn;
9218         captured = board[toY + 1][toX];
9219         board[toY + 1][toX] = EmptySquare;
9220     } else if ((fromY == 3)
9221                && (toX == fromX)
9222                && gameInfo.variant == VariantBerolina
9223                && (board[fromY][fromX] == BlackPawn)
9224                && (board[toY][toX] == EmptySquare)) {
9225         board[fromY][fromX] = EmptySquare;
9226         board[toY][toX] = BlackPawn;
9227         if(oldEP & EP_BEROLIN_A) {
9228                 captured = board[fromY][fromX-1];
9229                 board[fromY][fromX-1] = EmptySquare;
9230         }else{  captured = board[fromY][fromX+1];
9231                 board[fromY][fromX+1] = EmptySquare;
9232         }
9233     } else {
9234         board[toY][toX] = board[fromY][fromX];
9235         board[fromY][fromX] = EmptySquare;
9236     }
9237   }
9238
9239     if (gameInfo.holdingsWidth != 0) {
9240
9241       /* !!A lot more code needs to be written to support holdings  */
9242       /* [HGM] OK, so I have written it. Holdings are stored in the */
9243       /* penultimate board files, so they are automaticlly stored   */
9244       /* in the game history.                                       */
9245       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9246                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9247         /* Delete from holdings, by decreasing count */
9248         /* and erasing image if necessary            */
9249         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9250         if(p < (int) BlackPawn) { /* white drop */
9251              p -= (int)WhitePawn;
9252                  p = PieceToNumber((ChessSquare)p);
9253              if(p >= gameInfo.holdingsSize) p = 0;
9254              if(--board[p][BOARD_WIDTH-2] <= 0)
9255                   board[p][BOARD_WIDTH-1] = EmptySquare;
9256              if((int)board[p][BOARD_WIDTH-2] < 0)
9257                         board[p][BOARD_WIDTH-2] = 0;
9258         } else {                  /* black drop */
9259              p -= (int)BlackPawn;
9260                  p = PieceToNumber((ChessSquare)p);
9261              if(p >= gameInfo.holdingsSize) p = 0;
9262              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9263                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9264              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9265                         board[BOARD_HEIGHT-1-p][1] = 0;
9266         }
9267       }
9268       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9269           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9270         /* [HGM] holdings: Add to holdings, if holdings exist */
9271         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9272                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9273                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9274         }
9275         p = (int) captured;
9276         if (p >= (int) BlackPawn) {
9277           p -= (int)BlackPawn;
9278           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9279                   /* in Shogi restore piece to its original  first */
9280                   captured = (ChessSquare) (DEMOTED captured);
9281                   p = DEMOTED p;
9282           }
9283           p = PieceToNumber((ChessSquare)p);
9284           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9285           board[p][BOARD_WIDTH-2]++;
9286           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9287         } else {
9288           p -= (int)WhitePawn;
9289           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9290                   captured = (ChessSquare) (DEMOTED captured);
9291                   p = DEMOTED p;
9292           }
9293           p = PieceToNumber((ChessSquare)p);
9294           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9295           board[BOARD_HEIGHT-1-p][1]++;
9296           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9297         }
9298       }
9299     } else if (gameInfo.variant == VariantAtomic) {
9300       if (captured != EmptySquare) {
9301         int y, x;
9302         for (y = toY-1; y <= toY+1; y++) {
9303           for (x = toX-1; x <= toX+1; x++) {
9304             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9305                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9306               board[y][x] = EmptySquare;
9307             }
9308           }
9309         }
9310         board[toY][toX] = EmptySquare;
9311       }
9312     }
9313     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9314         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9315     } else
9316     if(promoChar == '+') {
9317         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9318         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9319     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9320         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9321     }
9322     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9323                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9324         // [HGM] superchess: take promotion piece out of holdings
9325         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9326         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9327             if(!--board[k][BOARD_WIDTH-2])
9328                 board[k][BOARD_WIDTH-1] = EmptySquare;
9329         } else {
9330             if(!--board[BOARD_HEIGHT-1-k][1])
9331                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9332         }
9333     }
9334
9335 }
9336
9337 /* Updates forwardMostMove */
9338 void
9339 MakeMove(fromX, fromY, toX, toY, promoChar)
9340      int fromX, fromY, toX, toY;
9341      int promoChar;
9342 {
9343 //    forwardMostMove++; // [HGM] bare: moved downstream
9344
9345     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9346         int timeLeft; static int lastLoadFlag=0; int king, piece;
9347         piece = boards[forwardMostMove][fromY][fromX];
9348         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9349         if(gameInfo.variant == VariantKnightmate)
9350             king += (int) WhiteUnicorn - (int) WhiteKing;
9351         if(forwardMostMove == 0) {
9352             if(blackPlaysFirst)
9353                 fprintf(serverMoves, "%s;", second.tidy);
9354             fprintf(serverMoves, "%s;", first.tidy);
9355             if(!blackPlaysFirst)
9356                 fprintf(serverMoves, "%s;", second.tidy);
9357         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9358         lastLoadFlag = loadFlag;
9359         // print base move
9360         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9361         // print castling suffix
9362         if( toY == fromY && piece == king ) {
9363             if(toX-fromX > 1)
9364                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9365             if(fromX-toX >1)
9366                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9367         }
9368         // e.p. suffix
9369         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9370              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9371              boards[forwardMostMove][toY][toX] == EmptySquare
9372              && fromX != toX && fromY != toY)
9373                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9374         // promotion suffix
9375         if(promoChar != NULLCHAR)
9376                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9377         if(!loadFlag) {
9378             fprintf(serverMoves, "/%d/%d",
9379                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9380             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9381             else                      timeLeft = blackTimeRemaining/1000;
9382             fprintf(serverMoves, "/%d", timeLeft);
9383         }
9384         fflush(serverMoves);
9385     }
9386
9387     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9388       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9389                         0, 1);
9390       return;
9391     }
9392     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9393     if (commentList[forwardMostMove+1] != NULL) {
9394         free(commentList[forwardMostMove+1]);
9395         commentList[forwardMostMove+1] = NULL;
9396     }
9397     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9398     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9399     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9400     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9401     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9402     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9403     gameInfo.result = GameUnfinished;
9404     if (gameInfo.resultDetails != NULL) {
9405         free(gameInfo.resultDetails);
9406         gameInfo.resultDetails = NULL;
9407     }
9408     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9409                               moveList[forwardMostMove - 1]);
9410     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9411                              PosFlags(forwardMostMove - 1),
9412                              fromY, fromX, toY, toX, promoChar,
9413                              parseList[forwardMostMove - 1]);
9414     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9415       case MT_NONE:
9416       case MT_STALEMATE:
9417       default:
9418         break;
9419       case MT_CHECK:
9420         if(gameInfo.variant != VariantShogi)
9421             strcat(parseList[forwardMostMove - 1], "+");
9422         break;
9423       case MT_CHECKMATE:
9424       case MT_STAINMATE:
9425         strcat(parseList[forwardMostMove - 1], "#");
9426         break;
9427     }
9428     if (appData.debugMode) {
9429         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9430     }
9431
9432 }
9433
9434 /* Updates currentMove if not pausing */
9435 void
9436 ShowMove(fromX, fromY, toX, toY)
9437 {
9438     int instant = (gameMode == PlayFromGameFile) ?
9439         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9440     if(appData.noGUI) return;
9441     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9442         if (!instant) {
9443             if (forwardMostMove == currentMove + 1) {
9444                 AnimateMove(boards[forwardMostMove - 1],
9445                             fromX, fromY, toX, toY);
9446             }
9447             if (appData.highlightLastMove) {
9448                 SetHighlights(fromX, fromY, toX, toY);
9449             }
9450         }
9451         currentMove = forwardMostMove;
9452     }
9453
9454     if (instant) return;
9455
9456     DisplayMove(currentMove - 1);
9457     DrawPosition(FALSE, boards[currentMove]);
9458     DisplayBothClocks();
9459     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9460     DisplayBook(currentMove);
9461 }
9462
9463 void SendEgtPath(ChessProgramState *cps)
9464 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9465         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9466
9467         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9468
9469         while(*p) {
9470             char c, *q = name+1, *r, *s;
9471
9472             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9473             while(*p && *p != ',') *q++ = *p++;
9474             *q++ = ':'; *q = 0;
9475             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9476                 strcmp(name, ",nalimov:") == 0 ) {
9477                 // take nalimov path from the menu-changeable option first, if it is defined
9478               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9479                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9480             } else
9481             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9482                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9483                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9484                 s = r = StrStr(s, ":") + 1; // beginning of path info
9485                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9486                 c = *r; *r = 0;             // temporarily null-terminate path info
9487                     *--q = 0;               // strip of trailig ':' from name
9488                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9489                 *r = c;
9490                 SendToProgram(buf,cps);     // send egtbpath command for this format
9491             }
9492             if(*p == ',') p++; // read away comma to position for next format name
9493         }
9494 }
9495
9496 void
9497 InitChessProgram(cps, setup)
9498      ChessProgramState *cps;
9499      int setup; /* [HGM] needed to setup FRC opening position */
9500 {
9501     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9502     if (appData.noChessProgram) return;
9503     hintRequested = FALSE;
9504     bookRequested = FALSE;
9505
9506     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9507     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9508     if(cps->memSize) { /* [HGM] memory */
9509       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9510         SendToProgram(buf, cps);
9511     }
9512     SendEgtPath(cps); /* [HGM] EGT */
9513     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9514       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9515         SendToProgram(buf, cps);
9516     }
9517
9518     SendToProgram(cps->initString, cps);
9519     if (gameInfo.variant != VariantNormal &&
9520         gameInfo.variant != VariantLoadable
9521         /* [HGM] also send variant if board size non-standard */
9522         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9523                                             ) {
9524       char *v = VariantName(gameInfo.variant);
9525       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9526         /* [HGM] in protocol 1 we have to assume all variants valid */
9527         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9528         DisplayFatalError(buf, 0, 1);
9529         return;
9530       }
9531
9532       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9533       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9534       if( gameInfo.variant == VariantXiangqi )
9535            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9536       if( gameInfo.variant == VariantShogi )
9537            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9538       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9539            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9540       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9541           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9542            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9543       if( gameInfo.variant == VariantCourier )
9544            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9545       if( gameInfo.variant == VariantSuper )
9546            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9547       if( gameInfo.variant == VariantGreat )
9548            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9549       if( gameInfo.variant == VariantSChess )
9550            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9551       if( gameInfo.variant == VariantGrand )
9552            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9553
9554       if(overruled) {
9555         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9556                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9557            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9558            if(StrStr(cps->variants, b) == NULL) {
9559                // specific sized variant not known, check if general sizing allowed
9560                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9561                    if(StrStr(cps->variants, "boardsize") == NULL) {
9562                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9563                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9564                        DisplayFatalError(buf, 0, 1);
9565                        return;
9566                    }
9567                    /* [HGM] here we really should compare with the maximum supported board size */
9568                }
9569            }
9570       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9571       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9572       SendToProgram(buf, cps);
9573     }
9574     currentlyInitializedVariant = gameInfo.variant;
9575
9576     /* [HGM] send opening position in FRC to first engine */
9577     if(setup) {
9578           SendToProgram("force\n", cps);
9579           SendBoard(cps, 0);
9580           /* engine is now in force mode! Set flag to wake it up after first move. */
9581           setboardSpoiledMachineBlack = 1;
9582     }
9583
9584     if (cps->sendICS) {
9585       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9586       SendToProgram(buf, cps);
9587     }
9588     cps->maybeThinking = FALSE;
9589     cps->offeredDraw = 0;
9590     if (!appData.icsActive) {
9591         SendTimeControl(cps, movesPerSession, timeControl,
9592                         timeIncrement, appData.searchDepth,
9593                         searchTime);
9594     }
9595     if (appData.showThinking
9596         // [HGM] thinking: four options require thinking output to be sent
9597         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9598                                 ) {
9599         SendToProgram("post\n", cps);
9600     }
9601     SendToProgram("hard\n", cps);
9602     if (!appData.ponderNextMove) {
9603         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9604            it without being sure what state we are in first.  "hard"
9605            is not a toggle, so that one is OK.
9606          */
9607         SendToProgram("easy\n", cps);
9608     }
9609     if (cps->usePing) {
9610       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9611       SendToProgram(buf, cps);
9612     }
9613     cps->initDone = TRUE;
9614     ClearEngineOutputPane(cps == &second);
9615 }
9616
9617
9618 void
9619 StartChessProgram(cps)
9620      ChessProgramState *cps;
9621 {
9622     char buf[MSG_SIZ];
9623     int err;
9624
9625     if (appData.noChessProgram) return;
9626     cps->initDone = FALSE;
9627
9628     if (strcmp(cps->host, "localhost") == 0) {
9629         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9630     } else if (*appData.remoteShell == NULLCHAR) {
9631         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9632     } else {
9633         if (*appData.remoteUser == NULLCHAR) {
9634           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9635                     cps->program);
9636         } else {
9637           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9638                     cps->host, appData.remoteUser, cps->program);
9639         }
9640         err = StartChildProcess(buf, "", &cps->pr);
9641     }
9642
9643     if (err != 0) {
9644       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9645         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9646         if(cps != &first) return;
9647         appData.noChessProgram = TRUE;
9648         ThawUI();
9649         SetNCPMode();
9650 //      DisplayFatalError(buf, err, 1);
9651 //      cps->pr = NoProc;
9652 //      cps->isr = NULL;
9653         return;
9654     }
9655
9656     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9657     if (cps->protocolVersion > 1) {
9658       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9659       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9660       cps->comboCnt = 0;  //                and values of combo boxes
9661       SendToProgram(buf, cps);
9662     } else {
9663       SendToProgram("xboard\n", cps);
9664     }
9665 }
9666
9667 void
9668 TwoMachinesEventIfReady P((void))
9669 {
9670   static int curMess = 0;
9671   if (first.lastPing != first.lastPong) {
9672     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9673     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9674     return;
9675   }
9676   if (second.lastPing != second.lastPong) {
9677     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9678     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9679     return;
9680   }
9681   DisplayMessage("", ""); curMess = 0;
9682   ThawUI();
9683   TwoMachinesEvent();
9684 }
9685
9686 char *
9687 MakeName(char *template)
9688 {
9689     time_t clock;
9690     struct tm *tm;
9691     static char buf[MSG_SIZ];
9692     char *p = buf;
9693     int i;
9694
9695     clock = time((time_t *)NULL);
9696     tm = localtime(&clock);
9697
9698     while(*p++ = *template++) if(p[-1] == '%') {
9699         switch(*template++) {
9700           case 0:   *p = 0; return buf;
9701           case 'Y': i = tm->tm_year+1900; break;
9702           case 'y': i = tm->tm_year-100; break;
9703           case 'M': i = tm->tm_mon+1; break;
9704           case 'd': i = tm->tm_mday; break;
9705           case 'h': i = tm->tm_hour; break;
9706           case 'm': i = tm->tm_min; break;
9707           case 's': i = tm->tm_sec; break;
9708           default:  i = 0;
9709         }
9710         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9711     }
9712     return buf;
9713 }
9714
9715 int
9716 CountPlayers(char *p)
9717 {
9718     int n = 0;
9719     while(p = strchr(p, '\n')) p++, n++; // count participants
9720     return n;
9721 }
9722
9723 FILE *
9724 WriteTourneyFile(char *results, FILE *f)
9725 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9726     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9727     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9728         // create a file with tournament description
9729         fprintf(f, "-participants {%s}\n", appData.participants);
9730         fprintf(f, "-seedBase %d\n", appData.seedBase);
9731         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9732         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9733         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9734         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9735         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9736         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9737         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9738         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9739         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9740         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9741         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9742         if(searchTime > 0)
9743                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9744         else {
9745                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9746                 fprintf(f, "-tc %s\n", appData.timeControl);
9747                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9748         }
9749         fprintf(f, "-results \"%s\"\n", results);
9750     }
9751     return f;
9752 }
9753
9754 #define MAXENGINES 1000
9755 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9756
9757 void Substitute(char *participants, int expunge)
9758 {
9759     int i, changed, changes=0, nPlayers=0;
9760     char *p, *q, *r, buf[MSG_SIZ];
9761     if(participants == NULL) return;
9762     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9763     r = p = participants; q = appData.participants;
9764     while(*p && *p == *q) {
9765         if(*p == '\n') r = p+1, nPlayers++;
9766         p++; q++;
9767     }
9768     if(*p) { // difference
9769         while(*p && *p++ != '\n');
9770         while(*q && *q++ != '\n');
9771       changed = nPlayers;
9772         changes = 1 + (strcmp(p, q) != 0);
9773     }
9774     if(changes == 1) { // a single engine mnemonic was changed
9775         q = r; while(*q) nPlayers += (*q++ == '\n');
9776         p = buf; while(*r && (*p = *r++) != '\n') p++;
9777         *p = NULLCHAR;
9778         NamesToList(firstChessProgramNames, command, mnemonic);
9779         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9780         if(mnemonic[i]) { // The substitute is valid
9781             FILE *f;
9782             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9783                 flock(fileno(f), LOCK_EX);
9784                 ParseArgsFromFile(f);
9785                 fseek(f, 0, SEEK_SET);
9786                 FREE(appData.participants); appData.participants = participants;
9787                 if(expunge) { // erase results of replaced engine
9788                     int len = strlen(appData.results), w, b, dummy;
9789                     for(i=0; i<len; i++) {
9790                         Pairing(i, nPlayers, &w, &b, &dummy);
9791                         if((w == changed || b == changed) && appData.results[i] == '*') {
9792                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9793                             fclose(f);
9794                             return;
9795                         }
9796                     }
9797                     for(i=0; i<len; i++) {
9798                         Pairing(i, nPlayers, &w, &b, &dummy);
9799                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9800                     }
9801                 }
9802                 WriteTourneyFile(appData.results, f);
9803                 fclose(f); // release lock
9804                 return;
9805             }
9806         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9807     }
9808     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9809     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9810     free(participants);
9811     return;
9812 }
9813
9814 int
9815 CreateTourney(char *name)
9816 {
9817         FILE *f;
9818         if(matchMode && strcmp(name, appData.tourneyFile)) {
9819              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9820         }
9821         if(name[0] == NULLCHAR) {
9822             if(appData.participants[0])
9823                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9824             return 0;
9825         }
9826         f = fopen(name, "r");
9827         if(f) { // file exists
9828             ASSIGN(appData.tourneyFile, name);
9829             ParseArgsFromFile(f); // parse it
9830         } else {
9831             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9832             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9833                 DisplayError(_("Not enough participants"), 0);
9834                 return 0;
9835             }
9836             ASSIGN(appData.tourneyFile, name);
9837             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9838             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9839         }
9840         fclose(f);
9841         appData.noChessProgram = FALSE;
9842         appData.clockMode = TRUE;
9843         SetGNUMode();
9844         return 1;
9845 }
9846
9847 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9848 {
9849     char buf[MSG_SIZ], *p, *q;
9850     int i=1;
9851     while(*names) {
9852         p = names; q = buf;
9853         while(*p && *p != '\n') *q++ = *p++;
9854         *q = 0;
9855         if(engineList[i]) free(engineList[i]);
9856         engineList[i] = strdup(buf);
9857         if(*p == '\n') p++;
9858         TidyProgramName(engineList[i], "localhost", buf);
9859         if(engineMnemonic[i]) free(engineMnemonic[i]);
9860         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9861             strcat(buf, " (");
9862             sscanf(q + 8, "%s", buf + strlen(buf));
9863             strcat(buf, ")");
9864         }
9865         engineMnemonic[i] = strdup(buf);
9866         names = p; i++;
9867       if(i > MAXENGINES - 2) break;
9868     }
9869     engineList[i] = engineMnemonic[i] = NULL;
9870 }
9871
9872 // following implemented as macro to avoid type limitations
9873 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9874
9875 void SwapEngines(int n)
9876 {   // swap settings for first engine and other engine (so far only some selected options)
9877     int h;
9878     char *p;
9879     if(n == 0) return;
9880     SWAP(directory, p)
9881     SWAP(chessProgram, p)
9882     SWAP(isUCI, h)
9883     SWAP(hasOwnBookUCI, h)
9884     SWAP(protocolVersion, h)
9885     SWAP(reuse, h)
9886     SWAP(scoreIsAbsolute, h)
9887     SWAP(timeOdds, h)
9888     SWAP(logo, p)
9889     SWAP(pgnName, p)
9890     SWAP(pvSAN, h)
9891 }
9892
9893 void
9894 SetPlayer(int player)
9895 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9896     int i;
9897     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9898     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9899     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9900     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9901     if(mnemonic[i]) {
9902         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9903         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9904         ParseArgsFromString(buf);
9905     }
9906     free(engineName);
9907 }
9908
9909 int
9910 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9911 {   // determine players from game number
9912     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9913
9914     if(appData.tourneyType == 0) {
9915         roundsPerCycle = (nPlayers - 1) | 1;
9916         pairingsPerRound = nPlayers / 2;
9917     } else if(appData.tourneyType > 0) {
9918         roundsPerCycle = nPlayers - appData.tourneyType;
9919         pairingsPerRound = appData.tourneyType;
9920     }
9921     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9922     gamesPerCycle = gamesPerRound * roundsPerCycle;
9923     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9924     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9925     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9926     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9927     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9928     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9929
9930     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9931     if(appData.roundSync) *syncInterval = gamesPerRound;
9932
9933     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9934
9935     if(appData.tourneyType == 0) {
9936         if(curPairing == (nPlayers-1)/2 ) {
9937             *whitePlayer = curRound;
9938             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9939         } else {
9940             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9941             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9942             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9943             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9944         }
9945     } else if(appData.tourneyType > 0) {
9946         *whitePlayer = curPairing;
9947         *blackPlayer = curRound + appData.tourneyType;
9948     }
9949
9950     // take care of white/black alternation per round. 
9951     // For cycles and games this is already taken care of by default, derived from matchGame!
9952     return curRound & 1;
9953 }
9954
9955 int
9956 NextTourneyGame(int nr, int *swapColors)
9957 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9958     char *p, *q;
9959     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9960     FILE *tf;
9961     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9962     tf = fopen(appData.tourneyFile, "r");
9963     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9964     ParseArgsFromFile(tf); fclose(tf);
9965     InitTimeControls(); // TC might be altered from tourney file
9966
9967     nPlayers = CountPlayers(appData.participants); // count participants
9968     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9969     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9970
9971     if(syncInterval) {
9972         p = q = appData.results;
9973         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9974         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9975             DisplayMessage(_("Waiting for other game(s)"),"");
9976             waitingForGame = TRUE;
9977             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9978             return 0;
9979         }
9980         waitingForGame = FALSE;
9981     }
9982
9983     if(appData.tourneyType < 0) {
9984         if(nr>=0 && !pairingReceived) {
9985             char buf[1<<16];
9986             if(pairing.pr == NoProc) {
9987                 if(!appData.pairingEngine[0]) {
9988                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9989                     return 0;
9990                 }
9991                 StartChessProgram(&pairing); // starts the pairing engine
9992             }
9993             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9994             SendToProgram(buf, &pairing);
9995             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9996             SendToProgram(buf, &pairing);
9997             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9998         }
9999         pairingReceived = 0;                              // ... so we continue here 
10000         *swapColors = 0;
10001         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10002         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10003         matchGame = 1; roundNr = nr / syncInterval + 1;
10004     }
10005
10006     if(first.pr != NoProc) return 1; // engines already loaded
10007
10008     // redefine engines, engine dir, etc.
10009     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10010     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10011     SwapEngines(1);
10012     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10013     SwapEngines(1);         // and make that valid for second engine by swapping
10014     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10015     InitEngine(&second, 1);
10016     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10017     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10018     return 1;
10019 }
10020
10021 void
10022 NextMatchGame()
10023 {   // performs game initialization that does not invoke engines, and then tries to start the game
10024     int firstWhite, swapColors = 0;
10025     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10026     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10027     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10028     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10029     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10030     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10031     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10032     Reset(FALSE, first.pr != NoProc);
10033     appData.noChessProgram = FALSE;
10034     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
10035     TwoMachinesEvent();
10036 }
10037
10038 void UserAdjudicationEvent( int result )
10039 {
10040     ChessMove gameResult = GameIsDrawn;
10041
10042     if( result > 0 ) {
10043         gameResult = WhiteWins;
10044     }
10045     else if( result < 0 ) {
10046         gameResult = BlackWins;
10047     }
10048
10049     if( gameMode == TwoMachinesPlay ) {
10050         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10051     }
10052 }
10053
10054
10055 // [HGM] save: calculate checksum of game to make games easily identifiable
10056 int StringCheckSum(char *s)
10057 {
10058         int i = 0;
10059         if(s==NULL) return 0;
10060         while(*s) i = i*259 + *s++;
10061         return i;
10062 }
10063
10064 int GameCheckSum()
10065 {
10066         int i, sum=0;
10067         for(i=backwardMostMove; i<forwardMostMove; i++) {
10068                 sum += pvInfoList[i].depth;
10069                 sum += StringCheckSum(parseList[i]);
10070                 sum += StringCheckSum(commentList[i]);
10071                 sum *= 261;
10072         }
10073         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10074         return sum + StringCheckSum(commentList[i]);
10075 } // end of save patch
10076
10077 void
10078 GameEnds(result, resultDetails, whosays)
10079      ChessMove result;
10080      char *resultDetails;
10081      int whosays;
10082 {
10083     GameMode nextGameMode;
10084     int isIcsGame;
10085     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10086
10087     if(endingGame) return; /* [HGM] crash: forbid recursion */
10088     endingGame = 1;
10089     if(twoBoards) { // [HGM] dual: switch back to one board
10090         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10091         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10092     }
10093     if (appData.debugMode) {
10094       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10095               result, resultDetails ? resultDetails : "(null)", whosays);
10096     }
10097
10098     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10099
10100     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10101         /* If we are playing on ICS, the server decides when the
10102            game is over, but the engine can offer to draw, claim
10103            a draw, or resign.
10104          */
10105 #if ZIPPY
10106         if (appData.zippyPlay && first.initDone) {
10107             if (result == GameIsDrawn) {
10108                 /* In case draw still needs to be claimed */
10109                 SendToICS(ics_prefix);
10110                 SendToICS("draw\n");
10111             } else if (StrCaseStr(resultDetails, "resign")) {
10112                 SendToICS(ics_prefix);
10113                 SendToICS("resign\n");
10114             }
10115         }
10116 #endif
10117         endingGame = 0; /* [HGM] crash */
10118         return;
10119     }
10120
10121     /* If we're loading the game from a file, stop */
10122     if (whosays == GE_FILE) {
10123       (void) StopLoadGameTimer();
10124       gameFileFP = NULL;
10125     }
10126
10127     /* Cancel draw offers */
10128     first.offeredDraw = second.offeredDraw = 0;
10129
10130     /* If this is an ICS game, only ICS can really say it's done;
10131        if not, anyone can. */
10132     isIcsGame = (gameMode == IcsPlayingWhite ||
10133                  gameMode == IcsPlayingBlack ||
10134                  gameMode == IcsObserving    ||
10135                  gameMode == IcsExamining);
10136
10137     if (!isIcsGame || whosays == GE_ICS) {
10138         /* OK -- not an ICS game, or ICS said it was done */
10139         StopClocks();
10140         if (!isIcsGame && !appData.noChessProgram)
10141           SetUserThinkingEnables();
10142
10143         /* [HGM] if a machine claims the game end we verify this claim */
10144         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10145             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10146                 char claimer;
10147                 ChessMove trueResult = (ChessMove) -1;
10148
10149                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10150                                             first.twoMachinesColor[0] :
10151                                             second.twoMachinesColor[0] ;
10152
10153                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10154                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10155                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10156                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10157                 } else
10158                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10159                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10160                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10161                 } else
10162                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10163                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10164                 }
10165
10166                 // now verify win claims, but not in drop games, as we don't understand those yet
10167                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10168                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10169                     (result == WhiteWins && claimer == 'w' ||
10170                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10171                       if (appData.debugMode) {
10172                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10173                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10174                       }
10175                       if(result != trueResult) {
10176                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10177                               result = claimer == 'w' ? BlackWins : WhiteWins;
10178                               resultDetails = buf;
10179                       }
10180                 } else
10181                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10182                     && (forwardMostMove <= backwardMostMove ||
10183                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10184                         (claimer=='b')==(forwardMostMove&1))
10185                                                                                   ) {
10186                       /* [HGM] verify: draws that were not flagged are false claims */
10187                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10188                       result = claimer == 'w' ? BlackWins : WhiteWins;
10189                       resultDetails = buf;
10190                 }
10191                 /* (Claiming a loss is accepted no questions asked!) */
10192             }
10193             /* [HGM] bare: don't allow bare King to win */
10194             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10195                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10196                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10197                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10198                && result != GameIsDrawn)
10199             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10200                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10201                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10202                         if(p >= 0 && p <= (int)WhiteKing) k++;
10203                 }
10204                 if (appData.debugMode) {
10205                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10206                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10207                 }
10208                 if(k <= 1) {
10209                         result = GameIsDrawn;
10210                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10211                         resultDetails = buf;
10212                 }
10213             }
10214         }
10215
10216
10217         if(serverMoves != NULL && !loadFlag) { char c = '=';
10218             if(result==WhiteWins) c = '+';
10219             if(result==BlackWins) c = '-';
10220             if(resultDetails != NULL)
10221                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10222         }
10223         if (resultDetails != NULL) {
10224             gameInfo.result = result;
10225             gameInfo.resultDetails = StrSave(resultDetails);
10226
10227             /* display last move only if game was not loaded from file */
10228             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10229                 DisplayMove(currentMove - 1);
10230
10231             if (forwardMostMove != 0) {
10232                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10233                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10234                                                                 ) {
10235                     if (*appData.saveGameFile != NULLCHAR) {
10236                         SaveGameToFile(appData.saveGameFile, TRUE);
10237                     } else if (appData.autoSaveGames) {
10238                         AutoSaveGame();
10239                     }
10240                     if (*appData.savePositionFile != NULLCHAR) {
10241                         SavePositionToFile(appData.savePositionFile);
10242                     }
10243                 }
10244             }
10245
10246             /* Tell program how game ended in case it is learning */
10247             /* [HGM] Moved this to after saving the PGN, just in case */
10248             /* engine died and we got here through time loss. In that */
10249             /* case we will get a fatal error writing the pipe, which */
10250             /* would otherwise lose us the PGN.                       */
10251             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10252             /* output during GameEnds should never be fatal anymore   */
10253             if (gameMode == MachinePlaysWhite ||
10254                 gameMode == MachinePlaysBlack ||
10255                 gameMode == TwoMachinesPlay ||
10256                 gameMode == IcsPlayingWhite ||
10257                 gameMode == IcsPlayingBlack ||
10258                 gameMode == BeginningOfGame) {
10259                 char buf[MSG_SIZ];
10260                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10261                         resultDetails);
10262                 if (first.pr != NoProc) {
10263                     SendToProgram(buf, &first);
10264                 }
10265                 if (second.pr != NoProc &&
10266                     gameMode == TwoMachinesPlay) {
10267                     SendToProgram(buf, &second);
10268                 }
10269             }
10270         }
10271
10272         if (appData.icsActive) {
10273             if (appData.quietPlay &&
10274                 (gameMode == IcsPlayingWhite ||
10275                  gameMode == IcsPlayingBlack)) {
10276                 SendToICS(ics_prefix);
10277                 SendToICS("set shout 1\n");
10278             }
10279             nextGameMode = IcsIdle;
10280             ics_user_moved = FALSE;
10281             /* clean up premove.  It's ugly when the game has ended and the
10282              * premove highlights are still on the board.
10283              */
10284             if (gotPremove) {
10285               gotPremove = FALSE;
10286               ClearPremoveHighlights();
10287               DrawPosition(FALSE, boards[currentMove]);
10288             }
10289             if (whosays == GE_ICS) {
10290                 switch (result) {
10291                 case WhiteWins:
10292                     if (gameMode == IcsPlayingWhite)
10293                         PlayIcsWinSound();
10294                     else if(gameMode == IcsPlayingBlack)
10295                         PlayIcsLossSound();
10296                     break;
10297                 case BlackWins:
10298                     if (gameMode == IcsPlayingBlack)
10299                         PlayIcsWinSound();
10300                     else if(gameMode == IcsPlayingWhite)
10301                         PlayIcsLossSound();
10302                     break;
10303                 case GameIsDrawn:
10304                     PlayIcsDrawSound();
10305                     break;
10306                 default:
10307                     PlayIcsUnfinishedSound();
10308                 }
10309             }
10310         } else if (gameMode == EditGame ||
10311                    gameMode == PlayFromGameFile ||
10312                    gameMode == AnalyzeMode ||
10313                    gameMode == AnalyzeFile) {
10314             nextGameMode = gameMode;
10315         } else {
10316             nextGameMode = EndOfGame;
10317         }
10318         pausing = FALSE;
10319         ModeHighlight();
10320     } else {
10321         nextGameMode = gameMode;
10322     }
10323
10324     if (appData.noChessProgram) {
10325         gameMode = nextGameMode;
10326         ModeHighlight();
10327         endingGame = 0; /* [HGM] crash */
10328         return;
10329     }
10330
10331     if (first.reuse) {
10332         /* Put first chess program into idle state */
10333         if (first.pr != NoProc &&
10334             (gameMode == MachinePlaysWhite ||
10335              gameMode == MachinePlaysBlack ||
10336              gameMode == TwoMachinesPlay ||
10337              gameMode == IcsPlayingWhite ||
10338              gameMode == IcsPlayingBlack ||
10339              gameMode == BeginningOfGame)) {
10340             SendToProgram("force\n", &first);
10341             if (first.usePing) {
10342               char buf[MSG_SIZ];
10343               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10344               SendToProgram(buf, &first);
10345             }
10346         }
10347     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10348         /* Kill off first chess program */
10349         if (first.isr != NULL)
10350           RemoveInputSource(first.isr);
10351         first.isr = NULL;
10352
10353         if (first.pr != NoProc) {
10354             ExitAnalyzeMode();
10355             DoSleep( appData.delayBeforeQuit );
10356             SendToProgram("quit\n", &first);
10357             DoSleep( appData.delayAfterQuit );
10358             DestroyChildProcess(first.pr, first.useSigterm);
10359         }
10360         first.pr = NoProc;
10361     }
10362     if (second.reuse) {
10363         /* Put second chess program into idle state */
10364         if (second.pr != NoProc &&
10365             gameMode == TwoMachinesPlay) {
10366             SendToProgram("force\n", &second);
10367             if (second.usePing) {
10368               char buf[MSG_SIZ];
10369               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10370               SendToProgram(buf, &second);
10371             }
10372         }
10373     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10374         /* Kill off second chess program */
10375         if (second.isr != NULL)
10376           RemoveInputSource(second.isr);
10377         second.isr = NULL;
10378
10379         if (second.pr != NoProc) {
10380             DoSleep( appData.delayBeforeQuit );
10381             SendToProgram("quit\n", &second);
10382             DoSleep( appData.delayAfterQuit );
10383             DestroyChildProcess(second.pr, second.useSigterm);
10384         }
10385         second.pr = NoProc;
10386     }
10387
10388     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10389         char resChar = '=';
10390         switch (result) {
10391         case WhiteWins:
10392           resChar = '+';
10393           if (first.twoMachinesColor[0] == 'w') {
10394             first.matchWins++;
10395           } else {
10396             second.matchWins++;
10397           }
10398           break;
10399         case BlackWins:
10400           resChar = '-';
10401           if (first.twoMachinesColor[0] == 'b') {
10402             first.matchWins++;
10403           } else {
10404             second.matchWins++;
10405           }
10406           break;
10407         case GameUnfinished:
10408           resChar = ' ';
10409         default:
10410           break;
10411         }
10412
10413         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10414         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10415             ReserveGame(nextGame, resChar); // sets nextGame
10416             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10417             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10418         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10419
10420         if (nextGame <= appData.matchGames && !abortMatch) {
10421             gameMode = nextGameMode;
10422             matchGame = nextGame; // this will be overruled in tourney mode!
10423             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10424             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10425             endingGame = 0; /* [HGM] crash */
10426             return;
10427         } else {
10428             gameMode = nextGameMode;
10429             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10430                      first.tidy, second.tidy,
10431                      first.matchWins, second.matchWins,
10432                      appData.matchGames - (first.matchWins + second.matchWins));
10433             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10434             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10435             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10436                 first.twoMachinesColor = "black\n";
10437                 second.twoMachinesColor = "white\n";
10438             } else {
10439                 first.twoMachinesColor = "white\n";
10440                 second.twoMachinesColor = "black\n";
10441             }
10442         }
10443     }
10444     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10445         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10446       ExitAnalyzeMode();
10447     gameMode = nextGameMode;
10448     ModeHighlight();
10449     endingGame = 0;  /* [HGM] crash */
10450     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10451         if(matchMode == TRUE) { // match through command line: exit with or without popup
10452             if(ranking) {
10453                 ToNrEvent(forwardMostMove);
10454                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10455                 else ExitEvent(0);
10456             } else DisplayFatalError(buf, 0, 0);
10457         } else { // match through menu; just stop, with or without popup
10458             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10459             ModeHighlight();
10460             if(ranking){
10461                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10462             } else DisplayNote(buf);
10463       }
10464       if(ranking) free(ranking);
10465     }
10466 }
10467
10468 /* Assumes program was just initialized (initString sent).
10469    Leaves program in force mode. */
10470 void
10471 FeedMovesToProgram(cps, upto)
10472      ChessProgramState *cps;
10473      int upto;
10474 {
10475     int i;
10476
10477     if (appData.debugMode)
10478       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10479               startedFromSetupPosition ? "position and " : "",
10480               backwardMostMove, upto, cps->which);
10481     if(currentlyInitializedVariant != gameInfo.variant) {
10482       char buf[MSG_SIZ];
10483         // [HGM] variantswitch: make engine aware of new variant
10484         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10485                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10486         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10487         SendToProgram(buf, cps);
10488         currentlyInitializedVariant = gameInfo.variant;
10489     }
10490     SendToProgram("force\n", cps);
10491     if (startedFromSetupPosition) {
10492         SendBoard(cps, backwardMostMove);
10493     if (appData.debugMode) {
10494         fprintf(debugFP, "feedMoves\n");
10495     }
10496     }
10497     for (i = backwardMostMove; i < upto; i++) {
10498         SendMoveToProgram(i, cps);
10499     }
10500 }
10501
10502
10503 int
10504 ResurrectChessProgram()
10505 {
10506      /* The chess program may have exited.
10507         If so, restart it and feed it all the moves made so far. */
10508     static int doInit = 0;
10509
10510     if (appData.noChessProgram) return 1;
10511
10512     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10513         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10514         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10515         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10516     } else {
10517         if (first.pr != NoProc) return 1;
10518         StartChessProgram(&first);
10519     }
10520     InitChessProgram(&first, FALSE);
10521     FeedMovesToProgram(&first, currentMove);
10522
10523     if (!first.sendTime) {
10524         /* can't tell gnuchess what its clock should read,
10525            so we bow to its notion. */
10526         ResetClocks();
10527         timeRemaining[0][currentMove] = whiteTimeRemaining;
10528         timeRemaining[1][currentMove] = blackTimeRemaining;
10529     }
10530
10531     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10532                 appData.icsEngineAnalyze) && first.analysisSupport) {
10533       SendToProgram("analyze\n", &first);
10534       first.analyzing = TRUE;
10535     }
10536     return 1;
10537 }
10538
10539 /*
10540  * Button procedures
10541  */
10542 void
10543 Reset(redraw, init)
10544      int redraw, init;
10545 {
10546     int i;
10547
10548     if (appData.debugMode) {
10549         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10550                 redraw, init, gameMode);
10551     }
10552     CleanupTail(); // [HGM] vari: delete any stored variations
10553     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10554     pausing = pauseExamInvalid = FALSE;
10555     startedFromSetupPosition = blackPlaysFirst = FALSE;
10556     firstMove = TRUE;
10557     whiteFlag = blackFlag = FALSE;
10558     userOfferedDraw = FALSE;
10559     hintRequested = bookRequested = FALSE;
10560     first.maybeThinking = FALSE;
10561     second.maybeThinking = FALSE;
10562     first.bookSuspend = FALSE; // [HGM] book
10563     second.bookSuspend = FALSE;
10564     thinkOutput[0] = NULLCHAR;
10565     lastHint[0] = NULLCHAR;
10566     ClearGameInfo(&gameInfo);
10567     gameInfo.variant = StringToVariant(appData.variant);
10568     ics_user_moved = ics_clock_paused = FALSE;
10569     ics_getting_history = H_FALSE;
10570     ics_gamenum = -1;
10571     white_holding[0] = black_holding[0] = NULLCHAR;
10572     ClearProgramStats();
10573     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10574
10575     ResetFrontEnd();
10576     ClearHighlights();
10577     flipView = appData.flipView;
10578     ClearPremoveHighlights();
10579     gotPremove = FALSE;
10580     alarmSounded = FALSE;
10581
10582     GameEnds(EndOfFile, NULL, GE_PLAYER);
10583     if(appData.serverMovesName != NULL) {
10584         /* [HGM] prepare to make moves file for broadcasting */
10585         clock_t t = clock();
10586         if(serverMoves != NULL) fclose(serverMoves);
10587         serverMoves = fopen(appData.serverMovesName, "r");
10588         if(serverMoves != NULL) {
10589             fclose(serverMoves);
10590             /* delay 15 sec before overwriting, so all clients can see end */
10591             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10592         }
10593         serverMoves = fopen(appData.serverMovesName, "w");
10594     }
10595
10596     ExitAnalyzeMode();
10597     gameMode = BeginningOfGame;
10598     ModeHighlight();
10599     if(appData.icsActive) gameInfo.variant = VariantNormal;
10600     currentMove = forwardMostMove = backwardMostMove = 0;
10601     InitPosition(redraw);
10602     for (i = 0; i < MAX_MOVES; i++) {
10603         if (commentList[i] != NULL) {
10604             free(commentList[i]);
10605             commentList[i] = NULL;
10606         }
10607     }
10608     ResetClocks();
10609     timeRemaining[0][0] = whiteTimeRemaining;
10610     timeRemaining[1][0] = blackTimeRemaining;
10611
10612     if (first.pr == NULL) {
10613         StartChessProgram(&first);
10614     }
10615     if (init) {
10616             InitChessProgram(&first, startedFromSetupPosition);
10617     }
10618     DisplayTitle("");
10619     DisplayMessage("", "");
10620     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10621     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10622 }
10623
10624 void
10625 AutoPlayGameLoop()
10626 {
10627     for (;;) {
10628         if (!AutoPlayOneMove())
10629           return;
10630         if (matchMode || appData.timeDelay == 0)
10631           continue;
10632         if (appData.timeDelay < 0)
10633           return;
10634         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10635         break;
10636     }
10637 }
10638
10639
10640 int
10641 AutoPlayOneMove()
10642 {
10643     int fromX, fromY, toX, toY;
10644
10645     if (appData.debugMode) {
10646       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10647     }
10648
10649     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10650       return FALSE;
10651
10652     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10653       pvInfoList[currentMove].depth = programStats.depth;
10654       pvInfoList[currentMove].score = programStats.score;
10655       pvInfoList[currentMove].time  = 0;
10656       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10657     }
10658
10659     if (currentMove >= forwardMostMove) {
10660       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10661 //      gameMode = EndOfGame;
10662 //      ModeHighlight();
10663
10664       /* [AS] Clear current move marker at the end of a game */
10665       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10666
10667       return FALSE;
10668     }
10669
10670     toX = moveList[currentMove][2] - AAA;
10671     toY = moveList[currentMove][3] - ONE;
10672
10673     if (moveList[currentMove][1] == '@') {
10674         if (appData.highlightLastMove) {
10675             SetHighlights(-1, -1, toX, toY);
10676         }
10677     } else {
10678         fromX = moveList[currentMove][0] - AAA;
10679         fromY = moveList[currentMove][1] - ONE;
10680
10681         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10682
10683         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10684
10685         if (appData.highlightLastMove) {
10686             SetHighlights(fromX, fromY, toX, toY);
10687         }
10688     }
10689     DisplayMove(currentMove);
10690     SendMoveToProgram(currentMove++, &first);
10691     DisplayBothClocks();
10692     DrawPosition(FALSE, boards[currentMove]);
10693     // [HGM] PV info: always display, routine tests if empty
10694     DisplayComment(currentMove - 1, commentList[currentMove]);
10695     return TRUE;
10696 }
10697
10698
10699 int
10700 LoadGameOneMove(readAhead)
10701      ChessMove readAhead;
10702 {
10703     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10704     char promoChar = NULLCHAR;
10705     ChessMove moveType;
10706     char move[MSG_SIZ];
10707     char *p, *q;
10708
10709     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10710         gameMode != AnalyzeMode && gameMode != Training) {
10711         gameFileFP = NULL;
10712         return FALSE;
10713     }
10714
10715     yyboardindex = forwardMostMove;
10716     if (readAhead != EndOfFile) {
10717       moveType = readAhead;
10718     } else {
10719       if (gameFileFP == NULL)
10720           return FALSE;
10721       moveType = (ChessMove) Myylex();
10722     }
10723
10724     done = FALSE;
10725     switch (moveType) {
10726       case Comment:
10727         if (appData.debugMode)
10728           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10729         p = yy_text;
10730
10731         /* append the comment but don't display it */
10732         AppendComment(currentMove, p, FALSE);
10733         return TRUE;
10734
10735       case WhiteCapturesEnPassant:
10736       case BlackCapturesEnPassant:
10737       case WhitePromotion:
10738       case BlackPromotion:
10739       case WhiteNonPromotion:
10740       case BlackNonPromotion:
10741       case NormalMove:
10742       case WhiteKingSideCastle:
10743       case WhiteQueenSideCastle:
10744       case BlackKingSideCastle:
10745       case BlackQueenSideCastle:
10746       case WhiteKingSideCastleWild:
10747       case WhiteQueenSideCastleWild:
10748       case BlackKingSideCastleWild:
10749       case BlackQueenSideCastleWild:
10750       /* PUSH Fabien */
10751       case WhiteHSideCastleFR:
10752       case WhiteASideCastleFR:
10753       case BlackHSideCastleFR:
10754       case BlackASideCastleFR:
10755       /* POP Fabien */
10756         if (appData.debugMode)
10757           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10758         fromX = currentMoveString[0] - AAA;
10759         fromY = currentMoveString[1] - ONE;
10760         toX = currentMoveString[2] - AAA;
10761         toY = currentMoveString[3] - ONE;
10762         promoChar = currentMoveString[4];
10763         break;
10764
10765       case WhiteDrop:
10766       case BlackDrop:
10767         if (appData.debugMode)
10768           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10769         fromX = moveType == WhiteDrop ?
10770           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10771         (int) CharToPiece(ToLower(currentMoveString[0]));
10772         fromY = DROP_RANK;
10773         toX = currentMoveString[2] - AAA;
10774         toY = currentMoveString[3] - ONE;
10775         break;
10776
10777       case WhiteWins:
10778       case BlackWins:
10779       case GameIsDrawn:
10780       case GameUnfinished:
10781         if (appData.debugMode)
10782           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10783         p = strchr(yy_text, '{');
10784         if (p == NULL) p = strchr(yy_text, '(');
10785         if (p == NULL) {
10786             p = yy_text;
10787             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10788         } else {
10789             q = strchr(p, *p == '{' ? '}' : ')');
10790             if (q != NULL) *q = NULLCHAR;
10791             p++;
10792         }
10793         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10794         GameEnds(moveType, p, GE_FILE);
10795         done = TRUE;
10796         if (cmailMsgLoaded) {
10797             ClearHighlights();
10798             flipView = WhiteOnMove(currentMove);
10799             if (moveType == GameUnfinished) flipView = !flipView;
10800             if (appData.debugMode)
10801               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10802         }
10803         break;
10804
10805       case EndOfFile:
10806         if (appData.debugMode)
10807           fprintf(debugFP, "Parser hit end of file\n");
10808         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10809           case MT_NONE:
10810           case MT_CHECK:
10811             break;
10812           case MT_CHECKMATE:
10813           case MT_STAINMATE:
10814             if (WhiteOnMove(currentMove)) {
10815                 GameEnds(BlackWins, "Black mates", GE_FILE);
10816             } else {
10817                 GameEnds(WhiteWins, "White mates", GE_FILE);
10818             }
10819             break;
10820           case MT_STALEMATE:
10821             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10822             break;
10823         }
10824         done = TRUE;
10825         break;
10826
10827       case MoveNumberOne:
10828         if (lastLoadGameStart == GNUChessGame) {
10829             /* GNUChessGames have numbers, but they aren't move numbers */
10830             if (appData.debugMode)
10831               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10832                       yy_text, (int) moveType);
10833             return LoadGameOneMove(EndOfFile); /* tail recursion */
10834         }
10835         /* else fall thru */
10836
10837       case XBoardGame:
10838       case GNUChessGame:
10839       case PGNTag:
10840         /* Reached start of next game in file */
10841         if (appData.debugMode)
10842           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10843         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10844           case MT_NONE:
10845           case MT_CHECK:
10846             break;
10847           case MT_CHECKMATE:
10848           case MT_STAINMATE:
10849             if (WhiteOnMove(currentMove)) {
10850                 GameEnds(BlackWins, "Black mates", GE_FILE);
10851             } else {
10852                 GameEnds(WhiteWins, "White mates", GE_FILE);
10853             }
10854             break;
10855           case MT_STALEMATE:
10856             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10857             break;
10858         }
10859         done = TRUE;
10860         break;
10861
10862       case PositionDiagram:     /* should not happen; ignore */
10863       case ElapsedTime:         /* ignore */
10864       case NAG:                 /* ignore */
10865         if (appData.debugMode)
10866           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10867                   yy_text, (int) moveType);
10868         return LoadGameOneMove(EndOfFile); /* tail recursion */
10869
10870       case IllegalMove:
10871         if (appData.testLegality) {
10872             if (appData.debugMode)
10873               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10874             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10875                     (forwardMostMove / 2) + 1,
10876                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10877             DisplayError(move, 0);
10878             done = TRUE;
10879         } else {
10880             if (appData.debugMode)
10881               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10882                       yy_text, currentMoveString);
10883             fromX = currentMoveString[0] - AAA;
10884             fromY = currentMoveString[1] - ONE;
10885             toX = currentMoveString[2] - AAA;
10886             toY = currentMoveString[3] - ONE;
10887             promoChar = currentMoveString[4];
10888         }
10889         break;
10890
10891       case AmbiguousMove:
10892         if (appData.debugMode)
10893           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10894         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10895                 (forwardMostMove / 2) + 1,
10896                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10897         DisplayError(move, 0);
10898         done = TRUE;
10899         break;
10900
10901       default:
10902       case ImpossibleMove:
10903         if (appData.debugMode)
10904           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10905         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10906                 (forwardMostMove / 2) + 1,
10907                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10908         DisplayError(move, 0);
10909         done = TRUE;
10910         break;
10911     }
10912
10913     if (done) {
10914         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10915             DrawPosition(FALSE, boards[currentMove]);
10916             DisplayBothClocks();
10917             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10918               DisplayComment(currentMove - 1, commentList[currentMove]);
10919         }
10920         (void) StopLoadGameTimer();
10921         gameFileFP = NULL;
10922         cmailOldMove = forwardMostMove;
10923         return FALSE;
10924     } else {
10925         /* currentMoveString is set as a side-effect of yylex */
10926
10927         thinkOutput[0] = NULLCHAR;
10928         MakeMove(fromX, fromY, toX, toY, promoChar);
10929         currentMove = forwardMostMove;
10930         return TRUE;
10931     }
10932 }
10933
10934 /* Load the nth game from the given file */
10935 int
10936 LoadGameFromFile(filename, n, title, useList)
10937      char *filename;
10938      int n;
10939      char *title;
10940      /*Boolean*/ int useList;
10941 {
10942     FILE *f;
10943     char buf[MSG_SIZ];
10944
10945     if (strcmp(filename, "-") == 0) {
10946         f = stdin;
10947         title = "stdin";
10948     } else {
10949         f = fopen(filename, "rb");
10950         if (f == NULL) {
10951           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10952             DisplayError(buf, errno);
10953             return FALSE;
10954         }
10955     }
10956     if (fseek(f, 0, 0) == -1) {
10957         /* f is not seekable; probably a pipe */
10958         useList = FALSE;
10959     }
10960     if (useList && n == 0) {
10961         int error = GameListBuild(f);
10962         if (error) {
10963             DisplayError(_("Cannot build game list"), error);
10964         } else if (!ListEmpty(&gameList) &&
10965                    ((ListGame *) gameList.tailPred)->number > 1) {
10966             GameListPopUp(f, title);
10967             return TRUE;
10968         }
10969         GameListDestroy();
10970         n = 1;
10971     }
10972     if (n == 0) n = 1;
10973     return LoadGame(f, n, title, FALSE);
10974 }
10975
10976
10977 void
10978 MakeRegisteredMove()
10979 {
10980     int fromX, fromY, toX, toY;
10981     char promoChar;
10982     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10983         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10984           case CMAIL_MOVE:
10985           case CMAIL_DRAW:
10986             if (appData.debugMode)
10987               fprintf(debugFP, "Restoring %s for game %d\n",
10988                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10989
10990             thinkOutput[0] = NULLCHAR;
10991             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10992             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10993             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10994             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10995             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10996             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10997             MakeMove(fromX, fromY, toX, toY, promoChar);
10998             ShowMove(fromX, fromY, toX, toY);
10999
11000             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11001               case MT_NONE:
11002               case MT_CHECK:
11003                 break;
11004
11005               case MT_CHECKMATE:
11006               case MT_STAINMATE:
11007                 if (WhiteOnMove(currentMove)) {
11008                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11009                 } else {
11010                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11011                 }
11012                 break;
11013
11014               case MT_STALEMATE:
11015                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11016                 break;
11017             }
11018
11019             break;
11020
11021           case CMAIL_RESIGN:
11022             if (WhiteOnMove(currentMove)) {
11023                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11024             } else {
11025                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11026             }
11027             break;
11028
11029           case CMAIL_ACCEPT:
11030             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11031             break;
11032
11033           default:
11034             break;
11035         }
11036     }
11037
11038     return;
11039 }
11040
11041 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11042 int
11043 CmailLoadGame(f, gameNumber, title, useList)
11044      FILE *f;
11045      int gameNumber;
11046      char *title;
11047      int useList;
11048 {
11049     int retVal;
11050
11051     if (gameNumber > nCmailGames) {
11052         DisplayError(_("No more games in this message"), 0);
11053         return FALSE;
11054     }
11055     if (f == lastLoadGameFP) {
11056         int offset = gameNumber - lastLoadGameNumber;
11057         if (offset == 0) {
11058             cmailMsg[0] = NULLCHAR;
11059             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11060                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11061                 nCmailMovesRegistered--;
11062             }
11063             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11064             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11065                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11066             }
11067         } else {
11068             if (! RegisterMove()) return FALSE;
11069         }
11070     }
11071
11072     retVal = LoadGame(f, gameNumber, title, useList);
11073
11074     /* Make move registered during previous look at this game, if any */
11075     MakeRegisteredMove();
11076
11077     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11078         commentList[currentMove]
11079           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11080         DisplayComment(currentMove - 1, commentList[currentMove]);
11081     }
11082
11083     return retVal;
11084 }
11085
11086 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11087 int
11088 ReloadGame(offset)
11089      int offset;
11090 {
11091     int gameNumber = lastLoadGameNumber + offset;
11092     if (lastLoadGameFP == NULL) {
11093         DisplayError(_("No game has been loaded yet"), 0);
11094         return FALSE;
11095     }
11096     if (gameNumber <= 0) {
11097         DisplayError(_("Can't back up any further"), 0);
11098         return FALSE;
11099     }
11100     if (cmailMsgLoaded) {
11101         return CmailLoadGame(lastLoadGameFP, gameNumber,
11102                              lastLoadGameTitle, lastLoadGameUseList);
11103     } else {
11104         return LoadGame(lastLoadGameFP, gameNumber,
11105                         lastLoadGameTitle, lastLoadGameUseList);
11106     }
11107 }
11108
11109 int keys[EmptySquare+1];
11110
11111 int
11112 PositionMatches(Board b1, Board b2)
11113 {
11114     int r, f, sum=0;
11115     switch(appData.searchMode) {
11116         case 1: return CompareWithRights(b1, b2);
11117         case 2:
11118             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11119                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11120             }
11121             return TRUE;
11122         case 3:
11123             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11124               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11125                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11126             }
11127             return sum==0;
11128         case 4:
11129             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11130                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11131             }
11132             return sum==0;
11133     }
11134     return TRUE;
11135 }
11136
11137 GameInfo dummyInfo;
11138
11139 int GameContainsPosition(FILE *f, ListGame *lg)
11140 {
11141     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11142     int fromX, fromY, toX, toY;
11143     char promoChar;
11144     static int initDone=FALSE;
11145
11146     if(!initDone) {
11147         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11148         initDone = TRUE;
11149     }
11150     dummyInfo.variant = VariantNormal;
11151     FREE(dummyInfo.fen); dummyInfo.fen = NULL;
11152     dummyInfo.whiteRating = 0;
11153     dummyInfo.blackRating = 0;
11154     FREE(dummyInfo.date); dummyInfo.date = NULL;
11155     fseek(f, lg->offset, 0);
11156     yynewfile(f);
11157     CopyBoard(boards[scratch], initialPosition); // default start position
11158     while(1) {
11159         yyboardindex = scratch + (plyNr&1);
11160       quickFlag = 1;
11161         next = Myylex();
11162       quickFlag = 0;
11163         switch(next) {
11164             case PGNTag:
11165                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11166 #if 0
11167                 ParsePGNTag(yy_text, &dummyInfo); // this has a bad memory leak...
11168                 if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL;
11169 #else
11170                 // do it ourselves avoiding malloc
11171                 { char *p = yy_text+1, *q;
11172                   while(!isdigit(*p) && !isalpha(*p)) p++;
11173                   q  = p; while(*p != ' ' && *p != '\t' && *p != '\n') p++;
11174                   *p = NULLCHAR;
11175                   if(!StrCaseCmp(q, "Date") && (p = strchr(p+1, '"'))) { if(atoi(p+1) < appData.dateThreshold) return -1; } else
11176                   if(!StrCaseCmp(q, "Variant")  &&  (p = strchr(p+1, '"'))) dummyInfo.variant = StringToVariant(p+1); else
11177                   if(!StrCaseCmp(q, "WhiteElo")  && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11178                   if(!StrCaseCmp(q, "BlackElo")  && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11179                   if(!StrCaseCmp(q, "WhiteUSCF") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11180                   if(!StrCaseCmp(q, "BlackUSCF") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11181                   if(!StrCaseCmp(q, "FEN")  && (p = strchr(p+1, '"'))) ParseFEN(boards[scratch], &btm, p+1);
11182                 }
11183 #endif
11184             default:
11185                 continue;
11186
11187             case XBoardGame:
11188             case GNUChessGame:
11189                 if(plyNr) return -1; // after we have seen moves, this is for new game
11190               continue;
11191
11192             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11193             case ImpossibleMove:
11194             case WhiteWins: // game ends here with these four
11195             case BlackWins:
11196             case GameIsDrawn:
11197             case GameUnfinished:
11198                 return -1;
11199
11200             case IllegalMove:
11201                 if(appData.testLegality) return -1;
11202             case WhiteCapturesEnPassant:
11203             case BlackCapturesEnPassant:
11204             case WhitePromotion:
11205             case BlackPromotion:
11206             case WhiteNonPromotion:
11207             case BlackNonPromotion:
11208             case NormalMove:
11209             case WhiteKingSideCastle:
11210             case WhiteQueenSideCastle:
11211             case BlackKingSideCastle:
11212             case BlackQueenSideCastle:
11213             case WhiteKingSideCastleWild:
11214             case WhiteQueenSideCastleWild:
11215             case BlackKingSideCastleWild:
11216             case BlackQueenSideCastleWild:
11217             case WhiteHSideCastleFR:
11218             case WhiteASideCastleFR:
11219             case BlackHSideCastleFR:
11220             case BlackASideCastleFR:
11221                 fromX = currentMoveString[0] - AAA;
11222                 fromY = currentMoveString[1] - ONE;
11223                 toX = currentMoveString[2] - AAA;
11224                 toY = currentMoveString[3] - ONE;
11225                 promoChar = currentMoveString[4];
11226                 break;
11227             case WhiteDrop:
11228             case BlackDrop:
11229                 fromX = next == WhiteDrop ?
11230                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11231                   (int) CharToPiece(ToLower(currentMoveString[0]));
11232                 fromY = DROP_RANK;
11233                 toX = currentMoveString[2] - AAA;
11234                 toY = currentMoveString[3] - ONE;
11235                 break;
11236         }
11237         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11238         if(plyNr == 0) { // but first figure out variant and initial position
11239             if(dummyInfo.variant != gameInfo.variant) return -1; // wrong variant
11240             if(appData.eloThreshold1 && (dummyInfo.whiteRating < appData.eloThreshold1 && dummyInfo.blackRating < appData.eloThreshold1)) return -1;
11241             if(appData.eloThreshold2 && (dummyInfo.whiteRating < appData.eloThreshold2 || dummyInfo.blackRating < appData.eloThreshold2)) return -1;
11242             if(appData.dateThreshold && (!dummyInfo.date || atoi(dummyInfo.date) < appData.dateThreshold)) return -1;
11243             if(btm) CopyBoard(boards[scratch+1], boards[scratch]), plyNr++;
11244             if(PositionMatches(boards[scratch + plyNr], boards[currentMove])) return plyNr;
11245         }
11246         CopyBoard(boards[scratch + (plyNr+1&1)], boards[scratch + (plyNr&1)]);
11247         plyNr++;
11248         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch + (plyNr&1)]);
11249         if(PositionMatches(boards[scratch + (plyNr&1)], boards[currentMove])) return plyNr;
11250     }
11251 }
11252
11253 /* Load the nth game from open file f */
11254 int
11255 LoadGame(f, gameNumber, title, useList)
11256      FILE *f;
11257      int gameNumber;
11258      char *title;
11259      int useList;
11260 {
11261     ChessMove cm;
11262     char buf[MSG_SIZ];
11263     int gn = gameNumber;
11264     ListGame *lg = NULL;
11265     int numPGNTags = 0;
11266     int err, pos = -1;
11267     GameMode oldGameMode;
11268     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11269
11270     if (appData.debugMode)
11271         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11272
11273     if (gameMode == Training )
11274         SetTrainingModeOff();
11275
11276     oldGameMode = gameMode;
11277     if (gameMode != BeginningOfGame) {
11278       Reset(FALSE, TRUE);
11279     }
11280
11281     gameFileFP = f;
11282     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11283         fclose(lastLoadGameFP);
11284     }
11285
11286     if (useList) {
11287         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11288
11289         if (lg) {
11290             fseek(f, lg->offset, 0);
11291             GameListHighlight(gameNumber);
11292             pos = lg->position;
11293             gn = 1;
11294         }
11295         else {
11296             DisplayError(_("Game number out of range"), 0);
11297             return FALSE;
11298         }
11299     } else {
11300         GameListDestroy();
11301         if (fseek(f, 0, 0) == -1) {
11302             if (f == lastLoadGameFP ?
11303                 gameNumber == lastLoadGameNumber + 1 :
11304                 gameNumber == 1) {
11305                 gn = 1;
11306             } else {
11307                 DisplayError(_("Can't seek on game file"), 0);
11308                 return FALSE;
11309             }
11310         }
11311     }
11312     lastLoadGameFP = f;
11313     lastLoadGameNumber = gameNumber;
11314     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11315     lastLoadGameUseList = useList;
11316
11317     yynewfile(f);
11318
11319     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11320       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11321                 lg->gameInfo.black);
11322             DisplayTitle(buf);
11323     } else if (*title != NULLCHAR) {
11324         if (gameNumber > 1) {
11325           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11326             DisplayTitle(buf);
11327         } else {
11328             DisplayTitle(title);
11329         }
11330     }
11331
11332     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11333         gameMode = PlayFromGameFile;
11334         ModeHighlight();
11335     }
11336
11337     currentMove = forwardMostMove = backwardMostMove = 0;
11338     CopyBoard(boards[0], initialPosition);
11339     StopClocks();
11340
11341     /*
11342      * Skip the first gn-1 games in the file.
11343      * Also skip over anything that precedes an identifiable
11344      * start of game marker, to avoid being confused by
11345      * garbage at the start of the file.  Currently
11346      * recognized start of game markers are the move number "1",
11347      * the pattern "gnuchess .* game", the pattern
11348      * "^[#;%] [^ ]* game file", and a PGN tag block.
11349      * A game that starts with one of the latter two patterns
11350      * will also have a move number 1, possibly
11351      * following a position diagram.
11352      * 5-4-02: Let's try being more lenient and allowing a game to
11353      * start with an unnumbered move.  Does that break anything?
11354      */
11355     cm = lastLoadGameStart = EndOfFile;
11356     while (gn > 0) {
11357         yyboardindex = forwardMostMove;
11358         cm = (ChessMove) Myylex();
11359         switch (cm) {
11360           case EndOfFile:
11361             if (cmailMsgLoaded) {
11362                 nCmailGames = CMAIL_MAX_GAMES - gn;
11363             } else {
11364                 Reset(TRUE, TRUE);
11365                 DisplayError(_("Game not found in file"), 0);
11366             }
11367             return FALSE;
11368
11369           case GNUChessGame:
11370           case XBoardGame:
11371             gn--;
11372             lastLoadGameStart = cm;
11373             break;
11374
11375           case MoveNumberOne:
11376             switch (lastLoadGameStart) {
11377               case GNUChessGame:
11378               case XBoardGame:
11379               case PGNTag:
11380                 break;
11381               case MoveNumberOne:
11382               case EndOfFile:
11383                 gn--;           /* count this game */
11384                 lastLoadGameStart = cm;
11385                 break;
11386               default:
11387                 /* impossible */
11388                 break;
11389             }
11390             break;
11391
11392           case PGNTag:
11393             switch (lastLoadGameStart) {
11394               case GNUChessGame:
11395               case PGNTag:
11396               case MoveNumberOne:
11397               case EndOfFile:
11398                 gn--;           /* count this game */
11399                 lastLoadGameStart = cm;
11400                 break;
11401               case XBoardGame:
11402                 lastLoadGameStart = cm; /* game counted already */
11403                 break;
11404               default:
11405                 /* impossible */
11406                 break;
11407             }
11408             if (gn > 0) {
11409                 do {
11410                     yyboardindex = forwardMostMove;
11411                     cm = (ChessMove) Myylex();
11412                 } while (cm == PGNTag || cm == Comment);
11413             }
11414             break;
11415
11416           case WhiteWins:
11417           case BlackWins:
11418           case GameIsDrawn:
11419             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11420                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11421                     != CMAIL_OLD_RESULT) {
11422                     nCmailResults ++ ;
11423                     cmailResult[  CMAIL_MAX_GAMES
11424                                 - gn - 1] = CMAIL_OLD_RESULT;
11425                 }
11426             }
11427             break;
11428
11429           case NormalMove:
11430             /* Only a NormalMove can be at the start of a game
11431              * without a position diagram. */
11432             if (lastLoadGameStart == EndOfFile ) {
11433               gn--;
11434               lastLoadGameStart = MoveNumberOne;
11435             }
11436             break;
11437
11438           default:
11439             break;
11440         }
11441     }
11442
11443     if (appData.debugMode)
11444       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11445
11446     if (cm == XBoardGame) {
11447         /* Skip any header junk before position diagram and/or move 1 */
11448         for (;;) {
11449             yyboardindex = forwardMostMove;
11450             cm = (ChessMove) Myylex();
11451
11452             if (cm == EndOfFile ||
11453                 cm == GNUChessGame || cm == XBoardGame) {
11454                 /* Empty game; pretend end-of-file and handle later */
11455                 cm = EndOfFile;
11456                 break;
11457             }
11458
11459             if (cm == MoveNumberOne || cm == PositionDiagram ||
11460                 cm == PGNTag || cm == Comment)
11461               break;
11462         }
11463     } else if (cm == GNUChessGame) {
11464         if (gameInfo.event != NULL) {
11465             free(gameInfo.event);
11466         }
11467         gameInfo.event = StrSave(yy_text);
11468     }
11469
11470     startedFromSetupPosition = FALSE;
11471     while (cm == PGNTag) {
11472         if (appData.debugMode)
11473           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11474         err = ParsePGNTag(yy_text, &gameInfo);
11475         if (!err) numPGNTags++;
11476
11477         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11478         if(gameInfo.variant != oldVariant) {
11479             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11480             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11481             InitPosition(TRUE);
11482             oldVariant = gameInfo.variant;
11483             if (appData.debugMode)
11484               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11485         }
11486
11487
11488         if (gameInfo.fen != NULL) {
11489           Board initial_position;
11490           startedFromSetupPosition = TRUE;
11491           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11492             Reset(TRUE, TRUE);
11493             DisplayError(_("Bad FEN position in file"), 0);
11494             return FALSE;
11495           }
11496           CopyBoard(boards[0], initial_position);
11497           if (blackPlaysFirst) {
11498             currentMove = forwardMostMove = backwardMostMove = 1;
11499             CopyBoard(boards[1], initial_position);
11500             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11501             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11502             timeRemaining[0][1] = whiteTimeRemaining;
11503             timeRemaining[1][1] = blackTimeRemaining;
11504             if (commentList[0] != NULL) {
11505               commentList[1] = commentList[0];
11506               commentList[0] = NULL;
11507             }
11508           } else {
11509             currentMove = forwardMostMove = backwardMostMove = 0;
11510           }
11511           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11512           {   int i;
11513               initialRulePlies = FENrulePlies;
11514               for( i=0; i< nrCastlingRights; i++ )
11515                   initialRights[i] = initial_position[CASTLING][i];
11516           }
11517           yyboardindex = forwardMostMove;
11518           free(gameInfo.fen);
11519           gameInfo.fen = NULL;
11520         }
11521
11522         yyboardindex = forwardMostMove;
11523         cm = (ChessMove) Myylex();
11524
11525         /* Handle comments interspersed among the tags */
11526         while (cm == Comment) {
11527             char *p;
11528             if (appData.debugMode)
11529               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11530             p = yy_text;
11531             AppendComment(currentMove, p, FALSE);
11532             yyboardindex = forwardMostMove;
11533             cm = (ChessMove) Myylex();
11534         }
11535     }
11536
11537     /* don't rely on existence of Event tag since if game was
11538      * pasted from clipboard the Event tag may not exist
11539      */
11540     if (numPGNTags > 0){
11541         char *tags;
11542         if (gameInfo.variant == VariantNormal) {
11543           VariantClass v = StringToVariant(gameInfo.event);
11544           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11545           if(v < VariantShogi) gameInfo.variant = v;
11546         }
11547         if (!matchMode) {
11548           if( appData.autoDisplayTags ) {
11549             tags = PGNTags(&gameInfo);
11550             TagsPopUp(tags, CmailMsg());
11551             free(tags);
11552           }
11553         }
11554     } else {
11555         /* Make something up, but don't display it now */
11556         SetGameInfo();
11557         TagsPopDown();
11558     }
11559
11560     if (cm == PositionDiagram) {
11561         int i, j;
11562         char *p;
11563         Board initial_position;
11564
11565         if (appData.debugMode)
11566           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11567
11568         if (!startedFromSetupPosition) {
11569             p = yy_text;
11570             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11571               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11572                 switch (*p) {
11573                   case '{':
11574                   case '[':
11575                   case '-':
11576                   case ' ':
11577                   case '\t':
11578                   case '\n':
11579                   case '\r':
11580                     break;
11581                   default:
11582                     initial_position[i][j++] = CharToPiece(*p);
11583                     break;
11584                 }
11585             while (*p == ' ' || *p == '\t' ||
11586                    *p == '\n' || *p == '\r') p++;
11587
11588             if (strncmp(p, "black", strlen("black"))==0)
11589               blackPlaysFirst = TRUE;
11590             else
11591               blackPlaysFirst = FALSE;
11592             startedFromSetupPosition = TRUE;
11593
11594             CopyBoard(boards[0], initial_position);
11595             if (blackPlaysFirst) {
11596                 currentMove = forwardMostMove = backwardMostMove = 1;
11597                 CopyBoard(boards[1], initial_position);
11598                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11599                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11600                 timeRemaining[0][1] = whiteTimeRemaining;
11601                 timeRemaining[1][1] = blackTimeRemaining;
11602                 if (commentList[0] != NULL) {
11603                     commentList[1] = commentList[0];
11604                     commentList[0] = NULL;
11605                 }
11606             } else {
11607                 currentMove = forwardMostMove = backwardMostMove = 0;
11608             }
11609         }
11610         yyboardindex = forwardMostMove;
11611         cm = (ChessMove) Myylex();
11612     }
11613
11614     if (first.pr == NoProc) {
11615         StartChessProgram(&first);
11616     }
11617     InitChessProgram(&first, FALSE);
11618     SendToProgram("force\n", &first);
11619     if (startedFromSetupPosition) {
11620         SendBoard(&first, forwardMostMove);
11621     if (appData.debugMode) {
11622         fprintf(debugFP, "Load Game\n");
11623     }
11624         DisplayBothClocks();
11625     }
11626
11627     /* [HGM] server: flag to write setup moves in broadcast file as one */
11628     loadFlag = appData.suppressLoadMoves;
11629
11630     while (cm == Comment) {
11631         char *p;
11632         if (appData.debugMode)
11633           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11634         p = yy_text;
11635         AppendComment(currentMove, p, FALSE);
11636         yyboardindex = forwardMostMove;
11637         cm = (ChessMove) Myylex();
11638     }
11639
11640     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11641         cm == WhiteWins || cm == BlackWins ||
11642         cm == GameIsDrawn || cm == GameUnfinished) {
11643         DisplayMessage("", _("No moves in game"));
11644         if (cmailMsgLoaded) {
11645             if (appData.debugMode)
11646               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11647             ClearHighlights();
11648             flipView = FALSE;
11649         }
11650         DrawPosition(FALSE, boards[currentMove]);
11651         DisplayBothClocks();
11652         gameMode = EditGame;
11653         ModeHighlight();
11654         gameFileFP = NULL;
11655         cmailOldMove = 0;
11656         return TRUE;
11657     }
11658
11659     // [HGM] PV info: routine tests if comment empty
11660     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11661         DisplayComment(currentMove - 1, commentList[currentMove]);
11662     }
11663     if (!matchMode && appData.timeDelay != 0)
11664       DrawPosition(FALSE, boards[currentMove]);
11665
11666     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11667       programStats.ok_to_send = 1;
11668     }
11669
11670     /* if the first token after the PGN tags is a move
11671      * and not move number 1, retrieve it from the parser
11672      */
11673     if (cm != MoveNumberOne)
11674         LoadGameOneMove(cm);
11675
11676     /* load the remaining moves from the file */
11677     while (LoadGameOneMove(EndOfFile)) {
11678       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11679       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11680     }
11681
11682     /* rewind to the start of the game */
11683     currentMove = backwardMostMove;
11684
11685     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11686
11687     if (oldGameMode == AnalyzeFile ||
11688         oldGameMode == AnalyzeMode) {
11689       AnalyzeFileEvent();
11690     }
11691
11692     if (!matchMode && pos >= 0) {
11693         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11694     } else
11695     if (matchMode || appData.timeDelay == 0) {
11696       ToEndEvent();
11697     } else if (appData.timeDelay > 0) {
11698       AutoPlayGameLoop();
11699     }
11700
11701     if (appData.debugMode)
11702         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11703
11704     loadFlag = 0; /* [HGM] true game starts */
11705     return TRUE;
11706 }
11707
11708 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11709 int
11710 ReloadPosition(offset)
11711      int offset;
11712 {
11713     int positionNumber = lastLoadPositionNumber + offset;
11714     if (lastLoadPositionFP == NULL) {
11715         DisplayError(_("No position has been loaded yet"), 0);
11716         return FALSE;
11717     }
11718     if (positionNumber <= 0) {
11719         DisplayError(_("Can't back up any further"), 0);
11720         return FALSE;
11721     }
11722     return LoadPosition(lastLoadPositionFP, positionNumber,
11723                         lastLoadPositionTitle);
11724 }
11725
11726 /* Load the nth position from the given file */
11727 int
11728 LoadPositionFromFile(filename, n, title)
11729      char *filename;
11730      int n;
11731      char *title;
11732 {
11733     FILE *f;
11734     char buf[MSG_SIZ];
11735
11736     if (strcmp(filename, "-") == 0) {
11737         return LoadPosition(stdin, n, "stdin");
11738     } else {
11739         f = fopen(filename, "rb");
11740         if (f == NULL) {
11741             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11742             DisplayError(buf, errno);
11743             return FALSE;
11744         } else {
11745             return LoadPosition(f, n, title);
11746         }
11747     }
11748 }
11749
11750 /* Load the nth position from the given open file, and close it */
11751 int
11752 LoadPosition(f, positionNumber, title)
11753      FILE *f;
11754      int positionNumber;
11755      char *title;
11756 {
11757     char *p, line[MSG_SIZ];
11758     Board initial_position;
11759     int i, j, fenMode, pn;
11760
11761     if (gameMode == Training )
11762         SetTrainingModeOff();
11763
11764     if (gameMode != BeginningOfGame) {
11765         Reset(FALSE, TRUE);
11766     }
11767     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11768         fclose(lastLoadPositionFP);
11769     }
11770     if (positionNumber == 0) positionNumber = 1;
11771     lastLoadPositionFP = f;
11772     lastLoadPositionNumber = positionNumber;
11773     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11774     if (first.pr == NoProc) {
11775       StartChessProgram(&first);
11776       InitChessProgram(&first, FALSE);
11777     }
11778     pn = positionNumber;
11779     if (positionNumber < 0) {
11780         /* Negative position number means to seek to that byte offset */
11781         if (fseek(f, -positionNumber, 0) == -1) {
11782             DisplayError(_("Can't seek on position file"), 0);
11783             return FALSE;
11784         };
11785         pn = 1;
11786     } else {
11787         if (fseek(f, 0, 0) == -1) {
11788             if (f == lastLoadPositionFP ?
11789                 positionNumber == lastLoadPositionNumber + 1 :
11790                 positionNumber == 1) {
11791                 pn = 1;
11792             } else {
11793                 DisplayError(_("Can't seek on position file"), 0);
11794                 return FALSE;
11795             }
11796         }
11797     }
11798     /* See if this file is FEN or old-style xboard */
11799     if (fgets(line, MSG_SIZ, f) == NULL) {
11800         DisplayError(_("Position not found in file"), 0);
11801         return FALSE;
11802     }
11803     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11804     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11805
11806     if (pn >= 2) {
11807         if (fenMode || line[0] == '#') pn--;
11808         while (pn > 0) {
11809             /* skip positions before number pn */
11810             if (fgets(line, MSG_SIZ, f) == NULL) {
11811                 Reset(TRUE, TRUE);
11812                 DisplayError(_("Position not found in file"), 0);
11813                 return FALSE;
11814             }
11815             if (fenMode || line[0] == '#') pn--;
11816         }
11817     }
11818
11819     if (fenMode) {
11820         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11821             DisplayError(_("Bad FEN position in file"), 0);
11822             return FALSE;
11823         }
11824     } else {
11825         (void) fgets(line, MSG_SIZ, f);
11826         (void) fgets(line, MSG_SIZ, f);
11827
11828         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11829             (void) fgets(line, MSG_SIZ, f);
11830             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11831                 if (*p == ' ')
11832                   continue;
11833                 initial_position[i][j++] = CharToPiece(*p);
11834             }
11835         }
11836
11837         blackPlaysFirst = FALSE;
11838         if (!feof(f)) {
11839             (void) fgets(line, MSG_SIZ, f);
11840             if (strncmp(line, "black", strlen("black"))==0)
11841               blackPlaysFirst = TRUE;
11842         }
11843     }
11844     startedFromSetupPosition = TRUE;
11845
11846     SendToProgram("force\n", &first);
11847     CopyBoard(boards[0], initial_position);
11848     if (blackPlaysFirst) {
11849         currentMove = forwardMostMove = backwardMostMove = 1;
11850         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11851         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11852         CopyBoard(boards[1], initial_position);
11853         DisplayMessage("", _("Black to play"));
11854     } else {
11855         currentMove = forwardMostMove = backwardMostMove = 0;
11856         DisplayMessage("", _("White to play"));
11857     }
11858     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11859     SendBoard(&first, forwardMostMove);
11860     if (appData.debugMode) {
11861 int i, j;
11862   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11863   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11864         fprintf(debugFP, "Load Position\n");
11865     }
11866
11867     if (positionNumber > 1) {
11868       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11869         DisplayTitle(line);
11870     } else {
11871         DisplayTitle(title);
11872     }
11873     gameMode = EditGame;
11874     ModeHighlight();
11875     ResetClocks();
11876     timeRemaining[0][1] = whiteTimeRemaining;
11877     timeRemaining[1][1] = blackTimeRemaining;
11878     DrawPosition(FALSE, boards[currentMove]);
11879
11880     return TRUE;
11881 }
11882
11883
11884 void
11885 CopyPlayerNameIntoFileName(dest, src)
11886      char **dest, *src;
11887 {
11888     while (*src != NULLCHAR && *src != ',') {
11889         if (*src == ' ') {
11890             *(*dest)++ = '_';
11891             src++;
11892         } else {
11893             *(*dest)++ = *src++;
11894         }
11895     }
11896 }
11897
11898 char *DefaultFileName(ext)
11899      char *ext;
11900 {
11901     static char def[MSG_SIZ];
11902     char *p;
11903
11904     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11905         p = def;
11906         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11907         *p++ = '-';
11908         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11909         *p++ = '.';
11910         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11911     } else {
11912         def[0] = NULLCHAR;
11913     }
11914     return def;
11915 }
11916
11917 /* Save the current game to the given file */
11918 int
11919 SaveGameToFile(filename, append)
11920      char *filename;
11921      int append;
11922 {
11923     FILE *f;
11924     char buf[MSG_SIZ];
11925     int result;
11926
11927     if (strcmp(filename, "-") == 0) {
11928         return SaveGame(stdout, 0, NULL);
11929     } else {
11930         f = fopen(filename, append ? "a" : "w");
11931         if (f == NULL) {
11932             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11933             DisplayError(buf, errno);
11934             return FALSE;
11935         } else {
11936             safeStrCpy(buf, lastMsg, MSG_SIZ);
11937             DisplayMessage(_("Waiting for access to save file"), "");
11938             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11939             DisplayMessage(_("Saving game"), "");
11940             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11941             result = SaveGame(f, 0, NULL);
11942             DisplayMessage(buf, "");
11943             return result;
11944         }
11945     }
11946 }
11947
11948 char *
11949 SavePart(str)
11950      char *str;
11951 {
11952     static char buf[MSG_SIZ];
11953     char *p;
11954
11955     p = strchr(str, ' ');
11956     if (p == NULL) return str;
11957     strncpy(buf, str, p - str);
11958     buf[p - str] = NULLCHAR;
11959     return buf;
11960 }
11961
11962 #define PGN_MAX_LINE 75
11963
11964 #define PGN_SIDE_WHITE  0
11965 #define PGN_SIDE_BLACK  1
11966
11967 /* [AS] */
11968 static int FindFirstMoveOutOfBook( int side )
11969 {
11970     int result = -1;
11971
11972     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11973         int index = backwardMostMove;
11974         int has_book_hit = 0;
11975
11976         if( (index % 2) != side ) {
11977             index++;
11978         }
11979
11980         while( index < forwardMostMove ) {
11981             /* Check to see if engine is in book */
11982             int depth = pvInfoList[index].depth;
11983             int score = pvInfoList[index].score;
11984             int in_book = 0;
11985
11986             if( depth <= 2 ) {
11987                 in_book = 1;
11988             }
11989             else if( score == 0 && depth == 63 ) {
11990                 in_book = 1; /* Zappa */
11991             }
11992             else if( score == 2 && depth == 99 ) {
11993                 in_book = 1; /* Abrok */
11994             }
11995
11996             has_book_hit += in_book;
11997
11998             if( ! in_book ) {
11999                 result = index;
12000
12001                 break;
12002             }
12003
12004             index += 2;
12005         }
12006     }
12007
12008     return result;
12009 }
12010
12011 /* [AS] */
12012 void GetOutOfBookInfo( char * buf )
12013 {
12014     int oob[2];
12015     int i;
12016     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12017
12018     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12019     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12020
12021     *buf = '\0';
12022
12023     if( oob[0] >= 0 || oob[1] >= 0 ) {
12024         for( i=0; i<2; i++ ) {
12025             int idx = oob[i];
12026
12027             if( idx >= 0 ) {
12028                 if( i > 0 && oob[0] >= 0 ) {
12029                     strcat( buf, "   " );
12030                 }
12031
12032                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12033                 sprintf( buf+strlen(buf), "%s%.2f",
12034                     pvInfoList[idx].score >= 0 ? "+" : "",
12035                     pvInfoList[idx].score / 100.0 );
12036             }
12037         }
12038     }
12039 }
12040
12041 /* Save game in PGN style and close the file */
12042 int
12043 SaveGamePGN(f)
12044      FILE *f;
12045 {
12046     int i, offset, linelen, newblock;
12047     time_t tm;
12048 //    char *movetext;
12049     char numtext[32];
12050     int movelen, numlen, blank;
12051     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12052
12053     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12054
12055     tm = time((time_t *) NULL);
12056
12057     PrintPGNTags(f, &gameInfo);
12058
12059     if (backwardMostMove > 0 || startedFromSetupPosition) {
12060         char *fen = PositionToFEN(backwardMostMove, NULL);
12061         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12062         fprintf(f, "\n{--------------\n");
12063         PrintPosition(f, backwardMostMove);
12064         fprintf(f, "--------------}\n");
12065         free(fen);
12066     }
12067     else {
12068         /* [AS] Out of book annotation */
12069         if( appData.saveOutOfBookInfo ) {
12070             char buf[64];
12071
12072             GetOutOfBookInfo( buf );
12073
12074             if( buf[0] != '\0' ) {
12075                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12076             }
12077         }
12078
12079         fprintf(f, "\n");
12080     }
12081
12082     i = backwardMostMove;
12083     linelen = 0;
12084     newblock = TRUE;
12085
12086     while (i < forwardMostMove) {
12087         /* Print comments preceding this move */
12088         if (commentList[i] != NULL) {
12089             if (linelen > 0) fprintf(f, "\n");
12090             fprintf(f, "%s", commentList[i]);
12091             linelen = 0;
12092             newblock = TRUE;
12093         }
12094
12095         /* Format move number */
12096         if ((i % 2) == 0)
12097           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12098         else
12099           if (newblock)
12100             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12101           else
12102             numtext[0] = NULLCHAR;
12103
12104         numlen = strlen(numtext);
12105         newblock = FALSE;
12106
12107         /* Print move number */
12108         blank = linelen > 0 && numlen > 0;
12109         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12110             fprintf(f, "\n");
12111             linelen = 0;
12112             blank = 0;
12113         }
12114         if (blank) {
12115             fprintf(f, " ");
12116             linelen++;
12117         }
12118         fprintf(f, "%s", numtext);
12119         linelen += numlen;
12120
12121         /* Get move */
12122         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12123         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12124
12125         /* Print move */
12126         blank = linelen > 0 && movelen > 0;
12127         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12128             fprintf(f, "\n");
12129             linelen = 0;
12130             blank = 0;
12131         }
12132         if (blank) {
12133             fprintf(f, " ");
12134             linelen++;
12135         }
12136         fprintf(f, "%s", move_buffer);
12137         linelen += movelen;
12138
12139         /* [AS] Add PV info if present */
12140         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12141             /* [HGM] add time */
12142             char buf[MSG_SIZ]; int seconds;
12143
12144             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12145
12146             if( seconds <= 0)
12147               buf[0] = 0;
12148             else
12149               if( seconds < 30 )
12150                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12151               else
12152                 {
12153                   seconds = (seconds + 4)/10; // round to full seconds
12154                   if( seconds < 60 )
12155                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12156                   else
12157                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12158                 }
12159
12160             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12161                       pvInfoList[i].score >= 0 ? "+" : "",
12162                       pvInfoList[i].score / 100.0,
12163                       pvInfoList[i].depth,
12164                       buf );
12165
12166             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12167
12168             /* Print score/depth */
12169             blank = linelen > 0 && movelen > 0;
12170             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12171                 fprintf(f, "\n");
12172                 linelen = 0;
12173                 blank = 0;
12174             }
12175             if (blank) {
12176                 fprintf(f, " ");
12177                 linelen++;
12178             }
12179             fprintf(f, "%s", move_buffer);
12180             linelen += movelen;
12181         }
12182
12183         i++;
12184     }
12185
12186     /* Start a new line */
12187     if (linelen > 0) fprintf(f, "\n");
12188
12189     /* Print comments after last move */
12190     if (commentList[i] != NULL) {
12191         fprintf(f, "%s\n", commentList[i]);
12192     }
12193
12194     /* Print result */
12195     if (gameInfo.resultDetails != NULL &&
12196         gameInfo.resultDetails[0] != NULLCHAR) {
12197         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12198                 PGNResult(gameInfo.result));
12199     } else {
12200         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12201     }
12202
12203     fclose(f);
12204     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12205     return TRUE;
12206 }
12207
12208 /* Save game in old style and close the file */
12209 int
12210 SaveGameOldStyle(f)
12211      FILE *f;
12212 {
12213     int i, offset;
12214     time_t tm;
12215
12216     tm = time((time_t *) NULL);
12217
12218     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12219     PrintOpponents(f);
12220
12221     if (backwardMostMove > 0 || startedFromSetupPosition) {
12222         fprintf(f, "\n[--------------\n");
12223         PrintPosition(f, backwardMostMove);
12224         fprintf(f, "--------------]\n");
12225     } else {
12226         fprintf(f, "\n");
12227     }
12228
12229     i = backwardMostMove;
12230     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12231
12232     while (i < forwardMostMove) {
12233         if (commentList[i] != NULL) {
12234             fprintf(f, "[%s]\n", commentList[i]);
12235         }
12236
12237         if ((i % 2) == 1) {
12238             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12239             i++;
12240         } else {
12241             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12242             i++;
12243             if (commentList[i] != NULL) {
12244                 fprintf(f, "\n");
12245                 continue;
12246             }
12247             if (i >= forwardMostMove) {
12248                 fprintf(f, "\n");
12249                 break;
12250             }
12251             fprintf(f, "%s\n", parseList[i]);
12252             i++;
12253         }
12254     }
12255
12256     if (commentList[i] != NULL) {
12257         fprintf(f, "[%s]\n", commentList[i]);
12258     }
12259
12260     /* This isn't really the old style, but it's close enough */
12261     if (gameInfo.resultDetails != NULL &&
12262         gameInfo.resultDetails[0] != NULLCHAR) {
12263         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12264                 gameInfo.resultDetails);
12265     } else {
12266         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12267     }
12268
12269     fclose(f);
12270     return TRUE;
12271 }
12272
12273 /* Save the current game to open file f and close the file */
12274 int
12275 SaveGame(f, dummy, dummy2)
12276      FILE *f;
12277      int dummy;
12278      char *dummy2;
12279 {
12280     if (gameMode == EditPosition) EditPositionDone(TRUE);
12281     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12282     if (appData.oldSaveStyle)
12283       return SaveGameOldStyle(f);
12284     else
12285       return SaveGamePGN(f);
12286 }
12287
12288 /* Save the current position to the given file */
12289 int
12290 SavePositionToFile(filename)
12291      char *filename;
12292 {
12293     FILE *f;
12294     char buf[MSG_SIZ];
12295
12296     if (strcmp(filename, "-") == 0) {
12297         return SavePosition(stdout, 0, NULL);
12298     } else {
12299         f = fopen(filename, "a");
12300         if (f == NULL) {
12301             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12302             DisplayError(buf, errno);
12303             return FALSE;
12304         } else {
12305             safeStrCpy(buf, lastMsg, MSG_SIZ);
12306             DisplayMessage(_("Waiting for access to save file"), "");
12307             flock(fileno(f), LOCK_EX); // [HGM] lock
12308             DisplayMessage(_("Saving position"), "");
12309             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12310             SavePosition(f, 0, NULL);
12311             DisplayMessage(buf, "");
12312             return TRUE;
12313         }
12314     }
12315 }
12316
12317 /* Save the current position to the given open file and close the file */
12318 int
12319 SavePosition(f, dummy, dummy2)
12320      FILE *f;
12321      int dummy;
12322      char *dummy2;
12323 {
12324     time_t tm;
12325     char *fen;
12326
12327     if (gameMode == EditPosition) EditPositionDone(TRUE);
12328     if (appData.oldSaveStyle) {
12329         tm = time((time_t *) NULL);
12330
12331         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12332         PrintOpponents(f);
12333         fprintf(f, "[--------------\n");
12334         PrintPosition(f, currentMove);
12335         fprintf(f, "--------------]\n");
12336     } else {
12337         fen = PositionToFEN(currentMove, NULL);
12338         fprintf(f, "%s\n", fen);
12339         free(fen);
12340     }
12341     fclose(f);
12342     return TRUE;
12343 }
12344
12345 void
12346 ReloadCmailMsgEvent(unregister)
12347      int unregister;
12348 {
12349 #if !WIN32
12350     static char *inFilename = NULL;
12351     static char *outFilename;
12352     int i;
12353     struct stat inbuf, outbuf;
12354     int status;
12355
12356     /* Any registered moves are unregistered if unregister is set, */
12357     /* i.e. invoked by the signal handler */
12358     if (unregister) {
12359         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12360             cmailMoveRegistered[i] = FALSE;
12361             if (cmailCommentList[i] != NULL) {
12362                 free(cmailCommentList[i]);
12363                 cmailCommentList[i] = NULL;
12364             }
12365         }
12366         nCmailMovesRegistered = 0;
12367     }
12368
12369     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12370         cmailResult[i] = CMAIL_NOT_RESULT;
12371     }
12372     nCmailResults = 0;
12373
12374     if (inFilename == NULL) {
12375         /* Because the filenames are static they only get malloced once  */
12376         /* and they never get freed                                      */
12377         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12378         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12379
12380         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12381         sprintf(outFilename, "%s.out", appData.cmailGameName);
12382     }
12383
12384     status = stat(outFilename, &outbuf);
12385     if (status < 0) {
12386         cmailMailedMove = FALSE;
12387     } else {
12388         status = stat(inFilename, &inbuf);
12389         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12390     }
12391
12392     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12393        counts the games, notes how each one terminated, etc.
12394
12395        It would be nice to remove this kludge and instead gather all
12396        the information while building the game list.  (And to keep it
12397        in the game list nodes instead of having a bunch of fixed-size
12398        parallel arrays.)  Note this will require getting each game's
12399        termination from the PGN tags, as the game list builder does
12400        not process the game moves.  --mann
12401        */
12402     cmailMsgLoaded = TRUE;
12403     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12404
12405     /* Load first game in the file or popup game menu */
12406     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12407
12408 #endif /* !WIN32 */
12409     return;
12410 }
12411
12412 int
12413 RegisterMove()
12414 {
12415     FILE *f;
12416     char string[MSG_SIZ];
12417
12418     if (   cmailMailedMove
12419         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12420         return TRUE;            /* Allow free viewing  */
12421     }
12422
12423     /* Unregister move to ensure that we don't leave RegisterMove        */
12424     /* with the move registered when the conditions for registering no   */
12425     /* longer hold                                                       */
12426     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12427         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12428         nCmailMovesRegistered --;
12429
12430         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12431           {
12432               free(cmailCommentList[lastLoadGameNumber - 1]);
12433               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12434           }
12435     }
12436
12437     if (cmailOldMove == -1) {
12438         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12439         return FALSE;
12440     }
12441
12442     if (currentMove > cmailOldMove + 1) {
12443         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12444         return FALSE;
12445     }
12446
12447     if (currentMove < cmailOldMove) {
12448         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12449         return FALSE;
12450     }
12451
12452     if (forwardMostMove > currentMove) {
12453         /* Silently truncate extra moves */
12454         TruncateGame();
12455     }
12456
12457     if (   (currentMove == cmailOldMove + 1)
12458         || (   (currentMove == cmailOldMove)
12459             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12460                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12461         if (gameInfo.result != GameUnfinished) {
12462             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12463         }
12464
12465         if (commentList[currentMove] != NULL) {
12466             cmailCommentList[lastLoadGameNumber - 1]
12467               = StrSave(commentList[currentMove]);
12468         }
12469         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12470
12471         if (appData.debugMode)
12472           fprintf(debugFP, "Saving %s for game %d\n",
12473                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12474
12475         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12476
12477         f = fopen(string, "w");
12478         if (appData.oldSaveStyle) {
12479             SaveGameOldStyle(f); /* also closes the file */
12480
12481             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12482             f = fopen(string, "w");
12483             SavePosition(f, 0, NULL); /* also closes the file */
12484         } else {
12485             fprintf(f, "{--------------\n");
12486             PrintPosition(f, currentMove);
12487             fprintf(f, "--------------}\n\n");
12488
12489             SaveGame(f, 0, NULL); /* also closes the file*/
12490         }
12491
12492         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12493         nCmailMovesRegistered ++;
12494     } else if (nCmailGames == 1) {
12495         DisplayError(_("You have not made a move yet"), 0);
12496         return FALSE;
12497     }
12498
12499     return TRUE;
12500 }
12501
12502 void
12503 MailMoveEvent()
12504 {
12505 #if !WIN32
12506     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12507     FILE *commandOutput;
12508     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12509     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12510     int nBuffers;
12511     int i;
12512     int archived;
12513     char *arcDir;
12514
12515     if (! cmailMsgLoaded) {
12516         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12517         return;
12518     }
12519
12520     if (nCmailGames == nCmailResults) {
12521         DisplayError(_("No unfinished games"), 0);
12522         return;
12523     }
12524
12525 #if CMAIL_PROHIBIT_REMAIL
12526     if (cmailMailedMove) {
12527       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);
12528         DisplayError(msg, 0);
12529         return;
12530     }
12531 #endif
12532
12533     if (! (cmailMailedMove || RegisterMove())) return;
12534
12535     if (   cmailMailedMove
12536         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12537       snprintf(string, MSG_SIZ, partCommandString,
12538                appData.debugMode ? " -v" : "", appData.cmailGameName);
12539         commandOutput = popen(string, "r");
12540
12541         if (commandOutput == NULL) {
12542             DisplayError(_("Failed to invoke cmail"), 0);
12543         } else {
12544             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12545                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12546             }
12547             if (nBuffers > 1) {
12548                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12549                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12550                 nBytes = MSG_SIZ - 1;
12551             } else {
12552                 (void) memcpy(msg, buffer, nBytes);
12553             }
12554             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12555
12556             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12557                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12558
12559                 archived = TRUE;
12560                 for (i = 0; i < nCmailGames; i ++) {
12561                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12562                         archived = FALSE;
12563                     }
12564                 }
12565                 if (   archived
12566                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12567                         != NULL)) {
12568                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12569                            arcDir,
12570                            appData.cmailGameName,
12571                            gameInfo.date);
12572                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12573                     cmailMsgLoaded = FALSE;
12574                 }
12575             }
12576
12577             DisplayInformation(msg);
12578             pclose(commandOutput);
12579         }
12580     } else {
12581         if ((*cmailMsg) != '\0') {
12582             DisplayInformation(cmailMsg);
12583         }
12584     }
12585
12586     return;
12587 #endif /* !WIN32 */
12588 }
12589
12590 char *
12591 CmailMsg()
12592 {
12593 #if WIN32
12594     return NULL;
12595 #else
12596     int  prependComma = 0;
12597     char number[5];
12598     char string[MSG_SIZ];       /* Space for game-list */
12599     int  i;
12600
12601     if (!cmailMsgLoaded) return "";
12602
12603     if (cmailMailedMove) {
12604       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12605     } else {
12606         /* Create a list of games left */
12607       snprintf(string, MSG_SIZ, "[");
12608         for (i = 0; i < nCmailGames; i ++) {
12609             if (! (   cmailMoveRegistered[i]
12610                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12611                 if (prependComma) {
12612                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12613                 } else {
12614                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12615                     prependComma = 1;
12616                 }
12617
12618                 strcat(string, number);
12619             }
12620         }
12621         strcat(string, "]");
12622
12623         if (nCmailMovesRegistered + nCmailResults == 0) {
12624             switch (nCmailGames) {
12625               case 1:
12626                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12627                 break;
12628
12629               case 2:
12630                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12631                 break;
12632
12633               default:
12634                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12635                          nCmailGames);
12636                 break;
12637             }
12638         } else {
12639             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12640               case 1:
12641                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12642                          string);
12643                 break;
12644
12645               case 0:
12646                 if (nCmailResults == nCmailGames) {
12647                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12648                 } else {
12649                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12650                 }
12651                 break;
12652
12653               default:
12654                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12655                          string);
12656             }
12657         }
12658     }
12659     return cmailMsg;
12660 #endif /* WIN32 */
12661 }
12662
12663 void
12664 ResetGameEvent()
12665 {
12666     if (gameMode == Training)
12667       SetTrainingModeOff();
12668
12669     Reset(TRUE, TRUE);
12670     cmailMsgLoaded = FALSE;
12671     if (appData.icsActive) {
12672       SendToICS(ics_prefix);
12673       SendToICS("refresh\n");
12674     }
12675 }
12676
12677 void
12678 ExitEvent(status)
12679      int status;
12680 {
12681     exiting++;
12682     if (exiting > 2) {
12683       /* Give up on clean exit */
12684       exit(status);
12685     }
12686     if (exiting > 1) {
12687       /* Keep trying for clean exit */
12688       return;
12689     }
12690
12691     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12692
12693     if (telnetISR != NULL) {
12694       RemoveInputSource(telnetISR);
12695     }
12696     if (icsPR != NoProc) {
12697       DestroyChildProcess(icsPR, TRUE);
12698     }
12699
12700     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12701     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12702
12703     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12704     /* make sure this other one finishes before killing it!                  */
12705     if(endingGame) { int count = 0;
12706         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12707         while(endingGame && count++ < 10) DoSleep(1);
12708         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12709     }
12710
12711     /* Kill off chess programs */
12712     if (first.pr != NoProc) {
12713         ExitAnalyzeMode();
12714
12715         DoSleep( appData.delayBeforeQuit );
12716         SendToProgram("quit\n", &first);
12717         DoSleep( appData.delayAfterQuit );
12718         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12719     }
12720     if (second.pr != NoProc) {
12721         DoSleep( appData.delayBeforeQuit );
12722         SendToProgram("quit\n", &second);
12723         DoSleep( appData.delayAfterQuit );
12724         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12725     }
12726     if (first.isr != NULL) {
12727         RemoveInputSource(first.isr);
12728     }
12729     if (second.isr != NULL) {
12730         RemoveInputSource(second.isr);
12731     }
12732
12733     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12734     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12735
12736     ShutDownFrontEnd();
12737     exit(status);
12738 }
12739
12740 void
12741 PauseEvent()
12742 {
12743     if (appData.debugMode)
12744         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12745     if (pausing) {
12746         pausing = FALSE;
12747         ModeHighlight();
12748         if (gameMode == MachinePlaysWhite ||
12749             gameMode == MachinePlaysBlack) {
12750             StartClocks();
12751         } else {
12752             DisplayBothClocks();
12753         }
12754         if (gameMode == PlayFromGameFile) {
12755             if (appData.timeDelay >= 0)
12756                 AutoPlayGameLoop();
12757         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12758             Reset(FALSE, TRUE);
12759             SendToICS(ics_prefix);
12760             SendToICS("refresh\n");
12761         } else if (currentMove < forwardMostMove) {
12762             ForwardInner(forwardMostMove);
12763         }
12764         pauseExamInvalid = FALSE;
12765     } else {
12766         switch (gameMode) {
12767           default:
12768             return;
12769           case IcsExamining:
12770             pauseExamForwardMostMove = forwardMostMove;
12771             pauseExamInvalid = FALSE;
12772             /* fall through */
12773           case IcsObserving:
12774           case IcsPlayingWhite:
12775           case IcsPlayingBlack:
12776             pausing = TRUE;
12777             ModeHighlight();
12778             return;
12779           case PlayFromGameFile:
12780             (void) StopLoadGameTimer();
12781             pausing = TRUE;
12782             ModeHighlight();
12783             break;
12784           case BeginningOfGame:
12785             if (appData.icsActive) return;
12786             /* else fall through */
12787           case MachinePlaysWhite:
12788           case MachinePlaysBlack:
12789           case TwoMachinesPlay:
12790             if (forwardMostMove == 0)
12791               return;           /* don't pause if no one has moved */
12792             if ((gameMode == MachinePlaysWhite &&
12793                  !WhiteOnMove(forwardMostMove)) ||
12794                 (gameMode == MachinePlaysBlack &&
12795                  WhiteOnMove(forwardMostMove))) {
12796                 StopClocks();
12797             }
12798             pausing = TRUE;
12799             ModeHighlight();
12800             break;
12801         }
12802     }
12803 }
12804
12805 void
12806 EditCommentEvent()
12807 {
12808     char title[MSG_SIZ];
12809
12810     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12811       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12812     } else {
12813       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12814                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12815                parseList[currentMove - 1]);
12816     }
12817
12818     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12819 }
12820
12821
12822 void
12823 EditTagsEvent()
12824 {
12825     char *tags = PGNTags(&gameInfo);
12826     bookUp = FALSE;
12827     EditTagsPopUp(tags, NULL);
12828     free(tags);
12829 }
12830
12831 void
12832 AnalyzeModeEvent()
12833 {
12834     if (appData.noChessProgram || gameMode == AnalyzeMode)
12835       return;
12836
12837     if (gameMode != AnalyzeFile) {
12838         if (!appData.icsEngineAnalyze) {
12839                EditGameEvent();
12840                if (gameMode != EditGame) return;
12841         }
12842         ResurrectChessProgram();
12843         SendToProgram("analyze\n", &first);
12844         first.analyzing = TRUE;
12845         /*first.maybeThinking = TRUE;*/
12846         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12847         EngineOutputPopUp();
12848     }
12849     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12850     pausing = FALSE;
12851     ModeHighlight();
12852     SetGameInfo();
12853
12854     StartAnalysisClock();
12855     GetTimeMark(&lastNodeCountTime);
12856     lastNodeCount = 0;
12857 }
12858
12859 void
12860 AnalyzeFileEvent()
12861 {
12862     if (appData.noChessProgram || gameMode == AnalyzeFile)
12863       return;
12864
12865     if (gameMode != AnalyzeMode) {
12866         EditGameEvent();
12867         if (gameMode != EditGame) return;
12868         ResurrectChessProgram();
12869         SendToProgram("analyze\n", &first);
12870         first.analyzing = TRUE;
12871         /*first.maybeThinking = TRUE;*/
12872         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12873         EngineOutputPopUp();
12874     }
12875     gameMode = AnalyzeFile;
12876     pausing = FALSE;
12877     ModeHighlight();
12878     SetGameInfo();
12879
12880     StartAnalysisClock();
12881     GetTimeMark(&lastNodeCountTime);
12882     lastNodeCount = 0;
12883 }
12884
12885 void
12886 MachineWhiteEvent()
12887 {
12888     char buf[MSG_SIZ];
12889     char *bookHit = NULL;
12890
12891     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12892       return;
12893
12894
12895     if (gameMode == PlayFromGameFile ||
12896         gameMode == TwoMachinesPlay  ||
12897         gameMode == Training         ||
12898         gameMode == AnalyzeMode      ||
12899         gameMode == EndOfGame)
12900         EditGameEvent();
12901
12902     if (gameMode == EditPosition)
12903         EditPositionDone(TRUE);
12904
12905     if (!WhiteOnMove(currentMove)) {
12906         DisplayError(_("It is not White's turn"), 0);
12907         return;
12908     }
12909
12910     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12911       ExitAnalyzeMode();
12912
12913     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12914         gameMode == AnalyzeFile)
12915         TruncateGame();
12916
12917     ResurrectChessProgram();    /* in case it isn't running */
12918     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12919         gameMode = MachinePlaysWhite;
12920         ResetClocks();
12921     } else
12922     gameMode = MachinePlaysWhite;
12923     pausing = FALSE;
12924     ModeHighlight();
12925     SetGameInfo();
12926     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12927     DisplayTitle(buf);
12928     if (first.sendName) {
12929       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12930       SendToProgram(buf, &first);
12931     }
12932     if (first.sendTime) {
12933       if (first.useColors) {
12934         SendToProgram("black\n", &first); /*gnu kludge*/
12935       }
12936       SendTimeRemaining(&first, TRUE);
12937     }
12938     if (first.useColors) {
12939       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12940     }
12941     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12942     SetMachineThinkingEnables();
12943     first.maybeThinking = TRUE;
12944     StartClocks();
12945     firstMove = FALSE;
12946
12947     if (appData.autoFlipView && !flipView) {
12948       flipView = !flipView;
12949       DrawPosition(FALSE, NULL);
12950       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12951     }
12952
12953     if(bookHit) { // [HGM] book: simulate book reply
12954         static char bookMove[MSG_SIZ]; // a bit generous?
12955
12956         programStats.nodes = programStats.depth = programStats.time =
12957         programStats.score = programStats.got_only_move = 0;
12958         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12959
12960         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12961         strcat(bookMove, bookHit);
12962         HandleMachineMove(bookMove, &first);
12963     }
12964 }
12965
12966 void
12967 MachineBlackEvent()
12968 {
12969   char buf[MSG_SIZ];
12970   char *bookHit = NULL;
12971
12972     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12973         return;
12974
12975
12976     if (gameMode == PlayFromGameFile ||
12977         gameMode == TwoMachinesPlay  ||
12978         gameMode == Training         ||
12979         gameMode == AnalyzeMode      ||
12980         gameMode == EndOfGame)
12981         EditGameEvent();
12982
12983     if (gameMode == EditPosition)
12984         EditPositionDone(TRUE);
12985
12986     if (WhiteOnMove(currentMove)) {
12987         DisplayError(_("It is not Black's turn"), 0);
12988         return;
12989     }
12990
12991     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12992       ExitAnalyzeMode();
12993
12994     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12995         gameMode == AnalyzeFile)
12996         TruncateGame();
12997
12998     ResurrectChessProgram();    /* in case it isn't running */
12999     gameMode = MachinePlaysBlack;
13000     pausing = FALSE;
13001     ModeHighlight();
13002     SetGameInfo();
13003     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13004     DisplayTitle(buf);
13005     if (first.sendName) {
13006       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13007       SendToProgram(buf, &first);
13008     }
13009     if (first.sendTime) {
13010       if (first.useColors) {
13011         SendToProgram("white\n", &first); /*gnu kludge*/
13012       }
13013       SendTimeRemaining(&first, FALSE);
13014     }
13015     if (first.useColors) {
13016       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13017     }
13018     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13019     SetMachineThinkingEnables();
13020     first.maybeThinking = TRUE;
13021     StartClocks();
13022
13023     if (appData.autoFlipView && flipView) {
13024       flipView = !flipView;
13025       DrawPosition(FALSE, NULL);
13026       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13027     }
13028     if(bookHit) { // [HGM] book: simulate book reply
13029         static char bookMove[MSG_SIZ]; // a bit generous?
13030
13031         programStats.nodes = programStats.depth = programStats.time =
13032         programStats.score = programStats.got_only_move = 0;
13033         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13034
13035         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13036         strcat(bookMove, bookHit);
13037         HandleMachineMove(bookMove, &first);
13038     }
13039 }
13040
13041
13042 void
13043 DisplayTwoMachinesTitle()
13044 {
13045     char buf[MSG_SIZ];
13046     if (appData.matchGames > 0) {
13047         if(appData.tourneyFile[0]) {
13048           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13049                    gameInfo.white, gameInfo.black,
13050                    nextGame+1, appData.matchGames+1,
13051                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13052         } else 
13053         if (first.twoMachinesColor[0] == 'w') {
13054           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13055                    gameInfo.white, gameInfo.black,
13056                    first.matchWins, second.matchWins,
13057                    matchGame - 1 - (first.matchWins + second.matchWins));
13058         } else {
13059           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13060                    gameInfo.white, gameInfo.black,
13061                    second.matchWins, first.matchWins,
13062                    matchGame - 1 - (first.matchWins + second.matchWins));
13063         }
13064     } else {
13065       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13066     }
13067     DisplayTitle(buf);
13068 }
13069
13070 void
13071 SettingsMenuIfReady()
13072 {
13073   if (second.lastPing != second.lastPong) {
13074     DisplayMessage("", _("Waiting for second chess program"));
13075     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13076     return;
13077   }
13078   ThawUI();
13079   DisplayMessage("", "");
13080   SettingsPopUp(&second);
13081 }
13082
13083 int
13084 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13085 {
13086     char buf[MSG_SIZ];
13087     if (cps->pr == NULL) {
13088         StartChessProgram(cps);
13089         if (cps->protocolVersion == 1) {
13090           retry();
13091         } else {
13092           /* kludge: allow timeout for initial "feature" command */
13093           FreezeUI();
13094           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13095           DisplayMessage("", buf);
13096           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13097         }
13098         return 1;
13099     }
13100     return 0;
13101 }
13102
13103 void
13104 TwoMachinesEvent P((void))
13105 {
13106     int i;
13107     char buf[MSG_SIZ];
13108     ChessProgramState *onmove;
13109     char *bookHit = NULL;
13110     static int stalling = 0;
13111     TimeMark now;
13112     long wait;
13113
13114     if (appData.noChessProgram) return;
13115
13116     switch (gameMode) {
13117       case TwoMachinesPlay:
13118         return;
13119       case MachinePlaysWhite:
13120       case MachinePlaysBlack:
13121         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13122             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13123             return;
13124         }
13125         /* fall through */
13126       case BeginningOfGame:
13127       case PlayFromGameFile:
13128       case EndOfGame:
13129         EditGameEvent();
13130         if (gameMode != EditGame) return;
13131         break;
13132       case EditPosition:
13133         EditPositionDone(TRUE);
13134         break;
13135       case AnalyzeMode:
13136       case AnalyzeFile:
13137         ExitAnalyzeMode();
13138         break;
13139       case EditGame:
13140       default:
13141         break;
13142     }
13143
13144 //    forwardMostMove = currentMove;
13145     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13146
13147     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13148
13149     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13150     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13151       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13152       return;
13153     }
13154     if(!stalling) {
13155       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13156       SendToProgram("force\n", &second);
13157       stalling = 1;
13158       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13159       return;
13160     }
13161     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13162     if(appData.matchPause>10000 || appData.matchPause<10)
13163                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13164     wait = SubtractTimeMarks(&now, &pauseStart);
13165     if(wait < appData.matchPause) {
13166         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13167         return;
13168     }
13169     stalling = 0;
13170     DisplayMessage("", "");
13171     if (startedFromSetupPosition) {
13172         SendBoard(&second, backwardMostMove);
13173     if (appData.debugMode) {
13174         fprintf(debugFP, "Two Machines\n");
13175     }
13176     }
13177     for (i = backwardMostMove; i < forwardMostMove; i++) {
13178         SendMoveToProgram(i, &second);
13179     }
13180
13181     gameMode = TwoMachinesPlay;
13182     pausing = FALSE;
13183     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13184     SetGameInfo();
13185     DisplayTwoMachinesTitle();
13186     firstMove = TRUE;
13187     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13188         onmove = &first;
13189     } else {
13190         onmove = &second;
13191     }
13192     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13193     SendToProgram(first.computerString, &first);
13194     if (first.sendName) {
13195       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13196       SendToProgram(buf, &first);
13197     }
13198     SendToProgram(second.computerString, &second);
13199     if (second.sendName) {
13200       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13201       SendToProgram(buf, &second);
13202     }
13203
13204     ResetClocks();
13205     if (!first.sendTime || !second.sendTime) {
13206         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13207         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13208     }
13209     if (onmove->sendTime) {
13210       if (onmove->useColors) {
13211         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13212       }
13213       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13214     }
13215     if (onmove->useColors) {
13216       SendToProgram(onmove->twoMachinesColor, onmove);
13217     }
13218     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13219 //    SendToProgram("go\n", onmove);
13220     onmove->maybeThinking = TRUE;
13221     SetMachineThinkingEnables();
13222
13223     StartClocks();
13224
13225     if(bookHit) { // [HGM] book: simulate book reply
13226         static char bookMove[MSG_SIZ]; // a bit generous?
13227
13228         programStats.nodes = programStats.depth = programStats.time =
13229         programStats.score = programStats.got_only_move = 0;
13230         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13231
13232         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13233         strcat(bookMove, bookHit);
13234         savedMessage = bookMove; // args for deferred call
13235         savedState = onmove;
13236         ScheduleDelayedEvent(DeferredBookMove, 1);
13237     }
13238 }
13239
13240 void
13241 TrainingEvent()
13242 {
13243     if (gameMode == Training) {
13244       SetTrainingModeOff();
13245       gameMode = PlayFromGameFile;
13246       DisplayMessage("", _("Training mode off"));
13247     } else {
13248       gameMode = Training;
13249       animateTraining = appData.animate;
13250
13251       /* make sure we are not already at the end of the game */
13252       if (currentMove < forwardMostMove) {
13253         SetTrainingModeOn();
13254         DisplayMessage("", _("Training mode on"));
13255       } else {
13256         gameMode = PlayFromGameFile;
13257         DisplayError(_("Already at end of game"), 0);
13258       }
13259     }
13260     ModeHighlight();
13261 }
13262
13263 void
13264 IcsClientEvent()
13265 {
13266     if (!appData.icsActive) return;
13267     switch (gameMode) {
13268       case IcsPlayingWhite:
13269       case IcsPlayingBlack:
13270       case IcsObserving:
13271       case IcsIdle:
13272       case BeginningOfGame:
13273       case IcsExamining:
13274         return;
13275
13276       case EditGame:
13277         break;
13278
13279       case EditPosition:
13280         EditPositionDone(TRUE);
13281         break;
13282
13283       case AnalyzeMode:
13284       case AnalyzeFile:
13285         ExitAnalyzeMode();
13286         break;
13287
13288       default:
13289         EditGameEvent();
13290         break;
13291     }
13292
13293     gameMode = IcsIdle;
13294     ModeHighlight();
13295     return;
13296 }
13297
13298
13299 void
13300 EditGameEvent()
13301 {
13302     int i;
13303
13304     switch (gameMode) {
13305       case Training:
13306         SetTrainingModeOff();
13307         break;
13308       case MachinePlaysWhite:
13309       case MachinePlaysBlack:
13310       case BeginningOfGame:
13311         SendToProgram("force\n", &first);
13312         SetUserThinkingEnables();
13313         break;
13314       case PlayFromGameFile:
13315         (void) StopLoadGameTimer();
13316         if (gameFileFP != NULL) {
13317             gameFileFP = NULL;
13318         }
13319         break;
13320       case EditPosition:
13321         EditPositionDone(TRUE);
13322         break;
13323       case AnalyzeMode:
13324       case AnalyzeFile:
13325         ExitAnalyzeMode();
13326         SendToProgram("force\n", &first);
13327         break;
13328       case TwoMachinesPlay:
13329         GameEnds(EndOfFile, NULL, GE_PLAYER);
13330         ResurrectChessProgram();
13331         SetUserThinkingEnables();
13332         break;
13333       case EndOfGame:
13334         ResurrectChessProgram();
13335         break;
13336       case IcsPlayingBlack:
13337       case IcsPlayingWhite:
13338         DisplayError(_("Warning: You are still playing a game"), 0);
13339         break;
13340       case IcsObserving:
13341         DisplayError(_("Warning: You are still observing a game"), 0);
13342         break;
13343       case IcsExamining:
13344         DisplayError(_("Warning: You are still examining a game"), 0);
13345         break;
13346       case IcsIdle:
13347         break;
13348       case EditGame:
13349       default:
13350         return;
13351     }
13352
13353     pausing = FALSE;
13354     StopClocks();
13355     first.offeredDraw = second.offeredDraw = 0;
13356
13357     if (gameMode == PlayFromGameFile) {
13358         whiteTimeRemaining = timeRemaining[0][currentMove];
13359         blackTimeRemaining = timeRemaining[1][currentMove];
13360         DisplayTitle("");
13361     }
13362
13363     if (gameMode == MachinePlaysWhite ||
13364         gameMode == MachinePlaysBlack ||
13365         gameMode == TwoMachinesPlay ||
13366         gameMode == EndOfGame) {
13367         i = forwardMostMove;
13368         while (i > currentMove) {
13369             SendToProgram("undo\n", &first);
13370             i--;
13371         }
13372         whiteTimeRemaining = timeRemaining[0][currentMove];
13373         blackTimeRemaining = timeRemaining[1][currentMove];
13374         DisplayBothClocks();
13375         if (whiteFlag || blackFlag) {
13376             whiteFlag = blackFlag = 0;
13377         }
13378         DisplayTitle("");
13379     }
13380
13381     gameMode = EditGame;
13382     ModeHighlight();
13383     SetGameInfo();
13384 }
13385
13386
13387 void
13388 EditPositionEvent()
13389 {
13390     if (gameMode == EditPosition) {
13391         EditGameEvent();
13392         return;
13393     }
13394
13395     EditGameEvent();
13396     if (gameMode != EditGame) return;
13397
13398     gameMode = EditPosition;
13399     ModeHighlight();
13400     SetGameInfo();
13401     if (currentMove > 0)
13402       CopyBoard(boards[0], boards[currentMove]);
13403
13404     blackPlaysFirst = !WhiteOnMove(currentMove);
13405     ResetClocks();
13406     currentMove = forwardMostMove = backwardMostMove = 0;
13407     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13408     DisplayMove(-1);
13409 }
13410
13411 void
13412 ExitAnalyzeMode()
13413 {
13414     /* [DM] icsEngineAnalyze - possible call from other functions */
13415     if (appData.icsEngineAnalyze) {
13416         appData.icsEngineAnalyze = FALSE;
13417
13418         DisplayMessage("",_("Close ICS engine analyze..."));
13419     }
13420     if (first.analysisSupport && first.analyzing) {
13421       SendToProgram("exit\n", &first);
13422       first.analyzing = FALSE;
13423     }
13424     thinkOutput[0] = NULLCHAR;
13425 }
13426
13427 void
13428 EditPositionDone(Boolean fakeRights)
13429 {
13430     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13431
13432     startedFromSetupPosition = TRUE;
13433     InitChessProgram(&first, FALSE);
13434     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13435       boards[0][EP_STATUS] = EP_NONE;
13436       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13437     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13438         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13439         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13440       } else boards[0][CASTLING][2] = NoRights;
13441     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13442         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13443         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13444       } else boards[0][CASTLING][5] = NoRights;
13445     }
13446     SendToProgram("force\n", &first);
13447     if (blackPlaysFirst) {
13448         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13449         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13450         currentMove = forwardMostMove = backwardMostMove = 1;
13451         CopyBoard(boards[1], boards[0]);
13452     } else {
13453         currentMove = forwardMostMove = backwardMostMove = 0;
13454     }
13455     SendBoard(&first, forwardMostMove);
13456     if (appData.debugMode) {
13457         fprintf(debugFP, "EditPosDone\n");
13458     }
13459     DisplayTitle("");
13460     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13461     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13462     gameMode = EditGame;
13463     ModeHighlight();
13464     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13465     ClearHighlights(); /* [AS] */
13466 }
13467
13468 /* Pause for `ms' milliseconds */
13469 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13470 void
13471 TimeDelay(ms)
13472      long ms;
13473 {
13474     TimeMark m1, m2;
13475
13476     GetTimeMark(&m1);
13477     do {
13478         GetTimeMark(&m2);
13479     } while (SubtractTimeMarks(&m2, &m1) < ms);
13480 }
13481
13482 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13483 void
13484 SendMultiLineToICS(buf)
13485      char *buf;
13486 {
13487     char temp[MSG_SIZ+1], *p;
13488     int len;
13489
13490     len = strlen(buf);
13491     if (len > MSG_SIZ)
13492       len = MSG_SIZ;
13493
13494     strncpy(temp, buf, len);
13495     temp[len] = 0;
13496
13497     p = temp;
13498     while (*p) {
13499         if (*p == '\n' || *p == '\r')
13500           *p = ' ';
13501         ++p;
13502     }
13503
13504     strcat(temp, "\n");
13505     SendToICS(temp);
13506     SendToPlayer(temp, strlen(temp));
13507 }
13508
13509 void
13510 SetWhiteToPlayEvent()
13511 {
13512     if (gameMode == EditPosition) {
13513         blackPlaysFirst = FALSE;
13514         DisplayBothClocks();    /* works because currentMove is 0 */
13515     } else if (gameMode == IcsExamining) {
13516         SendToICS(ics_prefix);
13517         SendToICS("tomove white\n");
13518     }
13519 }
13520
13521 void
13522 SetBlackToPlayEvent()
13523 {
13524     if (gameMode == EditPosition) {
13525         blackPlaysFirst = TRUE;
13526         currentMove = 1;        /* kludge */
13527         DisplayBothClocks();
13528         currentMove = 0;
13529     } else if (gameMode == IcsExamining) {
13530         SendToICS(ics_prefix);
13531         SendToICS("tomove black\n");
13532     }
13533 }
13534
13535 void
13536 EditPositionMenuEvent(selection, x, y)
13537      ChessSquare selection;
13538      int x, y;
13539 {
13540     char buf[MSG_SIZ];
13541     ChessSquare piece = boards[0][y][x];
13542
13543     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13544
13545     switch (selection) {
13546       case ClearBoard:
13547         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13548             SendToICS(ics_prefix);
13549             SendToICS("bsetup clear\n");
13550         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13551             SendToICS(ics_prefix);
13552             SendToICS("clearboard\n");
13553         } else {
13554             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13555                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13556                 for (y = 0; y < BOARD_HEIGHT; y++) {
13557                     if (gameMode == IcsExamining) {
13558                         if (boards[currentMove][y][x] != EmptySquare) {
13559                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13560                                     AAA + x, ONE + y);
13561                             SendToICS(buf);
13562                         }
13563                     } else {
13564                         boards[0][y][x] = p;
13565                     }
13566                 }
13567             }
13568         }
13569         if (gameMode == EditPosition) {
13570             DrawPosition(FALSE, boards[0]);
13571         }
13572         break;
13573
13574       case WhitePlay:
13575         SetWhiteToPlayEvent();
13576         break;
13577
13578       case BlackPlay:
13579         SetBlackToPlayEvent();
13580         break;
13581
13582       case EmptySquare:
13583         if (gameMode == IcsExamining) {
13584             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13585             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13586             SendToICS(buf);
13587         } else {
13588             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13589                 if(x == BOARD_LEFT-2) {
13590                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13591                     boards[0][y][1] = 0;
13592                 } else
13593                 if(x == BOARD_RGHT+1) {
13594                     if(y >= gameInfo.holdingsSize) break;
13595                     boards[0][y][BOARD_WIDTH-2] = 0;
13596                 } else break;
13597             }
13598             boards[0][y][x] = EmptySquare;
13599             DrawPosition(FALSE, boards[0]);
13600         }
13601         break;
13602
13603       case PromotePiece:
13604         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13605            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13606             selection = (ChessSquare) (PROMOTED piece);
13607         } else if(piece == EmptySquare) selection = WhiteSilver;
13608         else selection = (ChessSquare)((int)piece - 1);
13609         goto defaultlabel;
13610
13611       case DemotePiece:
13612         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13613            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13614             selection = (ChessSquare) (DEMOTED piece);
13615         } else if(piece == EmptySquare) selection = BlackSilver;
13616         else selection = (ChessSquare)((int)piece + 1);
13617         goto defaultlabel;
13618
13619       case WhiteQueen:
13620       case BlackQueen:
13621         if(gameInfo.variant == VariantShatranj ||
13622            gameInfo.variant == VariantXiangqi  ||
13623            gameInfo.variant == VariantCourier  ||
13624            gameInfo.variant == VariantMakruk     )
13625             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13626         goto defaultlabel;
13627
13628       case WhiteKing:
13629       case BlackKing:
13630         if(gameInfo.variant == VariantXiangqi)
13631             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13632         if(gameInfo.variant == VariantKnightmate)
13633             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13634       default:
13635         defaultlabel:
13636         if (gameMode == IcsExamining) {
13637             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13638             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13639                      PieceToChar(selection), AAA + x, ONE + y);
13640             SendToICS(buf);
13641         } else {
13642             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13643                 int n;
13644                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13645                     n = PieceToNumber(selection - BlackPawn);
13646                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13647                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13648                     boards[0][BOARD_HEIGHT-1-n][1]++;
13649                 } else
13650                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13651                     n = PieceToNumber(selection);
13652                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13653                     boards[0][n][BOARD_WIDTH-1] = selection;
13654                     boards[0][n][BOARD_WIDTH-2]++;
13655                 }
13656             } else
13657             boards[0][y][x] = selection;
13658             DrawPosition(TRUE, boards[0]);
13659         }
13660         break;
13661     }
13662 }
13663
13664
13665 void
13666 DropMenuEvent(selection, x, y)
13667      ChessSquare selection;
13668      int x, y;
13669 {
13670     ChessMove moveType;
13671
13672     switch (gameMode) {
13673       case IcsPlayingWhite:
13674       case MachinePlaysBlack:
13675         if (!WhiteOnMove(currentMove)) {
13676             DisplayMoveError(_("It is Black's turn"));
13677             return;
13678         }
13679         moveType = WhiteDrop;
13680         break;
13681       case IcsPlayingBlack:
13682       case MachinePlaysWhite:
13683         if (WhiteOnMove(currentMove)) {
13684             DisplayMoveError(_("It is White's turn"));
13685             return;
13686         }
13687         moveType = BlackDrop;
13688         break;
13689       case EditGame:
13690         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13691         break;
13692       default:
13693         return;
13694     }
13695
13696     if (moveType == BlackDrop && selection < BlackPawn) {
13697       selection = (ChessSquare) ((int) selection
13698                                  + (int) BlackPawn - (int) WhitePawn);
13699     }
13700     if (boards[currentMove][y][x] != EmptySquare) {
13701         DisplayMoveError(_("That square is occupied"));
13702         return;
13703     }
13704
13705     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13706 }
13707
13708 void
13709 AcceptEvent()
13710 {
13711     /* Accept a pending offer of any kind from opponent */
13712
13713     if (appData.icsActive) {
13714         SendToICS(ics_prefix);
13715         SendToICS("accept\n");
13716     } else if (cmailMsgLoaded) {
13717         if (currentMove == cmailOldMove &&
13718             commentList[cmailOldMove] != NULL &&
13719             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13720                    "Black offers a draw" : "White offers a draw")) {
13721             TruncateGame();
13722             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13723             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13724         } else {
13725             DisplayError(_("There is no pending offer on this move"), 0);
13726             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13727         }
13728     } else {
13729         /* Not used for offers from chess program */
13730     }
13731 }
13732
13733 void
13734 DeclineEvent()
13735 {
13736     /* Decline a pending offer of any kind from opponent */
13737
13738     if (appData.icsActive) {
13739         SendToICS(ics_prefix);
13740         SendToICS("decline\n");
13741     } else if (cmailMsgLoaded) {
13742         if (currentMove == cmailOldMove &&
13743             commentList[cmailOldMove] != NULL &&
13744             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13745                    "Black offers a draw" : "White offers a draw")) {
13746 #ifdef NOTDEF
13747             AppendComment(cmailOldMove, "Draw declined", TRUE);
13748             DisplayComment(cmailOldMove - 1, "Draw declined");
13749 #endif /*NOTDEF*/
13750         } else {
13751             DisplayError(_("There is no pending offer on this move"), 0);
13752         }
13753     } else {
13754         /* Not used for offers from chess program */
13755     }
13756 }
13757
13758 void
13759 RematchEvent()
13760 {
13761     /* Issue ICS rematch command */
13762     if (appData.icsActive) {
13763         SendToICS(ics_prefix);
13764         SendToICS("rematch\n");
13765     }
13766 }
13767
13768 void
13769 CallFlagEvent()
13770 {
13771     /* Call your opponent's flag (claim a win on time) */
13772     if (appData.icsActive) {
13773         SendToICS(ics_prefix);
13774         SendToICS("flag\n");
13775     } else {
13776         switch (gameMode) {
13777           default:
13778             return;
13779           case MachinePlaysWhite:
13780             if (whiteFlag) {
13781                 if (blackFlag)
13782                   GameEnds(GameIsDrawn, "Both players ran out of time",
13783                            GE_PLAYER);
13784                 else
13785                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13786             } else {
13787                 DisplayError(_("Your opponent is not out of time"), 0);
13788             }
13789             break;
13790           case MachinePlaysBlack:
13791             if (blackFlag) {
13792                 if (whiteFlag)
13793                   GameEnds(GameIsDrawn, "Both players ran out of time",
13794                            GE_PLAYER);
13795                 else
13796                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13797             } else {
13798                 DisplayError(_("Your opponent is not out of time"), 0);
13799             }
13800             break;
13801         }
13802     }
13803 }
13804
13805 void
13806 ClockClick(int which)
13807 {       // [HGM] code moved to back-end from winboard.c
13808         if(which) { // black clock
13809           if (gameMode == EditPosition || gameMode == IcsExamining) {
13810             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13811             SetBlackToPlayEvent();
13812           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13813           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13814           } else if (shiftKey) {
13815             AdjustClock(which, -1);
13816           } else if (gameMode == IcsPlayingWhite ||
13817                      gameMode == MachinePlaysBlack) {
13818             CallFlagEvent();
13819           }
13820         } else { // white clock
13821           if (gameMode == EditPosition || gameMode == IcsExamining) {
13822             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13823             SetWhiteToPlayEvent();
13824           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13825           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13826           } else if (shiftKey) {
13827             AdjustClock(which, -1);
13828           } else if (gameMode == IcsPlayingBlack ||
13829                    gameMode == MachinePlaysWhite) {
13830             CallFlagEvent();
13831           }
13832         }
13833 }
13834
13835 void
13836 DrawEvent()
13837 {
13838     /* Offer draw or accept pending draw offer from opponent */
13839
13840     if (appData.icsActive) {
13841         /* Note: tournament rules require draw offers to be
13842            made after you make your move but before you punch
13843            your clock.  Currently ICS doesn't let you do that;
13844            instead, you immediately punch your clock after making
13845            a move, but you can offer a draw at any time. */
13846
13847         SendToICS(ics_prefix);
13848         SendToICS("draw\n");
13849         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13850     } else if (cmailMsgLoaded) {
13851         if (currentMove == cmailOldMove &&
13852             commentList[cmailOldMove] != NULL &&
13853             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13854                    "Black offers a draw" : "White offers a draw")) {
13855             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13856             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13857         } else if (currentMove == cmailOldMove + 1) {
13858             char *offer = WhiteOnMove(cmailOldMove) ?
13859               "White offers a draw" : "Black offers a draw";
13860             AppendComment(currentMove, offer, TRUE);
13861             DisplayComment(currentMove - 1, offer);
13862             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13863         } else {
13864             DisplayError(_("You must make your move before offering a draw"), 0);
13865             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13866         }
13867     } else if (first.offeredDraw) {
13868         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13869     } else {
13870         if (first.sendDrawOffers) {
13871             SendToProgram("draw\n", &first);
13872             userOfferedDraw = TRUE;
13873         }
13874     }
13875 }
13876
13877 void
13878 AdjournEvent()
13879 {
13880     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13881
13882     if (appData.icsActive) {
13883         SendToICS(ics_prefix);
13884         SendToICS("adjourn\n");
13885     } else {
13886         /* Currently GNU Chess doesn't offer or accept Adjourns */
13887     }
13888 }
13889
13890
13891 void
13892 AbortEvent()
13893 {
13894     /* Offer Abort or accept pending Abort offer from opponent */
13895
13896     if (appData.icsActive) {
13897         SendToICS(ics_prefix);
13898         SendToICS("abort\n");
13899     } else {
13900         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13901     }
13902 }
13903
13904 void
13905 ResignEvent()
13906 {
13907     /* Resign.  You can do this even if it's not your turn. */
13908
13909     if (appData.icsActive) {
13910         SendToICS(ics_prefix);
13911         SendToICS("resign\n");
13912     } else {
13913         switch (gameMode) {
13914           case MachinePlaysWhite:
13915             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13916             break;
13917           case MachinePlaysBlack:
13918             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13919             break;
13920           case EditGame:
13921             if (cmailMsgLoaded) {
13922                 TruncateGame();
13923                 if (WhiteOnMove(cmailOldMove)) {
13924                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13925                 } else {
13926                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13927                 }
13928                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13929             }
13930             break;
13931           default:
13932             break;
13933         }
13934     }
13935 }
13936
13937
13938 void
13939 StopObservingEvent()
13940 {
13941     /* Stop observing current games */
13942     SendToICS(ics_prefix);
13943     SendToICS("unobserve\n");
13944 }
13945
13946 void
13947 StopExaminingEvent()
13948 {
13949     /* Stop observing current game */
13950     SendToICS(ics_prefix);
13951     SendToICS("unexamine\n");
13952 }
13953
13954 void
13955 ForwardInner(target)
13956      int target;
13957 {
13958     int limit;
13959
13960     if (appData.debugMode)
13961         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13962                 target, currentMove, forwardMostMove);
13963
13964     if (gameMode == EditPosition)
13965       return;
13966
13967     if (gameMode == PlayFromGameFile && !pausing)
13968       PauseEvent();
13969
13970     if (gameMode == IcsExamining && pausing)
13971       limit = pauseExamForwardMostMove;
13972     else
13973       limit = forwardMostMove;
13974
13975     if (target > limit) target = limit;
13976
13977     if (target > 0 && moveList[target - 1][0]) {
13978         int fromX, fromY, toX, toY;
13979         toX = moveList[target - 1][2] - AAA;
13980         toY = moveList[target - 1][3] - ONE;
13981         if (moveList[target - 1][1] == '@') {
13982             if (appData.highlightLastMove) {
13983                 SetHighlights(-1, -1, toX, toY);
13984             }
13985         } else {
13986             fromX = moveList[target - 1][0] - AAA;
13987             fromY = moveList[target - 1][1] - ONE;
13988             if (target == currentMove + 1) {
13989                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13990             }
13991             if (appData.highlightLastMove) {
13992                 SetHighlights(fromX, fromY, toX, toY);
13993             }
13994         }
13995     }
13996     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13997         gameMode == Training || gameMode == PlayFromGameFile ||
13998         gameMode == AnalyzeFile) {
13999         while (currentMove < target) {
14000             SendMoveToProgram(currentMove++, &first);
14001         }
14002     } else {
14003         currentMove = target;
14004     }
14005
14006     if (gameMode == EditGame || gameMode == EndOfGame) {
14007         whiteTimeRemaining = timeRemaining[0][currentMove];
14008         blackTimeRemaining = timeRemaining[1][currentMove];
14009     }
14010     DisplayBothClocks();
14011     DisplayMove(currentMove - 1);
14012     DrawPosition(FALSE, boards[currentMove]);
14013     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14014     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14015         DisplayComment(currentMove - 1, commentList[currentMove]);
14016     }
14017     DisplayBook(currentMove);
14018 }
14019
14020
14021 void
14022 ForwardEvent()
14023 {
14024     if (gameMode == IcsExamining && !pausing) {
14025         SendToICS(ics_prefix);
14026         SendToICS("forward\n");
14027     } else {
14028         ForwardInner(currentMove + 1);
14029     }
14030 }
14031
14032 void
14033 ToEndEvent()
14034 {
14035     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14036         /* to optimze, we temporarily turn off analysis mode while we feed
14037          * the remaining moves to the engine. Otherwise we get analysis output
14038          * after each move.
14039          */
14040         if (first.analysisSupport) {
14041           SendToProgram("exit\nforce\n", &first);
14042           first.analyzing = FALSE;
14043         }
14044     }
14045
14046     if (gameMode == IcsExamining && !pausing) {
14047         SendToICS(ics_prefix);
14048         SendToICS("forward 999999\n");
14049     } else {
14050         ForwardInner(forwardMostMove);
14051     }
14052
14053     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14054         /* we have fed all the moves, so reactivate analysis mode */
14055         SendToProgram("analyze\n", &first);
14056         first.analyzing = TRUE;
14057         /*first.maybeThinking = TRUE;*/
14058         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14059     }
14060 }
14061
14062 void
14063 BackwardInner(target)
14064      int target;
14065 {
14066     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14067
14068     if (appData.debugMode)
14069         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14070                 target, currentMove, forwardMostMove);
14071
14072     if (gameMode == EditPosition) return;
14073     if (currentMove <= backwardMostMove) {
14074         ClearHighlights();
14075         DrawPosition(full_redraw, boards[currentMove]);
14076         return;
14077     }
14078     if (gameMode == PlayFromGameFile && !pausing)
14079       PauseEvent();
14080
14081     if (moveList[target][0]) {
14082         int fromX, fromY, toX, toY;
14083         toX = moveList[target][2] - AAA;
14084         toY = moveList[target][3] - ONE;
14085         if (moveList[target][1] == '@') {
14086             if (appData.highlightLastMove) {
14087                 SetHighlights(-1, -1, toX, toY);
14088             }
14089         } else {
14090             fromX = moveList[target][0] - AAA;
14091             fromY = moveList[target][1] - ONE;
14092             if (target == currentMove - 1) {
14093                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14094             }
14095             if (appData.highlightLastMove) {
14096                 SetHighlights(fromX, fromY, toX, toY);
14097             }
14098         }
14099     }
14100     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14101         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14102         while (currentMove > target) {
14103             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14104                 // null move cannot be undone. Reload program with move history before it.
14105                 int i;
14106                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14107                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14108                 }
14109                 SendBoard(&first, i); 
14110                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14111                 break;
14112             }
14113             SendToProgram("undo\n", &first);
14114             currentMove--;
14115         }
14116     } else {
14117         currentMove = target;
14118     }
14119
14120     if (gameMode == EditGame || gameMode == EndOfGame) {
14121         whiteTimeRemaining = timeRemaining[0][currentMove];
14122         blackTimeRemaining = timeRemaining[1][currentMove];
14123     }
14124     DisplayBothClocks();
14125     DisplayMove(currentMove - 1);
14126     DrawPosition(full_redraw, boards[currentMove]);
14127     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14128     // [HGM] PV info: routine tests if comment empty
14129     DisplayComment(currentMove - 1, commentList[currentMove]);
14130     DisplayBook(currentMove);
14131 }
14132
14133 void
14134 BackwardEvent()
14135 {
14136     if (gameMode == IcsExamining && !pausing) {
14137         SendToICS(ics_prefix);
14138         SendToICS("backward\n");
14139     } else {
14140         BackwardInner(currentMove - 1);
14141     }
14142 }
14143
14144 void
14145 ToStartEvent()
14146 {
14147     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14148         /* to optimize, we temporarily turn off analysis mode while we undo
14149          * all the moves. Otherwise we get analysis output after each undo.
14150          */
14151         if (first.analysisSupport) {
14152           SendToProgram("exit\nforce\n", &first);
14153           first.analyzing = FALSE;
14154         }
14155     }
14156
14157     if (gameMode == IcsExamining && !pausing) {
14158         SendToICS(ics_prefix);
14159         SendToICS("backward 999999\n");
14160     } else {
14161         BackwardInner(backwardMostMove);
14162     }
14163
14164     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14165         /* we have fed all the moves, so reactivate analysis mode */
14166         SendToProgram("analyze\n", &first);
14167         first.analyzing = TRUE;
14168         /*first.maybeThinking = TRUE;*/
14169         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14170     }
14171 }
14172
14173 void
14174 ToNrEvent(int to)
14175 {
14176   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14177   if (to >= forwardMostMove) to = forwardMostMove;
14178   if (to <= backwardMostMove) to = backwardMostMove;
14179   if (to < currentMove) {
14180     BackwardInner(to);
14181   } else {
14182     ForwardInner(to);
14183   }
14184 }
14185
14186 void
14187 RevertEvent(Boolean annotate)
14188 {
14189     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14190         return;
14191     }
14192     if (gameMode != IcsExamining) {
14193         DisplayError(_("You are not examining a game"), 0);
14194         return;
14195     }
14196     if (pausing) {
14197         DisplayError(_("You can't revert while pausing"), 0);
14198         return;
14199     }
14200     SendToICS(ics_prefix);
14201     SendToICS("revert\n");
14202 }
14203
14204 void
14205 RetractMoveEvent()
14206 {
14207     switch (gameMode) {
14208       case MachinePlaysWhite:
14209       case MachinePlaysBlack:
14210         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14211             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14212             return;
14213         }
14214         if (forwardMostMove < 2) return;
14215         currentMove = forwardMostMove = forwardMostMove - 2;
14216         whiteTimeRemaining = timeRemaining[0][currentMove];
14217         blackTimeRemaining = timeRemaining[1][currentMove];
14218         DisplayBothClocks();
14219         DisplayMove(currentMove - 1);
14220         ClearHighlights();/*!! could figure this out*/
14221         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14222         SendToProgram("remove\n", &first);
14223         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14224         break;
14225
14226       case BeginningOfGame:
14227       default:
14228         break;
14229
14230       case IcsPlayingWhite:
14231       case IcsPlayingBlack:
14232         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14233             SendToICS(ics_prefix);
14234             SendToICS("takeback 2\n");
14235         } else {
14236             SendToICS(ics_prefix);
14237             SendToICS("takeback 1\n");
14238         }
14239         break;
14240     }
14241 }
14242
14243 void
14244 MoveNowEvent()
14245 {
14246     ChessProgramState *cps;
14247
14248     switch (gameMode) {
14249       case MachinePlaysWhite:
14250         if (!WhiteOnMove(forwardMostMove)) {
14251             DisplayError(_("It is your turn"), 0);
14252             return;
14253         }
14254         cps = &first;
14255         break;
14256       case MachinePlaysBlack:
14257         if (WhiteOnMove(forwardMostMove)) {
14258             DisplayError(_("It is your turn"), 0);
14259             return;
14260         }
14261         cps = &first;
14262         break;
14263       case TwoMachinesPlay:
14264         if (WhiteOnMove(forwardMostMove) ==
14265             (first.twoMachinesColor[0] == 'w')) {
14266             cps = &first;
14267         } else {
14268             cps = &second;
14269         }
14270         break;
14271       case BeginningOfGame:
14272       default:
14273         return;
14274     }
14275     SendToProgram("?\n", cps);
14276 }
14277
14278 void
14279 TruncateGameEvent()
14280 {
14281     EditGameEvent();
14282     if (gameMode != EditGame) return;
14283     TruncateGame();
14284 }
14285
14286 void
14287 TruncateGame()
14288 {
14289     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14290     if (forwardMostMove > currentMove) {
14291         if (gameInfo.resultDetails != NULL) {
14292             free(gameInfo.resultDetails);
14293             gameInfo.resultDetails = NULL;
14294             gameInfo.result = GameUnfinished;
14295         }
14296         forwardMostMove = currentMove;
14297         HistorySet(parseList, backwardMostMove, forwardMostMove,
14298                    currentMove-1);
14299     }
14300 }
14301
14302 void
14303 HintEvent()
14304 {
14305     if (appData.noChessProgram) return;
14306     switch (gameMode) {
14307       case MachinePlaysWhite:
14308         if (WhiteOnMove(forwardMostMove)) {
14309             DisplayError(_("Wait until your turn"), 0);
14310             return;
14311         }
14312         break;
14313       case BeginningOfGame:
14314       case MachinePlaysBlack:
14315         if (!WhiteOnMove(forwardMostMove)) {
14316             DisplayError(_("Wait until your turn"), 0);
14317             return;
14318         }
14319         break;
14320       default:
14321         DisplayError(_("No hint available"), 0);
14322         return;
14323     }
14324     SendToProgram("hint\n", &first);
14325     hintRequested = TRUE;
14326 }
14327
14328 void
14329 BookEvent()
14330 {
14331     if (appData.noChessProgram) return;
14332     switch (gameMode) {
14333       case MachinePlaysWhite:
14334         if (WhiteOnMove(forwardMostMove)) {
14335             DisplayError(_("Wait until your turn"), 0);
14336             return;
14337         }
14338         break;
14339       case BeginningOfGame:
14340       case MachinePlaysBlack:
14341         if (!WhiteOnMove(forwardMostMove)) {
14342             DisplayError(_("Wait until your turn"), 0);
14343             return;
14344         }
14345         break;
14346       case EditPosition:
14347         EditPositionDone(TRUE);
14348         break;
14349       case TwoMachinesPlay:
14350         return;
14351       default:
14352         break;
14353     }
14354     SendToProgram("bk\n", &first);
14355     bookOutput[0] = NULLCHAR;
14356     bookRequested = TRUE;
14357 }
14358
14359 void
14360 AboutGameEvent()
14361 {
14362     char *tags = PGNTags(&gameInfo);
14363     TagsPopUp(tags, CmailMsg());
14364     free(tags);
14365 }
14366
14367 /* end button procedures */
14368
14369 void
14370 PrintPosition(fp, move)
14371      FILE *fp;
14372      int move;
14373 {
14374     int i, j;
14375
14376     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14377         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14378             char c = PieceToChar(boards[move][i][j]);
14379             fputc(c == 'x' ? '.' : c, fp);
14380             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14381         }
14382     }
14383     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14384       fprintf(fp, "white to play\n");
14385     else
14386       fprintf(fp, "black to play\n");
14387 }
14388
14389 void
14390 PrintOpponents(fp)
14391      FILE *fp;
14392 {
14393     if (gameInfo.white != NULL) {
14394         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14395     } else {
14396         fprintf(fp, "\n");
14397     }
14398 }
14399
14400 /* Find last component of program's own name, using some heuristics */
14401 void
14402 TidyProgramName(prog, host, buf)
14403      char *prog, *host, buf[MSG_SIZ];
14404 {
14405     char *p, *q;
14406     int local = (strcmp(host, "localhost") == 0);
14407     while (!local && (p = strchr(prog, ';')) != NULL) {
14408         p++;
14409         while (*p == ' ') p++;
14410         prog = p;
14411     }
14412     if (*prog == '"' || *prog == '\'') {
14413         q = strchr(prog + 1, *prog);
14414     } else {
14415         q = strchr(prog, ' ');
14416     }
14417     if (q == NULL) q = prog + strlen(prog);
14418     p = q;
14419     while (p >= prog && *p != '/' && *p != '\\') p--;
14420     p++;
14421     if(p == prog && *p == '"') p++;
14422     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14423     memcpy(buf, p, q - p);
14424     buf[q - p] = NULLCHAR;
14425     if (!local) {
14426         strcat(buf, "@");
14427         strcat(buf, host);
14428     }
14429 }
14430
14431 char *
14432 TimeControlTagValue()
14433 {
14434     char buf[MSG_SIZ];
14435     if (!appData.clockMode) {
14436       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14437     } else if (movesPerSession > 0) {
14438       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14439     } else if (timeIncrement == 0) {
14440       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14441     } else {
14442       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14443     }
14444     return StrSave(buf);
14445 }
14446
14447 void
14448 SetGameInfo()
14449 {
14450     /* This routine is used only for certain modes */
14451     VariantClass v = gameInfo.variant;
14452     ChessMove r = GameUnfinished;
14453     char *p = NULL;
14454
14455     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14456         r = gameInfo.result;
14457         p = gameInfo.resultDetails;
14458         gameInfo.resultDetails = NULL;
14459     }
14460     ClearGameInfo(&gameInfo);
14461     gameInfo.variant = v;
14462
14463     switch (gameMode) {
14464       case MachinePlaysWhite:
14465         gameInfo.event = StrSave( appData.pgnEventHeader );
14466         gameInfo.site = StrSave(HostName());
14467         gameInfo.date = PGNDate();
14468         gameInfo.round = StrSave("-");
14469         gameInfo.white = StrSave(first.tidy);
14470         gameInfo.black = StrSave(UserName());
14471         gameInfo.timeControl = TimeControlTagValue();
14472         break;
14473
14474       case MachinePlaysBlack:
14475         gameInfo.event = StrSave( appData.pgnEventHeader );
14476         gameInfo.site = StrSave(HostName());
14477         gameInfo.date = PGNDate();
14478         gameInfo.round = StrSave("-");
14479         gameInfo.white = StrSave(UserName());
14480         gameInfo.black = StrSave(first.tidy);
14481         gameInfo.timeControl = TimeControlTagValue();
14482         break;
14483
14484       case TwoMachinesPlay:
14485         gameInfo.event = StrSave( appData.pgnEventHeader );
14486         gameInfo.site = StrSave(HostName());
14487         gameInfo.date = PGNDate();
14488         if (roundNr > 0) {
14489             char buf[MSG_SIZ];
14490             snprintf(buf, MSG_SIZ, "%d", roundNr);
14491             gameInfo.round = StrSave(buf);
14492         } else {
14493             gameInfo.round = StrSave("-");
14494         }
14495         if (first.twoMachinesColor[0] == 'w') {
14496             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14497             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14498         } else {
14499             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14500             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14501         }
14502         gameInfo.timeControl = TimeControlTagValue();
14503         break;
14504
14505       case EditGame:
14506         gameInfo.event = StrSave("Edited game");
14507         gameInfo.site = StrSave(HostName());
14508         gameInfo.date = PGNDate();
14509         gameInfo.round = StrSave("-");
14510         gameInfo.white = StrSave("-");
14511         gameInfo.black = StrSave("-");
14512         gameInfo.result = r;
14513         gameInfo.resultDetails = p;
14514         break;
14515
14516       case EditPosition:
14517         gameInfo.event = StrSave("Edited position");
14518         gameInfo.site = StrSave(HostName());
14519         gameInfo.date = PGNDate();
14520         gameInfo.round = StrSave("-");
14521         gameInfo.white = StrSave("-");
14522         gameInfo.black = StrSave("-");
14523         break;
14524
14525       case IcsPlayingWhite:
14526       case IcsPlayingBlack:
14527       case IcsObserving:
14528       case IcsExamining:
14529         break;
14530
14531       case PlayFromGameFile:
14532         gameInfo.event = StrSave("Game from non-PGN file");
14533         gameInfo.site = StrSave(HostName());
14534         gameInfo.date = PGNDate();
14535         gameInfo.round = StrSave("-");
14536         gameInfo.white = StrSave("?");
14537         gameInfo.black = StrSave("?");
14538         break;
14539
14540       default:
14541         break;
14542     }
14543 }
14544
14545 void
14546 ReplaceComment(index, text)
14547      int index;
14548      char *text;
14549 {
14550     int len;
14551     char *p;
14552     float score;
14553
14554     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14555        pvInfoList[index-1].depth == len &&
14556        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14557        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14558     while (*text == '\n') text++;
14559     len = strlen(text);
14560     while (len > 0 && text[len - 1] == '\n') len--;
14561
14562     if (commentList[index] != NULL)
14563       free(commentList[index]);
14564
14565     if (len == 0) {
14566         commentList[index] = NULL;
14567         return;
14568     }
14569   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14570       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14571       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14572     commentList[index] = (char *) malloc(len + 2);
14573     strncpy(commentList[index], text, len);
14574     commentList[index][len] = '\n';
14575     commentList[index][len + 1] = NULLCHAR;
14576   } else {
14577     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14578     char *p;
14579     commentList[index] = (char *) malloc(len + 7);
14580     safeStrCpy(commentList[index], "{\n", 3);
14581     safeStrCpy(commentList[index]+2, text, len+1);
14582     commentList[index][len+2] = NULLCHAR;
14583     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14584     strcat(commentList[index], "\n}\n");
14585   }
14586 }
14587
14588 void
14589 CrushCRs(text)
14590      char *text;
14591 {
14592   char *p = text;
14593   char *q = text;
14594   char ch;
14595
14596   do {
14597     ch = *p++;
14598     if (ch == '\r') continue;
14599     *q++ = ch;
14600   } while (ch != '\0');
14601 }
14602
14603 void
14604 AppendComment(index, text, addBraces)
14605      int index;
14606      char *text;
14607      Boolean addBraces; // [HGM] braces: tells if we should add {}
14608 {
14609     int oldlen, len;
14610     char *old;
14611
14612 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14613     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14614
14615     CrushCRs(text);
14616     while (*text == '\n') text++;
14617     len = strlen(text);
14618     while (len > 0 && text[len - 1] == '\n') len--;
14619
14620     if (len == 0) return;
14621
14622     if (commentList[index] != NULL) {
14623         old = commentList[index];
14624         oldlen = strlen(old);
14625         while(commentList[index][oldlen-1] ==  '\n')
14626           commentList[index][--oldlen] = NULLCHAR;
14627         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14628         safeStrCpy(commentList[index], old, oldlen + len + 6);
14629         free(old);
14630         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14631         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14632           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14633           while (*text == '\n') { text++; len--; }
14634           commentList[index][--oldlen] = NULLCHAR;
14635       }
14636         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14637         else          strcat(commentList[index], "\n");
14638         strcat(commentList[index], text);
14639         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14640         else          strcat(commentList[index], "\n");
14641     } else {
14642         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14643         if(addBraces)
14644           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14645         else commentList[index][0] = NULLCHAR;
14646         strcat(commentList[index], text);
14647         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14648         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14649     }
14650 }
14651
14652 static char * FindStr( char * text, char * sub_text )
14653 {
14654     char * result = strstr( text, sub_text );
14655
14656     if( result != NULL ) {
14657         result += strlen( sub_text );
14658     }
14659
14660     return result;
14661 }
14662
14663 /* [AS] Try to extract PV info from PGN comment */
14664 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14665 char *GetInfoFromComment( int index, char * text )
14666 {
14667     char * sep = text, *p;
14668
14669     if( text != NULL && index > 0 ) {
14670         int score = 0;
14671         int depth = 0;
14672         int time = -1, sec = 0, deci;
14673         char * s_eval = FindStr( text, "[%eval " );
14674         char * s_emt = FindStr( text, "[%emt " );
14675
14676         if( s_eval != NULL || s_emt != NULL ) {
14677             /* New style */
14678             char delim;
14679
14680             if( s_eval != NULL ) {
14681                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14682                     return text;
14683                 }
14684
14685                 if( delim != ']' ) {
14686                     return text;
14687                 }
14688             }
14689
14690             if( s_emt != NULL ) {
14691             }
14692                 return text;
14693         }
14694         else {
14695             /* We expect something like: [+|-]nnn.nn/dd */
14696             int score_lo = 0;
14697
14698             if(*text != '{') return text; // [HGM] braces: must be normal comment
14699
14700             sep = strchr( text, '/' );
14701             if( sep == NULL || sep < (text+4) ) {
14702                 return text;
14703             }
14704
14705             p = text;
14706             if(p[1] == '(') { // comment starts with PV
14707                p = strchr(p, ')'); // locate end of PV
14708                if(p == NULL || sep < p+5) return text;
14709                // at this point we have something like "{(.*) +0.23/6 ..."
14710                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14711                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14712                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14713             }
14714             time = -1; sec = -1; deci = -1;
14715             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14716                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14717                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14718                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14719                 return text;
14720             }
14721
14722             if( score_lo < 0 || score_lo >= 100 ) {
14723                 return text;
14724             }
14725
14726             if(sec >= 0) time = 600*time + 10*sec; else
14727             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14728
14729             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14730
14731             /* [HGM] PV time: now locate end of PV info */
14732             while( *++sep >= '0' && *sep <= '9'); // strip depth
14733             if(time >= 0)
14734             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14735             if(sec >= 0)
14736             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14737             if(deci >= 0)
14738             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14739             while(*sep == ' ') sep++;
14740         }
14741
14742         if( depth <= 0 ) {
14743             return text;
14744         }
14745
14746         if( time < 0 ) {
14747             time = -1;
14748         }
14749
14750         pvInfoList[index-1].depth = depth;
14751         pvInfoList[index-1].score = score;
14752         pvInfoList[index-1].time  = 10*time; // centi-sec
14753         if(*sep == '}') *sep = 0; else *--sep = '{';
14754         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14755     }
14756     return sep;
14757 }
14758
14759 void
14760 SendToProgram(message, cps)
14761      char *message;
14762      ChessProgramState *cps;
14763 {
14764     int count, outCount, error;
14765     char buf[MSG_SIZ];
14766
14767     if (cps->pr == NULL) return;
14768     Attention(cps);
14769
14770     if (appData.debugMode) {
14771         TimeMark now;
14772         GetTimeMark(&now);
14773         fprintf(debugFP, "%ld >%-6s: %s",
14774                 SubtractTimeMarks(&now, &programStartTime),
14775                 cps->which, message);
14776     }
14777
14778     count = strlen(message);
14779     outCount = OutputToProcess(cps->pr, message, count, &error);
14780     if (outCount < count && !exiting
14781                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14782       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14783       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14784         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14785             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14786                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14787                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14788                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14789             } else {
14790                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14791                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14792                 gameInfo.result = res;
14793             }
14794             gameInfo.resultDetails = StrSave(buf);
14795         }
14796         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14797         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14798     }
14799 }
14800
14801 void
14802 ReceiveFromProgram(isr, closure, message, count, error)
14803      InputSourceRef isr;
14804      VOIDSTAR closure;
14805      char *message;
14806      int count;
14807      int error;
14808 {
14809     char *end_str;
14810     char buf[MSG_SIZ];
14811     ChessProgramState *cps = (ChessProgramState *)closure;
14812
14813     if (isr != cps->isr) return; /* Killed intentionally */
14814     if (count <= 0) {
14815         if (count == 0) {
14816             RemoveInputSource(cps->isr);
14817             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14818             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14819                     _(cps->which), cps->program);
14820         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14821                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14822                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14823                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14824                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14825                 } else {
14826                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14827                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14828                     gameInfo.result = res;
14829                 }
14830                 gameInfo.resultDetails = StrSave(buf);
14831             }
14832             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14833             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14834         } else {
14835             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14836                     _(cps->which), cps->program);
14837             RemoveInputSource(cps->isr);
14838
14839             /* [AS] Program is misbehaving badly... kill it */
14840             if( count == -2 ) {
14841                 DestroyChildProcess( cps->pr, 9 );
14842                 cps->pr = NoProc;
14843             }
14844
14845             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14846         }
14847         return;
14848     }
14849
14850     if ((end_str = strchr(message, '\r')) != NULL)
14851       *end_str = NULLCHAR;
14852     if ((end_str = strchr(message, '\n')) != NULL)
14853       *end_str = NULLCHAR;
14854
14855     if (appData.debugMode) {
14856         TimeMark now; int print = 1;
14857         char *quote = ""; char c; int i;
14858
14859         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14860                 char start = message[0];
14861                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14862                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14863                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14864                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14865                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14866                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14867                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14868                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14869                    sscanf(message, "hint: %c", &c)!=1 && 
14870                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14871                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14872                     print = (appData.engineComments >= 2);
14873                 }
14874                 message[0] = start; // restore original message
14875         }
14876         if(print) {
14877                 GetTimeMark(&now);
14878                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14879                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14880                         quote,
14881                         message);
14882         }
14883     }
14884
14885     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14886     if (appData.icsEngineAnalyze) {
14887         if (strstr(message, "whisper") != NULL ||
14888              strstr(message, "kibitz") != NULL ||
14889             strstr(message, "tellics") != NULL) return;
14890     }
14891
14892     HandleMachineMove(message, cps);
14893 }
14894
14895
14896 void
14897 SendTimeControl(cps, mps, tc, inc, sd, st)
14898      ChessProgramState *cps;
14899      int mps, inc, sd, st;
14900      long tc;
14901 {
14902     char buf[MSG_SIZ];
14903     int seconds;
14904
14905     if( timeControl_2 > 0 ) {
14906         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14907             tc = timeControl_2;
14908         }
14909     }
14910     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14911     inc /= cps->timeOdds;
14912     st  /= cps->timeOdds;
14913
14914     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14915
14916     if (st > 0) {
14917       /* Set exact time per move, normally using st command */
14918       if (cps->stKludge) {
14919         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14920         seconds = st % 60;
14921         if (seconds == 0) {
14922           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14923         } else {
14924           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14925         }
14926       } else {
14927         snprintf(buf, MSG_SIZ, "st %d\n", st);
14928       }
14929     } else {
14930       /* Set conventional or incremental time control, using level command */
14931       if (seconds == 0) {
14932         /* Note old gnuchess bug -- minutes:seconds used to not work.
14933            Fixed in later versions, but still avoid :seconds
14934            when seconds is 0. */
14935         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14936       } else {
14937         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14938                  seconds, inc/1000.);
14939       }
14940     }
14941     SendToProgram(buf, cps);
14942
14943     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14944     /* Orthogonally, limit search to given depth */
14945     if (sd > 0) {
14946       if (cps->sdKludge) {
14947         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14948       } else {
14949         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14950       }
14951       SendToProgram(buf, cps);
14952     }
14953
14954     if(cps->nps >= 0) { /* [HGM] nps */
14955         if(cps->supportsNPS == FALSE)
14956           cps->nps = -1; // don't use if engine explicitly says not supported!
14957         else {
14958           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14959           SendToProgram(buf, cps);
14960         }
14961     }
14962 }
14963
14964 ChessProgramState *WhitePlayer()
14965 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14966 {
14967     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14968        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14969         return &second;
14970     return &first;
14971 }
14972
14973 void
14974 SendTimeRemaining(cps, machineWhite)
14975      ChessProgramState *cps;
14976      int /*boolean*/ machineWhite;
14977 {
14978     char message[MSG_SIZ];
14979     long time, otime;
14980
14981     /* Note: this routine must be called when the clocks are stopped
14982        or when they have *just* been set or switched; otherwise
14983        it will be off by the time since the current tick started.
14984     */
14985     if (machineWhite) {
14986         time = whiteTimeRemaining / 10;
14987         otime = blackTimeRemaining / 10;
14988     } else {
14989         time = blackTimeRemaining / 10;
14990         otime = whiteTimeRemaining / 10;
14991     }
14992     /* [HGM] translate opponent's time by time-odds factor */
14993     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14994     if (appData.debugMode) {
14995         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14996     }
14997
14998     if (time <= 0) time = 1;
14999     if (otime <= 0) otime = 1;
15000
15001     snprintf(message, MSG_SIZ, "time %ld\n", time);
15002     SendToProgram(message, cps);
15003
15004     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15005     SendToProgram(message, cps);
15006 }
15007
15008 int
15009 BoolFeature(p, name, loc, cps)
15010      char **p;
15011      char *name;
15012      int *loc;
15013      ChessProgramState *cps;
15014 {
15015   char buf[MSG_SIZ];
15016   int len = strlen(name);
15017   int val;
15018
15019   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15020     (*p) += len + 1;
15021     sscanf(*p, "%d", &val);
15022     *loc = (val != 0);
15023     while (**p && **p != ' ')
15024       (*p)++;
15025     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15026     SendToProgram(buf, cps);
15027     return TRUE;
15028   }
15029   return FALSE;
15030 }
15031
15032 int
15033 IntFeature(p, name, loc, cps)
15034      char **p;
15035      char *name;
15036      int *loc;
15037      ChessProgramState *cps;
15038 {
15039   char buf[MSG_SIZ];
15040   int len = strlen(name);
15041   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15042     (*p) += len + 1;
15043     sscanf(*p, "%d", loc);
15044     while (**p && **p != ' ') (*p)++;
15045     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15046     SendToProgram(buf, cps);
15047     return TRUE;
15048   }
15049   return FALSE;
15050 }
15051
15052 int
15053 StringFeature(p, name, loc, cps)
15054      char **p;
15055      char *name;
15056      char loc[];
15057      ChessProgramState *cps;
15058 {
15059   char buf[MSG_SIZ];
15060   int len = strlen(name);
15061   if (strncmp((*p), name, len) == 0
15062       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15063     (*p) += len + 2;
15064     sscanf(*p, "%[^\"]", loc);
15065     while (**p && **p != '\"') (*p)++;
15066     if (**p == '\"') (*p)++;
15067     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15068     SendToProgram(buf, cps);
15069     return TRUE;
15070   }
15071   return FALSE;
15072 }
15073
15074 int
15075 ParseOption(Option *opt, ChessProgramState *cps)
15076 // [HGM] options: process the string that defines an engine option, and determine
15077 // name, type, default value, and allowed value range
15078 {
15079         char *p, *q, buf[MSG_SIZ];
15080         int n, min = (-1)<<31, max = 1<<31, def;
15081
15082         if(p = strstr(opt->name, " -spin ")) {
15083             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15084             if(max < min) max = min; // enforce consistency
15085             if(def < min) def = min;
15086             if(def > max) def = max;
15087             opt->value = def;
15088             opt->min = min;
15089             opt->max = max;
15090             opt->type = Spin;
15091         } else if((p = strstr(opt->name, " -slider "))) {
15092             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15093             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15094             if(max < min) max = min; // enforce consistency
15095             if(def < min) def = min;
15096             if(def > max) def = max;
15097             opt->value = def;
15098             opt->min = min;
15099             opt->max = max;
15100             opt->type = Spin; // Slider;
15101         } else if((p = strstr(opt->name, " -string "))) {
15102             opt->textValue = p+9;
15103             opt->type = TextBox;
15104         } else if((p = strstr(opt->name, " -file "))) {
15105             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15106             opt->textValue = p+7;
15107             opt->type = FileName; // FileName;
15108         } else if((p = strstr(opt->name, " -path "))) {
15109             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15110             opt->textValue = p+7;
15111             opt->type = PathName; // PathName;
15112         } else if(p = strstr(opt->name, " -check ")) {
15113             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15114             opt->value = (def != 0);
15115             opt->type = CheckBox;
15116         } else if(p = strstr(opt->name, " -combo ")) {
15117             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15118             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15119             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15120             opt->value = n = 0;
15121             while(q = StrStr(q, " /// ")) {
15122                 n++; *q = 0;    // count choices, and null-terminate each of them
15123                 q += 5;
15124                 if(*q == '*') { // remember default, which is marked with * prefix
15125                     q++;
15126                     opt->value = n;
15127                 }
15128                 cps->comboList[cps->comboCnt++] = q;
15129             }
15130             cps->comboList[cps->comboCnt++] = NULL;
15131             opt->max = n + 1;
15132             opt->type = ComboBox;
15133         } else if(p = strstr(opt->name, " -button")) {
15134             opt->type = Button;
15135         } else if(p = strstr(opt->name, " -save")) {
15136             opt->type = SaveButton;
15137         } else return FALSE;
15138         *p = 0; // terminate option name
15139         // now look if the command-line options define a setting for this engine option.
15140         if(cps->optionSettings && cps->optionSettings[0])
15141             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15142         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15143           snprintf(buf, MSG_SIZ, "option %s", p);
15144                 if(p = strstr(buf, ",")) *p = 0;
15145                 if(q = strchr(buf, '=')) switch(opt->type) {
15146                     case ComboBox:
15147                         for(n=0; n<opt->max; n++)
15148                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15149                         break;
15150                     case TextBox:
15151                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15152                         break;
15153                     case Spin:
15154                     case CheckBox:
15155                         opt->value = atoi(q+1);
15156                     default:
15157                         break;
15158                 }
15159                 strcat(buf, "\n");
15160                 SendToProgram(buf, cps);
15161         }
15162         return TRUE;
15163 }
15164
15165 void
15166 FeatureDone(cps, val)
15167      ChessProgramState* cps;
15168      int val;
15169 {
15170   DelayedEventCallback cb = GetDelayedEvent();
15171   if ((cb == InitBackEnd3 && cps == &first) ||
15172       (cb == SettingsMenuIfReady && cps == &second) ||
15173       (cb == LoadEngine) ||
15174       (cb == TwoMachinesEventIfReady)) {
15175     CancelDelayedEvent();
15176     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15177   }
15178   cps->initDone = val;
15179 }
15180
15181 /* Parse feature command from engine */
15182 void
15183 ParseFeatures(args, cps)
15184      char* args;
15185      ChessProgramState *cps;
15186 {
15187   char *p = args;
15188   char *q;
15189   int val;
15190   char buf[MSG_SIZ];
15191
15192   for (;;) {
15193     while (*p == ' ') p++;
15194     if (*p == NULLCHAR) return;
15195
15196     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15197     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15198     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15199     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15200     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15201     if (BoolFeature(&p, "reuse", &val, cps)) {
15202       /* Engine can disable reuse, but can't enable it if user said no */
15203       if (!val) cps->reuse = FALSE;
15204       continue;
15205     }
15206     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15207     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15208       if (gameMode == TwoMachinesPlay) {
15209         DisplayTwoMachinesTitle();
15210       } else {
15211         DisplayTitle("");
15212       }
15213       continue;
15214     }
15215     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15216     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15217     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15218     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15219     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15220     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15221     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15222     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15223     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15224     if (IntFeature(&p, "done", &val, cps)) {
15225       FeatureDone(cps, val);
15226       continue;
15227     }
15228     /* Added by Tord: */
15229     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15230     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15231     /* End of additions by Tord */
15232
15233     /* [HGM] added features: */
15234     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15235     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15236     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15237     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15238     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15239     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15240     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15241         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15242           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15243             SendToProgram(buf, cps);
15244             continue;
15245         }
15246         if(cps->nrOptions >= MAX_OPTIONS) {
15247             cps->nrOptions--;
15248             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15249             DisplayError(buf, 0);
15250         }
15251         continue;
15252     }
15253     /* End of additions by HGM */
15254
15255     /* unknown feature: complain and skip */
15256     q = p;
15257     while (*q && *q != '=') q++;
15258     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15259     SendToProgram(buf, cps);
15260     p = q;
15261     if (*p == '=') {
15262       p++;
15263       if (*p == '\"') {
15264         p++;
15265         while (*p && *p != '\"') p++;
15266         if (*p == '\"') p++;
15267       } else {
15268         while (*p && *p != ' ') p++;
15269       }
15270     }
15271   }
15272
15273 }
15274
15275 void
15276 PeriodicUpdatesEvent(newState)
15277      int newState;
15278 {
15279     if (newState == appData.periodicUpdates)
15280       return;
15281
15282     appData.periodicUpdates=newState;
15283
15284     /* Display type changes, so update it now */
15285 //    DisplayAnalysis();
15286
15287     /* Get the ball rolling again... */
15288     if (newState) {
15289         AnalysisPeriodicEvent(1);
15290         StartAnalysisClock();
15291     }
15292 }
15293
15294 void
15295 PonderNextMoveEvent(newState)
15296      int newState;
15297 {
15298     if (newState == appData.ponderNextMove) return;
15299     if (gameMode == EditPosition) EditPositionDone(TRUE);
15300     if (newState) {
15301         SendToProgram("hard\n", &first);
15302         if (gameMode == TwoMachinesPlay) {
15303             SendToProgram("hard\n", &second);
15304         }
15305     } else {
15306         SendToProgram("easy\n", &first);
15307         thinkOutput[0] = NULLCHAR;
15308         if (gameMode == TwoMachinesPlay) {
15309             SendToProgram("easy\n", &second);
15310         }
15311     }
15312     appData.ponderNextMove = newState;
15313 }
15314
15315 void
15316 NewSettingEvent(option, feature, command, value)
15317      char *command;
15318      int option, value, *feature;
15319 {
15320     char buf[MSG_SIZ];
15321
15322     if (gameMode == EditPosition) EditPositionDone(TRUE);
15323     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15324     if(feature == NULL || *feature) SendToProgram(buf, &first);
15325     if (gameMode == TwoMachinesPlay) {
15326         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15327     }
15328 }
15329
15330 void
15331 ShowThinkingEvent()
15332 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15333 {
15334     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15335     int newState = appData.showThinking
15336         // [HGM] thinking: other features now need thinking output as well
15337         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15338
15339     if (oldState == newState) return;
15340     oldState = newState;
15341     if (gameMode == EditPosition) EditPositionDone(TRUE);
15342     if (oldState) {
15343         SendToProgram("post\n", &first);
15344         if (gameMode == TwoMachinesPlay) {
15345             SendToProgram("post\n", &second);
15346         }
15347     } else {
15348         SendToProgram("nopost\n", &first);
15349         thinkOutput[0] = NULLCHAR;
15350         if (gameMode == TwoMachinesPlay) {
15351             SendToProgram("nopost\n", &second);
15352         }
15353     }
15354 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15355 }
15356
15357 void
15358 AskQuestionEvent(title, question, replyPrefix, which)
15359      char *title; char *question; char *replyPrefix; char *which;
15360 {
15361   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15362   if (pr == NoProc) return;
15363   AskQuestion(title, question, replyPrefix, pr);
15364 }
15365
15366 void
15367 TypeInEvent(char firstChar)
15368 {
15369     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15370         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15371         gameMode == AnalyzeMode || gameMode == EditGame || 
15372         gameMode == EditPosition || gameMode == IcsExamining ||
15373         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15374         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15375                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15376                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15377         gameMode == Training) PopUpMoveDialog(firstChar);
15378 }
15379
15380 void
15381 TypeInDoneEvent(char *move)
15382 {
15383         Board board;
15384         int n, fromX, fromY, toX, toY;
15385         char promoChar;
15386         ChessMove moveType;
15387
15388         // [HGM] FENedit
15389         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15390                 EditPositionPasteFEN(move);
15391                 return;
15392         }
15393         // [HGM] movenum: allow move number to be typed in any mode
15394         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15395           ToNrEvent(2*n-1);
15396           return;
15397         }
15398
15399       if (gameMode != EditGame && currentMove != forwardMostMove && 
15400         gameMode != Training) {
15401         DisplayMoveError(_("Displayed move is not current"));
15402       } else {
15403         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15404           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15405         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15406         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15407           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15408           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15409         } else {
15410           DisplayMoveError(_("Could not parse move"));
15411         }
15412       }
15413 }
15414
15415 void
15416 DisplayMove(moveNumber)
15417      int moveNumber;
15418 {
15419     char message[MSG_SIZ];
15420     char res[MSG_SIZ];
15421     char cpThinkOutput[MSG_SIZ];
15422
15423     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15424
15425     if (moveNumber == forwardMostMove - 1 ||
15426         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15427
15428         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15429
15430         if (strchr(cpThinkOutput, '\n')) {
15431             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15432         }
15433     } else {
15434         *cpThinkOutput = NULLCHAR;
15435     }
15436
15437     /* [AS] Hide thinking from human user */
15438     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15439         *cpThinkOutput = NULLCHAR;
15440         if( thinkOutput[0] != NULLCHAR ) {
15441             int i;
15442
15443             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15444                 cpThinkOutput[i] = '.';
15445             }
15446             cpThinkOutput[i] = NULLCHAR;
15447             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15448         }
15449     }
15450
15451     if (moveNumber == forwardMostMove - 1 &&
15452         gameInfo.resultDetails != NULL) {
15453         if (gameInfo.resultDetails[0] == NULLCHAR) {
15454           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15455         } else {
15456           snprintf(res, MSG_SIZ, " {%s} %s",
15457                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15458         }
15459     } else {
15460         res[0] = NULLCHAR;
15461     }
15462
15463     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15464         DisplayMessage(res, cpThinkOutput);
15465     } else {
15466       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15467                 WhiteOnMove(moveNumber) ? " " : ".. ",
15468                 parseList[moveNumber], res);
15469         DisplayMessage(message, cpThinkOutput);
15470     }
15471 }
15472
15473 void
15474 DisplayComment(moveNumber, text)
15475      int moveNumber;
15476      char *text;
15477 {
15478     char title[MSG_SIZ];
15479
15480     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15481       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15482     } else {
15483       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15484               WhiteOnMove(moveNumber) ? " " : ".. ",
15485               parseList[moveNumber]);
15486     }
15487     if (text != NULL && (appData.autoDisplayComment || commentUp))
15488         CommentPopUp(title, text);
15489 }
15490
15491 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15492  * might be busy thinking or pondering.  It can be omitted if your
15493  * gnuchess is configured to stop thinking immediately on any user
15494  * input.  However, that gnuchess feature depends on the FIONREAD
15495  * ioctl, which does not work properly on some flavors of Unix.
15496  */
15497 void
15498 Attention(cps)
15499      ChessProgramState *cps;
15500 {
15501 #if ATTENTION
15502     if (!cps->useSigint) return;
15503     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15504     switch (gameMode) {
15505       case MachinePlaysWhite:
15506       case MachinePlaysBlack:
15507       case TwoMachinesPlay:
15508       case IcsPlayingWhite:
15509       case IcsPlayingBlack:
15510       case AnalyzeMode:
15511       case AnalyzeFile:
15512         /* Skip if we know it isn't thinking */
15513         if (!cps->maybeThinking) return;
15514         if (appData.debugMode)
15515           fprintf(debugFP, "Interrupting %s\n", cps->which);
15516         InterruptChildProcess(cps->pr);
15517         cps->maybeThinking = FALSE;
15518         break;
15519       default:
15520         break;
15521     }
15522 #endif /*ATTENTION*/
15523 }
15524
15525 int
15526 CheckFlags()
15527 {
15528     if (whiteTimeRemaining <= 0) {
15529         if (!whiteFlag) {
15530             whiteFlag = TRUE;
15531             if (appData.icsActive) {
15532                 if (appData.autoCallFlag &&
15533                     gameMode == IcsPlayingBlack && !blackFlag) {
15534                   SendToICS(ics_prefix);
15535                   SendToICS("flag\n");
15536                 }
15537             } else {
15538                 if (blackFlag) {
15539                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15540                 } else {
15541                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15542                     if (appData.autoCallFlag) {
15543                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15544                         return TRUE;
15545                     }
15546                 }
15547             }
15548         }
15549     }
15550     if (blackTimeRemaining <= 0) {
15551         if (!blackFlag) {
15552             blackFlag = TRUE;
15553             if (appData.icsActive) {
15554                 if (appData.autoCallFlag &&
15555                     gameMode == IcsPlayingWhite && !whiteFlag) {
15556                   SendToICS(ics_prefix);
15557                   SendToICS("flag\n");
15558                 }
15559             } else {
15560                 if (whiteFlag) {
15561                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15562                 } else {
15563                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15564                     if (appData.autoCallFlag) {
15565                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15566                         return TRUE;
15567                     }
15568                 }
15569             }
15570         }
15571     }
15572     return FALSE;
15573 }
15574
15575 void
15576 CheckTimeControl()
15577 {
15578     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15579         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15580
15581     /*
15582      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15583      */
15584     if ( !WhiteOnMove(forwardMostMove) ) {
15585         /* White made time control */
15586         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15587         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15588         /* [HGM] time odds: correct new time quota for time odds! */
15589                                             / WhitePlayer()->timeOdds;
15590         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15591     } else {
15592         lastBlack -= blackTimeRemaining;
15593         /* Black made time control */
15594         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15595                                             / WhitePlayer()->other->timeOdds;
15596         lastWhite = whiteTimeRemaining;
15597     }
15598 }
15599
15600 void
15601 DisplayBothClocks()
15602 {
15603     int wom = gameMode == EditPosition ?
15604       !blackPlaysFirst : WhiteOnMove(currentMove);
15605     DisplayWhiteClock(whiteTimeRemaining, wom);
15606     DisplayBlackClock(blackTimeRemaining, !wom);
15607 }
15608
15609
15610 /* Timekeeping seems to be a portability nightmare.  I think everyone
15611    has ftime(), but I'm really not sure, so I'm including some ifdefs
15612    to use other calls if you don't.  Clocks will be less accurate if
15613    you have neither ftime nor gettimeofday.
15614 */
15615
15616 /* VS 2008 requires the #include outside of the function */
15617 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15618 #include <sys/timeb.h>
15619 #endif
15620
15621 /* Get the current time as a TimeMark */
15622 void
15623 GetTimeMark(tm)
15624      TimeMark *tm;
15625 {
15626 #if HAVE_GETTIMEOFDAY
15627
15628     struct timeval timeVal;
15629     struct timezone timeZone;
15630
15631     gettimeofday(&timeVal, &timeZone);
15632     tm->sec = (long) timeVal.tv_sec;
15633     tm->ms = (int) (timeVal.tv_usec / 1000L);
15634
15635 #else /*!HAVE_GETTIMEOFDAY*/
15636 #if HAVE_FTIME
15637
15638 // include <sys/timeb.h> / moved to just above start of function
15639     struct timeb timeB;
15640
15641     ftime(&timeB);
15642     tm->sec = (long) timeB.time;
15643     tm->ms = (int) timeB.millitm;
15644
15645 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15646     tm->sec = (long) time(NULL);
15647     tm->ms = 0;
15648 #endif
15649 #endif
15650 }
15651
15652 /* Return the difference in milliseconds between two
15653    time marks.  We assume the difference will fit in a long!
15654 */
15655 long
15656 SubtractTimeMarks(tm2, tm1)
15657      TimeMark *tm2, *tm1;
15658 {
15659     return 1000L*(tm2->sec - tm1->sec) +
15660            (long) (tm2->ms - tm1->ms);
15661 }
15662
15663
15664 /*
15665  * Code to manage the game clocks.
15666  *
15667  * In tournament play, black starts the clock and then white makes a move.
15668  * We give the human user a slight advantage if he is playing white---the
15669  * clocks don't run until he makes his first move, so it takes zero time.
15670  * Also, we don't account for network lag, so we could get out of sync
15671  * with GNU Chess's clock -- but then, referees are always right.
15672  */
15673
15674 static TimeMark tickStartTM;
15675 static long intendedTickLength;
15676
15677 long
15678 NextTickLength(timeRemaining)
15679      long timeRemaining;
15680 {
15681     long nominalTickLength, nextTickLength;
15682
15683     if (timeRemaining > 0L && timeRemaining <= 10000L)
15684       nominalTickLength = 100L;
15685     else
15686       nominalTickLength = 1000L;
15687     nextTickLength = timeRemaining % nominalTickLength;
15688     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15689
15690     return nextTickLength;
15691 }
15692
15693 /* Adjust clock one minute up or down */
15694 void
15695 AdjustClock(Boolean which, int dir)
15696 {
15697     if(which) blackTimeRemaining += 60000*dir;
15698     else      whiteTimeRemaining += 60000*dir;
15699     DisplayBothClocks();
15700 }
15701
15702 /* Stop clocks and reset to a fresh time control */
15703 void
15704 ResetClocks()
15705 {
15706     (void) StopClockTimer();
15707     if (appData.icsActive) {
15708         whiteTimeRemaining = blackTimeRemaining = 0;
15709     } else if (searchTime) {
15710         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15711         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15712     } else { /* [HGM] correct new time quote for time odds */
15713         whiteTC = blackTC = fullTimeControlString;
15714         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15715         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15716     }
15717     if (whiteFlag || blackFlag) {
15718         DisplayTitle("");
15719         whiteFlag = blackFlag = FALSE;
15720     }
15721     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15722     DisplayBothClocks();
15723 }
15724
15725 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15726
15727 /* Decrement running clock by amount of time that has passed */
15728 void
15729 DecrementClocks()
15730 {
15731     long timeRemaining;
15732     long lastTickLength, fudge;
15733     TimeMark now;
15734
15735     if (!appData.clockMode) return;
15736     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15737
15738     GetTimeMark(&now);
15739
15740     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15741
15742     /* Fudge if we woke up a little too soon */
15743     fudge = intendedTickLength - lastTickLength;
15744     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15745
15746     if (WhiteOnMove(forwardMostMove)) {
15747         if(whiteNPS >= 0) lastTickLength = 0;
15748         timeRemaining = whiteTimeRemaining -= lastTickLength;
15749         if(timeRemaining < 0 && !appData.icsActive) {
15750             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15751             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15752                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15753                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15754             }
15755         }
15756         DisplayWhiteClock(whiteTimeRemaining - fudge,
15757                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15758     } else {
15759         if(blackNPS >= 0) lastTickLength = 0;
15760         timeRemaining = blackTimeRemaining -= lastTickLength;
15761         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15762             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15763             if(suddenDeath) {
15764                 blackStartMove = forwardMostMove;
15765                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15766             }
15767         }
15768         DisplayBlackClock(blackTimeRemaining - fudge,
15769                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15770     }
15771     if (CheckFlags()) return;
15772
15773     tickStartTM = now;
15774     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15775     StartClockTimer(intendedTickLength);
15776
15777     /* if the time remaining has fallen below the alarm threshold, sound the
15778      * alarm. if the alarm has sounded and (due to a takeback or time control
15779      * with increment) the time remaining has increased to a level above the
15780      * threshold, reset the alarm so it can sound again.
15781      */
15782
15783     if (appData.icsActive && appData.icsAlarm) {
15784
15785         /* make sure we are dealing with the user's clock */
15786         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15787                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15788            )) return;
15789
15790         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15791             alarmSounded = FALSE;
15792         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15793             PlayAlarmSound();
15794             alarmSounded = TRUE;
15795         }
15796     }
15797 }
15798
15799
15800 /* A player has just moved, so stop the previously running
15801    clock and (if in clock mode) start the other one.
15802    We redisplay both clocks in case we're in ICS mode, because
15803    ICS gives us an update to both clocks after every move.
15804    Note that this routine is called *after* forwardMostMove
15805    is updated, so the last fractional tick must be subtracted
15806    from the color that is *not* on move now.
15807 */
15808 void
15809 SwitchClocks(int newMoveNr)
15810 {
15811     long lastTickLength;
15812     TimeMark now;
15813     int flagged = FALSE;
15814
15815     GetTimeMark(&now);
15816
15817     if (StopClockTimer() && appData.clockMode) {
15818         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15819         if (!WhiteOnMove(forwardMostMove)) {
15820             if(blackNPS >= 0) lastTickLength = 0;
15821             blackTimeRemaining -= lastTickLength;
15822            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15823 //         if(pvInfoList[forwardMostMove].time == -1)
15824                  pvInfoList[forwardMostMove].time =               // use GUI time
15825                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15826         } else {
15827            if(whiteNPS >= 0) lastTickLength = 0;
15828            whiteTimeRemaining -= lastTickLength;
15829            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15830 //         if(pvInfoList[forwardMostMove].time == -1)
15831                  pvInfoList[forwardMostMove].time =
15832                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15833         }
15834         flagged = CheckFlags();
15835     }
15836     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15837     CheckTimeControl();
15838
15839     if (flagged || !appData.clockMode) return;
15840
15841     switch (gameMode) {
15842       case MachinePlaysBlack:
15843       case MachinePlaysWhite:
15844       case BeginningOfGame:
15845         if (pausing) return;
15846         break;
15847
15848       case EditGame:
15849       case PlayFromGameFile:
15850       case IcsExamining:
15851         return;
15852
15853       default:
15854         break;
15855     }
15856
15857     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15858         if(WhiteOnMove(forwardMostMove))
15859              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15860         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15861     }
15862
15863     tickStartTM = now;
15864     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15865       whiteTimeRemaining : blackTimeRemaining);
15866     StartClockTimer(intendedTickLength);
15867 }
15868
15869
15870 /* Stop both clocks */
15871 void
15872 StopClocks()
15873 {
15874     long lastTickLength;
15875     TimeMark now;
15876
15877     if (!StopClockTimer()) return;
15878     if (!appData.clockMode) return;
15879
15880     GetTimeMark(&now);
15881
15882     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15883     if (WhiteOnMove(forwardMostMove)) {
15884         if(whiteNPS >= 0) lastTickLength = 0;
15885         whiteTimeRemaining -= lastTickLength;
15886         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15887     } else {
15888         if(blackNPS >= 0) lastTickLength = 0;
15889         blackTimeRemaining -= lastTickLength;
15890         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15891     }
15892     CheckFlags();
15893 }
15894
15895 /* Start clock of player on move.  Time may have been reset, so
15896    if clock is already running, stop and restart it. */
15897 void
15898 StartClocks()
15899 {
15900     (void) StopClockTimer(); /* in case it was running already */
15901     DisplayBothClocks();
15902     if (CheckFlags()) return;
15903
15904     if (!appData.clockMode) return;
15905     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15906
15907     GetTimeMark(&tickStartTM);
15908     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15909       whiteTimeRemaining : blackTimeRemaining);
15910
15911    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15912     whiteNPS = blackNPS = -1;
15913     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15914        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15915         whiteNPS = first.nps;
15916     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15917        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15918         blackNPS = first.nps;
15919     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15920         whiteNPS = second.nps;
15921     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15922         blackNPS = second.nps;
15923     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15924
15925     StartClockTimer(intendedTickLength);
15926 }
15927
15928 char *
15929 TimeString(ms)
15930      long ms;
15931 {
15932     long second, minute, hour, day;
15933     char *sign = "";
15934     static char buf[32];
15935
15936     if (ms > 0 && ms <= 9900) {
15937       /* convert milliseconds to tenths, rounding up */
15938       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15939
15940       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15941       return buf;
15942     }
15943
15944     /* convert milliseconds to seconds, rounding up */
15945     /* use floating point to avoid strangeness of integer division
15946        with negative dividends on many machines */
15947     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15948
15949     if (second < 0) {
15950         sign = "-";
15951         second = -second;
15952     }
15953
15954     day = second / (60 * 60 * 24);
15955     second = second % (60 * 60 * 24);
15956     hour = second / (60 * 60);
15957     second = second % (60 * 60);
15958     minute = second / 60;
15959     second = second % 60;
15960
15961     if (day > 0)
15962       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15963               sign, day, hour, minute, second);
15964     else if (hour > 0)
15965       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15966     else
15967       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15968
15969     return buf;
15970 }
15971
15972
15973 /*
15974  * This is necessary because some C libraries aren't ANSI C compliant yet.
15975  */
15976 char *
15977 StrStr(string, match)
15978      char *string, *match;
15979 {
15980     int i, length;
15981
15982     length = strlen(match);
15983
15984     for (i = strlen(string) - length; i >= 0; i--, string++)
15985       if (!strncmp(match, string, length))
15986         return string;
15987
15988     return NULL;
15989 }
15990
15991 char *
15992 StrCaseStr(string, match)
15993      char *string, *match;
15994 {
15995     int i, j, length;
15996
15997     length = strlen(match);
15998
15999     for (i = strlen(string) - length; i >= 0; i--, string++) {
16000         for (j = 0; j < length; j++) {
16001             if (ToLower(match[j]) != ToLower(string[j]))
16002               break;
16003         }
16004         if (j == length) return string;
16005     }
16006
16007     return NULL;
16008 }
16009
16010 #ifndef _amigados
16011 int
16012 StrCaseCmp(s1, s2)
16013      char *s1, *s2;
16014 {
16015     char c1, c2;
16016
16017     for (;;) {
16018         c1 = ToLower(*s1++);
16019         c2 = ToLower(*s2++);
16020         if (c1 > c2) return 1;
16021         if (c1 < c2) return -1;
16022         if (c1 == NULLCHAR) return 0;
16023     }
16024 }
16025
16026
16027 int
16028 ToLower(c)
16029      int c;
16030 {
16031     return isupper(c) ? tolower(c) : c;
16032 }
16033
16034
16035 int
16036 ToUpper(c)
16037      int c;
16038 {
16039     return islower(c) ? toupper(c) : c;
16040 }
16041 #endif /* !_amigados    */
16042
16043 char *
16044 StrSave(s)
16045      char *s;
16046 {
16047   char *ret;
16048
16049   if ((ret = (char *) malloc(strlen(s) + 1)))
16050     {
16051       safeStrCpy(ret, s, strlen(s)+1);
16052     }
16053   return ret;
16054 }
16055
16056 char *
16057 StrSavePtr(s, savePtr)
16058      char *s, **savePtr;
16059 {
16060     if (*savePtr) {
16061         free(*savePtr);
16062     }
16063     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16064       safeStrCpy(*savePtr, s, strlen(s)+1);
16065     }
16066     return(*savePtr);
16067 }
16068
16069 char *
16070 PGNDate()
16071 {
16072     time_t clock;
16073     struct tm *tm;
16074     char buf[MSG_SIZ];
16075
16076     clock = time((time_t *)NULL);
16077     tm = localtime(&clock);
16078     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16079             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16080     return StrSave(buf);
16081 }
16082
16083
16084 char *
16085 PositionToFEN(move, overrideCastling)
16086      int move;
16087      char *overrideCastling;
16088 {
16089     int i, j, fromX, fromY, toX, toY;
16090     int whiteToPlay;
16091     char buf[MSG_SIZ];
16092     char *p, *q;
16093     int emptycount;
16094     ChessSquare piece;
16095
16096     whiteToPlay = (gameMode == EditPosition) ?
16097       !blackPlaysFirst : (move % 2 == 0);
16098     p = buf;
16099
16100     /* Piece placement data */
16101     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16102         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16103         emptycount = 0;
16104         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16105             if (boards[move][i][j] == EmptySquare) {
16106                 emptycount++;
16107             } else { ChessSquare piece = boards[move][i][j];
16108                 if (emptycount > 0) {
16109                     if(emptycount<10) /* [HGM] can be >= 10 */
16110                         *p++ = '0' + emptycount;
16111                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16112                     emptycount = 0;
16113                 }
16114                 if(PieceToChar(piece) == '+') {
16115                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16116                     *p++ = '+';
16117                     piece = (ChessSquare)(DEMOTED piece);
16118                 }
16119                 *p++ = PieceToChar(piece);
16120                 if(p[-1] == '~') {
16121                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16122                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16123                     *p++ = '~';
16124                 }
16125             }
16126         }
16127         if (emptycount > 0) {
16128             if(emptycount<10) /* [HGM] can be >= 10 */
16129                 *p++ = '0' + emptycount;
16130             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16131             emptycount = 0;
16132         }
16133         *p++ = '/';
16134     }
16135     *(p - 1) = ' ';
16136
16137     /* [HGM] print Crazyhouse or Shogi holdings */
16138     if( gameInfo.holdingsWidth ) {
16139         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16140         q = p;
16141         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16142             piece = boards[move][i][BOARD_WIDTH-1];
16143             if( piece != EmptySquare )
16144               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16145                   *p++ = PieceToChar(piece);
16146         }
16147         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16148             piece = boards[move][BOARD_HEIGHT-i-1][0];
16149             if( piece != EmptySquare )
16150               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16151                   *p++ = PieceToChar(piece);
16152         }
16153
16154         if( q == p ) *p++ = '-';
16155         *p++ = ']';
16156         *p++ = ' ';
16157     }
16158
16159     /* Active color */
16160     *p++ = whiteToPlay ? 'w' : 'b';
16161     *p++ = ' ';
16162
16163   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16164     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16165   } else {
16166   if(nrCastlingRights) {
16167      q = p;
16168      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16169        /* [HGM] write directly from rights */
16170            if(boards[move][CASTLING][2] != NoRights &&
16171               boards[move][CASTLING][0] != NoRights   )
16172                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16173            if(boards[move][CASTLING][2] != NoRights &&
16174               boards[move][CASTLING][1] != NoRights   )
16175                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16176            if(boards[move][CASTLING][5] != NoRights &&
16177               boards[move][CASTLING][3] != NoRights   )
16178                 *p++ = boards[move][CASTLING][3] + AAA;
16179            if(boards[move][CASTLING][5] != NoRights &&
16180               boards[move][CASTLING][4] != NoRights   )
16181                 *p++ = boards[move][CASTLING][4] + AAA;
16182      } else {
16183
16184         /* [HGM] write true castling rights */
16185         if( nrCastlingRights == 6 ) {
16186             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16187                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16188             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16189                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16190             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16191                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16192             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16193                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16194         }
16195      }
16196      if (q == p) *p++ = '-'; /* No castling rights */
16197      *p++ = ' ';
16198   }
16199
16200   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16201      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16202     /* En passant target square */
16203     if (move > backwardMostMove) {
16204         fromX = moveList[move - 1][0] - AAA;
16205         fromY = moveList[move - 1][1] - ONE;
16206         toX = moveList[move - 1][2] - AAA;
16207         toY = moveList[move - 1][3] - ONE;
16208         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16209             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16210             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16211             fromX == toX) {
16212             /* 2-square pawn move just happened */
16213             *p++ = toX + AAA;
16214             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16215         } else {
16216             *p++ = '-';
16217         }
16218     } else if(move == backwardMostMove) {
16219         // [HGM] perhaps we should always do it like this, and forget the above?
16220         if((signed char)boards[move][EP_STATUS] >= 0) {
16221             *p++ = boards[move][EP_STATUS] + AAA;
16222             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16223         } else {
16224             *p++ = '-';
16225         }
16226     } else {
16227         *p++ = '-';
16228     }
16229     *p++ = ' ';
16230   }
16231   }
16232
16233     /* [HGM] find reversible plies */
16234     {   int i = 0, j=move;
16235
16236         if (appData.debugMode) { int k;
16237             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16238             for(k=backwardMostMove; k<=forwardMostMove; k++)
16239                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16240
16241         }
16242
16243         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16244         if( j == backwardMostMove ) i += initialRulePlies;
16245         sprintf(p, "%d ", i);
16246         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16247     }
16248     /* Fullmove number */
16249     sprintf(p, "%d", (move / 2) + 1);
16250
16251     return StrSave(buf);
16252 }
16253
16254 Boolean
16255 ParseFEN(board, blackPlaysFirst, fen)
16256     Board board;
16257      int *blackPlaysFirst;
16258      char *fen;
16259 {
16260     int i, j;
16261     char *p, c;
16262     int emptycount;
16263     ChessSquare piece;
16264
16265     p = fen;
16266
16267     /* [HGM] by default clear Crazyhouse holdings, if present */
16268     if(gameInfo.holdingsWidth) {
16269        for(i=0; i<BOARD_HEIGHT; i++) {
16270            board[i][0]             = EmptySquare; /* black holdings */
16271            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16272            board[i][1]             = (ChessSquare) 0; /* black counts */
16273            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16274        }
16275     }
16276
16277     /* Piece placement data */
16278     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16279         j = 0;
16280         for (;;) {
16281             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16282                 if (*p == '/') p++;
16283                 emptycount = gameInfo.boardWidth - j;
16284                 while (emptycount--)
16285                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16286                 break;
16287 #if(BOARD_FILES >= 10)
16288             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16289                 p++; emptycount=10;
16290                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16291                 while (emptycount--)
16292                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16293 #endif
16294             } else if (isdigit(*p)) {
16295                 emptycount = *p++ - '0';
16296                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16297                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16298                 while (emptycount--)
16299                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16300             } else if (*p == '+' || isalpha(*p)) {
16301                 if (j >= gameInfo.boardWidth) return FALSE;
16302                 if(*p=='+') {
16303                     piece = CharToPiece(*++p);
16304                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16305                     piece = (ChessSquare) (PROMOTED piece ); p++;
16306                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16307                 } else piece = CharToPiece(*p++);
16308
16309                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16310                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16311                     piece = (ChessSquare) (PROMOTED piece);
16312                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16313                     p++;
16314                 }
16315                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16316             } else {
16317                 return FALSE;
16318             }
16319         }
16320     }
16321     while (*p == '/' || *p == ' ') p++;
16322
16323     /* [HGM] look for Crazyhouse holdings here */
16324     while(*p==' ') p++;
16325     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16326         if(*p == '[') p++;
16327         if(*p == '-' ) p++; /* empty holdings */ else {
16328             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16329             /* if we would allow FEN reading to set board size, we would   */
16330             /* have to add holdings and shift the board read so far here   */
16331             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16332                 p++;
16333                 if((int) piece >= (int) BlackPawn ) {
16334                     i = (int)piece - (int)BlackPawn;
16335                     i = PieceToNumber((ChessSquare)i);
16336                     if( i >= gameInfo.holdingsSize ) return FALSE;
16337                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16338                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16339                 } else {
16340                     i = (int)piece - (int)WhitePawn;
16341                     i = PieceToNumber((ChessSquare)i);
16342                     if( i >= gameInfo.holdingsSize ) return FALSE;
16343                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16344                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16345                 }
16346             }
16347         }
16348         if(*p == ']') p++;
16349     }
16350
16351     while(*p == ' ') p++;
16352
16353     /* Active color */
16354     c = *p++;
16355     if(appData.colorNickNames) {
16356       if( c == appData.colorNickNames[0] ) c = 'w'; else
16357       if( c == appData.colorNickNames[1] ) c = 'b';
16358     }
16359     switch (c) {
16360       case 'w':
16361         *blackPlaysFirst = FALSE;
16362         break;
16363       case 'b':
16364         *blackPlaysFirst = TRUE;
16365         break;
16366       default:
16367         return FALSE;
16368     }
16369
16370     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16371     /* return the extra info in global variiables             */
16372
16373     /* set defaults in case FEN is incomplete */
16374     board[EP_STATUS] = EP_UNKNOWN;
16375     for(i=0; i<nrCastlingRights; i++ ) {
16376         board[CASTLING][i] =
16377             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16378     }   /* assume possible unless obviously impossible */
16379     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16380     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16381     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16382                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16383     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16384     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16385     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16386                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16387     FENrulePlies = 0;
16388
16389     while(*p==' ') p++;
16390     if(nrCastlingRights) {
16391       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16392           /* castling indicator present, so default becomes no castlings */
16393           for(i=0; i<nrCastlingRights; i++ ) {
16394                  board[CASTLING][i] = NoRights;
16395           }
16396       }
16397       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16398              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16399              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16400              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16401         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16402
16403         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16404             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16405             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16406         }
16407         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16408             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16409         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16410                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16411         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16412                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16413         switch(c) {
16414           case'K':
16415               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16416               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16417               board[CASTLING][2] = whiteKingFile;
16418               break;
16419           case'Q':
16420               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16421               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16422               board[CASTLING][2] = whiteKingFile;
16423               break;
16424           case'k':
16425               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16426               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16427               board[CASTLING][5] = blackKingFile;
16428               break;
16429           case'q':
16430               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16431               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16432               board[CASTLING][5] = blackKingFile;
16433           case '-':
16434               break;
16435           default: /* FRC castlings */
16436               if(c >= 'a') { /* black rights */
16437                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16438                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16439                   if(i == BOARD_RGHT) break;
16440                   board[CASTLING][5] = i;
16441                   c -= AAA;
16442                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16443                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16444                   if(c > i)
16445                       board[CASTLING][3] = c;
16446                   else
16447                       board[CASTLING][4] = c;
16448               } else { /* white rights */
16449                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16450                     if(board[0][i] == WhiteKing) break;
16451                   if(i == BOARD_RGHT) break;
16452                   board[CASTLING][2] = i;
16453                   c -= AAA - 'a' + 'A';
16454                   if(board[0][c] >= WhiteKing) break;
16455                   if(c > i)
16456                       board[CASTLING][0] = c;
16457                   else
16458                       board[CASTLING][1] = c;
16459               }
16460         }
16461       }
16462       for(i=0; i<nrCastlingRights; i++)
16463         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16464     if (appData.debugMode) {
16465         fprintf(debugFP, "FEN castling rights:");
16466         for(i=0; i<nrCastlingRights; i++)
16467         fprintf(debugFP, " %d", board[CASTLING][i]);
16468         fprintf(debugFP, "\n");
16469     }
16470
16471       while(*p==' ') p++;
16472     }
16473
16474     /* read e.p. field in games that know e.p. capture */
16475     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16476        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16477       if(*p=='-') {
16478         p++; board[EP_STATUS] = EP_NONE;
16479       } else {
16480          char c = *p++ - AAA;
16481
16482          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16483          if(*p >= '0' && *p <='9') p++;
16484          board[EP_STATUS] = c;
16485       }
16486     }
16487
16488
16489     if(sscanf(p, "%d", &i) == 1) {
16490         FENrulePlies = i; /* 50-move ply counter */
16491         /* (The move number is still ignored)    */
16492     }
16493
16494     return TRUE;
16495 }
16496
16497 void
16498 EditPositionPasteFEN(char *fen)
16499 {
16500   if (fen != NULL) {
16501     Board initial_position;
16502
16503     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16504       DisplayError(_("Bad FEN position in clipboard"), 0);
16505       return ;
16506     } else {
16507       int savedBlackPlaysFirst = blackPlaysFirst;
16508       EditPositionEvent();
16509       blackPlaysFirst = savedBlackPlaysFirst;
16510       CopyBoard(boards[0], initial_position);
16511       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16512       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16513       DisplayBothClocks();
16514       DrawPosition(FALSE, boards[currentMove]);
16515     }
16516   }
16517 }
16518
16519 static char cseq[12] = "\\   ";
16520
16521 Boolean set_cont_sequence(char *new_seq)
16522 {
16523     int len;
16524     Boolean ret;
16525
16526     // handle bad attempts to set the sequence
16527         if (!new_seq)
16528                 return 0; // acceptable error - no debug
16529
16530     len = strlen(new_seq);
16531     ret = (len > 0) && (len < sizeof(cseq));
16532     if (ret)
16533       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16534     else if (appData.debugMode)
16535       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16536     return ret;
16537 }
16538
16539 /*
16540     reformat a source message so words don't cross the width boundary.  internal
16541     newlines are not removed.  returns the wrapped size (no null character unless
16542     included in source message).  If dest is NULL, only calculate the size required
16543     for the dest buffer.  lp argument indicats line position upon entry, and it's
16544     passed back upon exit.
16545 */
16546 int wrap(char *dest, char *src, int count, int width, int *lp)
16547 {
16548     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16549
16550     cseq_len = strlen(cseq);
16551     old_line = line = *lp;
16552     ansi = len = clen = 0;
16553
16554     for (i=0; i < count; i++)
16555     {
16556         if (src[i] == '\033')
16557             ansi = 1;
16558
16559         // if we hit the width, back up
16560         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16561         {
16562             // store i & len in case the word is too long
16563             old_i = i, old_len = len;
16564
16565             // find the end of the last word
16566             while (i && src[i] != ' ' && src[i] != '\n')
16567             {
16568                 i--;
16569                 len--;
16570             }
16571
16572             // word too long?  restore i & len before splitting it
16573             if ((old_i-i+clen) >= width)
16574             {
16575                 i = old_i;
16576                 len = old_len;
16577             }
16578
16579             // extra space?
16580             if (i && src[i-1] == ' ')
16581                 len--;
16582
16583             if (src[i] != ' ' && src[i] != '\n')
16584             {
16585                 i--;
16586                 if (len)
16587                     len--;
16588             }
16589
16590             // now append the newline and continuation sequence
16591             if (dest)
16592                 dest[len] = '\n';
16593             len++;
16594             if (dest)
16595                 strncpy(dest+len, cseq, cseq_len);
16596             len += cseq_len;
16597             line = cseq_len;
16598             clen = cseq_len;
16599             continue;
16600         }
16601
16602         if (dest)
16603             dest[len] = src[i];
16604         len++;
16605         if (!ansi)
16606             line++;
16607         if (src[i] == '\n')
16608             line = 0;
16609         if (src[i] == 'm')
16610             ansi = 0;
16611     }
16612     if (dest && appData.debugMode)
16613     {
16614         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16615             count, width, line, len, *lp);
16616         show_bytes(debugFP, src, count);
16617         fprintf(debugFP, "\ndest: ");
16618         show_bytes(debugFP, dest, len);
16619         fprintf(debugFP, "\n");
16620     }
16621     *lp = dest ? line : old_line;
16622
16623     return len;
16624 }
16625
16626 // [HGM] vari: routines for shelving variations
16627 Boolean modeRestore = FALSE;
16628
16629 void
16630 PushInner(int firstMove, int lastMove)
16631 {
16632         int i, j, nrMoves = lastMove - firstMove;
16633
16634         // push current tail of game on stack
16635         savedResult[storedGames] = gameInfo.result;
16636         savedDetails[storedGames] = gameInfo.resultDetails;
16637         gameInfo.resultDetails = NULL;
16638         savedFirst[storedGames] = firstMove;
16639         savedLast [storedGames] = lastMove;
16640         savedFramePtr[storedGames] = framePtr;
16641         framePtr -= nrMoves; // reserve space for the boards
16642         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16643             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16644             for(j=0; j<MOVE_LEN; j++)
16645                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16646             for(j=0; j<2*MOVE_LEN; j++)
16647                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16648             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16649             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16650             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16651             pvInfoList[firstMove+i-1].depth = 0;
16652             commentList[framePtr+i] = commentList[firstMove+i];
16653             commentList[firstMove+i] = NULL;
16654         }
16655
16656         storedGames++;
16657         forwardMostMove = firstMove; // truncate game so we can start variation
16658 }
16659
16660 void
16661 PushTail(int firstMove, int lastMove)
16662 {
16663         if(appData.icsActive) { // only in local mode
16664                 forwardMostMove = currentMove; // mimic old ICS behavior
16665                 return;
16666         }
16667         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16668
16669         PushInner(firstMove, lastMove);
16670         if(storedGames == 1) GreyRevert(FALSE);
16671         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16672 }
16673
16674 void
16675 PopInner(Boolean annotate)
16676 {
16677         int i, j, nrMoves;
16678         char buf[8000], moveBuf[20];
16679
16680         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16681         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16682         nrMoves = savedLast[storedGames] - currentMove;
16683         if(annotate) {
16684                 int cnt = 10;
16685                 if(!WhiteOnMove(currentMove))
16686                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16687                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16688                 for(i=currentMove; i<forwardMostMove; i++) {
16689                         if(WhiteOnMove(i))
16690                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16691                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16692                         strcat(buf, moveBuf);
16693                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16694                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16695                 }
16696                 strcat(buf, ")");
16697         }
16698         for(i=1; i<=nrMoves; i++) { // copy last variation back
16699             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16700             for(j=0; j<MOVE_LEN; j++)
16701                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16702             for(j=0; j<2*MOVE_LEN; j++)
16703                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16704             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16705             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16706             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16707             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16708             commentList[currentMove+i] = commentList[framePtr+i];
16709             commentList[framePtr+i] = NULL;
16710         }
16711         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16712         framePtr = savedFramePtr[storedGames];
16713         gameInfo.result = savedResult[storedGames];
16714         if(gameInfo.resultDetails != NULL) {
16715             free(gameInfo.resultDetails);
16716       }
16717         gameInfo.resultDetails = savedDetails[storedGames];
16718         forwardMostMove = currentMove + nrMoves;
16719 }
16720
16721 Boolean
16722 PopTail(Boolean annotate)
16723 {
16724         if(appData.icsActive) return FALSE; // only in local mode
16725         if(!storedGames) return FALSE; // sanity
16726         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16727
16728         PopInner(annotate);
16729         if(currentMove < forwardMostMove) ForwardEvent(); else
16730         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16731
16732         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16733         return TRUE;
16734 }
16735
16736 void
16737 CleanupTail()
16738 {       // remove all shelved variations
16739         int i;
16740         for(i=0; i<storedGames; i++) {
16741             if(savedDetails[i])
16742                 free(savedDetails[i]);
16743             savedDetails[i] = NULL;
16744         }
16745         for(i=framePtr; i<MAX_MOVES; i++) {
16746                 if(commentList[i]) free(commentList[i]);
16747                 commentList[i] = NULL;
16748         }
16749         framePtr = MAX_MOVES-1;
16750         storedGames = 0;
16751 }
16752
16753 void
16754 LoadVariation(int index, char *text)
16755 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16756         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16757         int level = 0, move;
16758
16759         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16760         // first find outermost bracketing variation
16761         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16762             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16763                 if(*p == '{') wait = '}'; else
16764                 if(*p == '[') wait = ']'; else
16765                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16766                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16767             }
16768             if(*p == wait) wait = NULLCHAR; // closing ]} found
16769             p++;
16770         }
16771         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16772         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16773         end[1] = NULLCHAR; // clip off comment beyond variation
16774         ToNrEvent(currentMove-1);
16775         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16776         // kludge: use ParsePV() to append variation to game
16777         move = currentMove;
16778         ParsePV(start, TRUE, TRUE);
16779         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16780         ClearPremoveHighlights();
16781         CommentPopDown();
16782         ToNrEvent(currentMove+1);
16783 }
16784