Fix treatment of PGN score/depth info with linefeeds in them
[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(params[0]) {
911         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
912         snprintf(command, MSG_SIZ, "%s %s", p, params);
913         p = command;
914     }
915     appData.chessProgram[i] = strdup(p);
916     appData.isUCI[i] = isUCI;
917     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
918     appData.hasOwnBookUCI[i] = hasBook;
919     if(!nickName[0]) useNick = FALSE;
920     if(useNick) ASSIGN(appData.pgnName[i], nickName);
921     if(addToList) {
922         int len;
923         char quote;
924         q = firstChessProgramNames;
925         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
926         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
927         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
928                         quote, p, quote, appData.directory[i], 
929                         useNick ? " -fn \"" : "",
930                         useNick ? nickName : "",
931                         useNick ? "\"" : "",
932                         v1 ? " -firstProtocolVersion 1" : "",
933                         hasBook ? "" : " -fNoOwnBookUCI",
934                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
935                         storeVariant ? " -variant " : "",
936                         storeVariant ? VariantName(gameInfo.variant) : "");
937         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
938         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
939         if(q)   free(q);
940     }
941     ReplaceEngine(cps, i);
942 }
943
944 void
945 InitTimeControls()
946 {
947     int matched, min, sec;
948     /*
949      * Parse timeControl resource
950      */
951     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
952                           appData.movesPerSession)) {
953         char buf[MSG_SIZ];
954         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
955         DisplayFatalError(buf, 0, 2);
956     }
957
958     /*
959      * Parse searchTime resource
960      */
961     if (*appData.searchTime != NULLCHAR) {
962         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
963         if (matched == 1) {
964             searchTime = min * 60;
965         } else if (matched == 2) {
966             searchTime = min * 60 + sec;
967         } else {
968             char buf[MSG_SIZ];
969             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
970             DisplayFatalError(buf, 0, 2);
971         }
972     }
973 }
974
975 void
976 InitBackEnd1()
977 {
978
979     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
980     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
981
982     GetTimeMark(&programStartTime);
983     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
984     appData.seedBase = random() + (random()<<15);
985     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
986
987     ClearProgramStats();
988     programStats.ok_to_send = 1;
989     programStats.seen_stat = 0;
990
991     /*
992      * Initialize game list
993      */
994     ListNew(&gameList);
995
996
997     /*
998      * Internet chess server status
999      */
1000     if (appData.icsActive) {
1001         appData.matchMode = FALSE;
1002         appData.matchGames = 0;
1003 #if ZIPPY
1004         appData.noChessProgram = !appData.zippyPlay;
1005 #else
1006         appData.zippyPlay = FALSE;
1007         appData.zippyTalk = FALSE;
1008         appData.noChessProgram = TRUE;
1009 #endif
1010         if (*appData.icsHelper != NULLCHAR) {
1011             appData.useTelnet = TRUE;
1012             appData.telnetProgram = appData.icsHelper;
1013         }
1014     } else {
1015         appData.zippyTalk = appData.zippyPlay = FALSE;
1016     }
1017
1018     /* [AS] Initialize pv info list [HGM] and game state */
1019     {
1020         int i, j;
1021
1022         for( i=0; i<=framePtr; i++ ) {
1023             pvInfoList[i].depth = -1;
1024             boards[i][EP_STATUS] = EP_NONE;
1025             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1026         }
1027     }
1028
1029     InitTimeControls();
1030
1031     /* [AS] Adjudication threshold */
1032     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1033
1034     InitEngine(&first, 0);
1035     InitEngine(&second, 1);
1036     CommonEngineInit();
1037
1038     pairing.which = "pairing"; // pairing engine
1039     pairing.pr = NoProc;
1040     pairing.isr = NULL;
1041     pairing.program = appData.pairingEngine;
1042     pairing.host = "localhost";
1043     pairing.dir = ".";
1044
1045     if (appData.icsActive) {
1046         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1047     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1048         appData.clockMode = FALSE;
1049         first.sendTime = second.sendTime = 0;
1050     }
1051
1052 #if ZIPPY
1053     /* Override some settings from environment variables, for backward
1054        compatibility.  Unfortunately it's not feasible to have the env
1055        vars just set defaults, at least in xboard.  Ugh.
1056     */
1057     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1058       ZippyInit();
1059     }
1060 #endif
1061
1062     if (!appData.icsActive) {
1063       char buf[MSG_SIZ];
1064       int len;
1065
1066       /* Check for variants that are supported only in ICS mode,
1067          or not at all.  Some that are accepted here nevertheless
1068          have bugs; see comments below.
1069       */
1070       VariantClass variant = StringToVariant(appData.variant);
1071       switch (variant) {
1072       case VariantBughouse:     /* need four players and two boards */
1073       case VariantKriegspiel:   /* need to hide pieces and move details */
1074         /* case VariantFischeRandom: (Fabien: moved below) */
1075         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1076         if( (len > MSG_SIZ) && appData.debugMode )
1077           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1078
1079         DisplayFatalError(buf, 0, 2);
1080         return;
1081
1082       case VariantUnknown:
1083       case VariantLoadable:
1084       case Variant29:
1085       case Variant30:
1086       case Variant31:
1087       case Variant32:
1088       case Variant33:
1089       case Variant34:
1090       case Variant35:
1091       case Variant36:
1092       default:
1093         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1094         if( (len > MSG_SIZ) && appData.debugMode )
1095           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1096
1097         DisplayFatalError(buf, 0, 2);
1098         return;
1099
1100       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1101       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1102       case VariantGothic:     /* [HGM] should work */
1103       case VariantCapablanca: /* [HGM] should work */
1104       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1105       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1106       case VariantKnightmate: /* [HGM] should work */
1107       case VariantCylinder:   /* [HGM] untested */
1108       case VariantFalcon:     /* [HGM] untested */
1109       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1110                                  offboard interposition not understood */
1111       case VariantNormal:     /* definitely works! */
1112       case VariantWildCastle: /* pieces not automatically shuffled */
1113       case VariantNoCastle:   /* pieces not automatically shuffled */
1114       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1115       case VariantLosers:     /* should work except for win condition,
1116                                  and doesn't know captures are mandatory */
1117       case VariantSuicide:    /* should work except for win condition,
1118                                  and doesn't know captures are mandatory */
1119       case VariantGiveaway:   /* should work except for win condition,
1120                                  and doesn't know captures are mandatory */
1121       case VariantTwoKings:   /* should work */
1122       case VariantAtomic:     /* should work except for win condition */
1123       case Variant3Check:     /* should work except for win condition */
1124       case VariantShatranj:   /* should work except for all win conditions */
1125       case VariantMakruk:     /* should work except for draw countdown */
1126       case VariantBerolina:   /* might work if TestLegality is off */
1127       case VariantCapaRandom: /* should work */
1128       case VariantJanus:      /* should work */
1129       case VariantSuper:      /* experimental */
1130       case VariantGreat:      /* experimental, requires legality testing to be off */
1131       case VariantSChess:     /* S-Chess, should work */
1132       case VariantGrand:      /* should work */
1133       case VariantSpartan:    /* should work */
1134         break;
1135       }
1136     }
1137
1138 }
1139
1140 int NextIntegerFromString( char ** str, long * value )
1141 {
1142     int result = -1;
1143     char * s = *str;
1144
1145     while( *s == ' ' || *s == '\t' ) {
1146         s++;
1147     }
1148
1149     *value = 0;
1150
1151     if( *s >= '0' && *s <= '9' ) {
1152         while( *s >= '0' && *s <= '9' ) {
1153             *value = *value * 10 + (*s - '0');
1154             s++;
1155         }
1156
1157         result = 0;
1158     }
1159
1160     *str = s;
1161
1162     return result;
1163 }
1164
1165 int NextTimeControlFromString( char ** str, long * value )
1166 {
1167     long temp;
1168     int result = NextIntegerFromString( str, &temp );
1169
1170     if( result == 0 ) {
1171         *value = temp * 60; /* Minutes */
1172         if( **str == ':' ) {
1173             (*str)++;
1174             result = NextIntegerFromString( str, &temp );
1175             *value += temp; /* Seconds */
1176         }
1177     }
1178
1179     return result;
1180 }
1181
1182 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1183 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1184     int result = -1, type = 0; long temp, temp2;
1185
1186     if(**str != ':') return -1; // old params remain in force!
1187     (*str)++;
1188     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1189     if( NextIntegerFromString( str, &temp ) ) return -1;
1190     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1191
1192     if(**str != '/') {
1193         /* time only: incremental or sudden-death time control */
1194         if(**str == '+') { /* increment follows; read it */
1195             (*str)++;
1196             if(**str == '!') type = *(*str)++; // Bronstein TC
1197             if(result = NextIntegerFromString( str, &temp2)) return -1;
1198             *inc = temp2 * 1000;
1199             if(**str == '.') { // read fraction of increment
1200                 char *start = ++(*str);
1201                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1202                 temp2 *= 1000;
1203                 while(start++ < *str) temp2 /= 10;
1204                 *inc += temp2;
1205             }
1206         } else *inc = 0;
1207         *moves = 0; *tc = temp * 1000; *incType = type;
1208         return 0;
1209     }
1210
1211     (*str)++; /* classical time control */
1212     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1213
1214     if(result == 0) {
1215         *moves = temp;
1216         *tc    = temp2 * 1000;
1217         *inc   = 0;
1218         *incType = type;
1219     }
1220     return result;
1221 }
1222
1223 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1224 {   /* [HGM] get time to add from the multi-session time-control string */
1225     int incType, moves=1; /* kludge to force reading of first session */
1226     long time, increment;
1227     char *s = tcString;
1228
1229     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1230     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1231     do {
1232         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1233         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1234         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1235         if(movenr == -1) return time;    /* last move before new session     */
1236         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1237         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1238         if(!moves) return increment;     /* current session is incremental   */
1239         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1240     } while(movenr >= -1);               /* try again for next session       */
1241
1242     return 0; // no new time quota on this move
1243 }
1244
1245 int
1246 ParseTimeControl(tc, ti, mps)
1247      char *tc;
1248      float ti;
1249      int mps;
1250 {
1251   long tc1;
1252   long tc2;
1253   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1254   int min, sec=0;
1255
1256   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1257   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1258       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1259   if(ti > 0) {
1260
1261     if(mps)
1262       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1263     else 
1264       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1265   } else {
1266     if(mps)
1267       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1268     else 
1269       snprintf(buf, MSG_SIZ, ":%s", mytc);
1270   }
1271   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1272   
1273   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1274     return FALSE;
1275   }
1276
1277   if( *tc == '/' ) {
1278     /* Parse second time control */
1279     tc++;
1280
1281     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1282       return FALSE;
1283     }
1284
1285     if( tc2 == 0 ) {
1286       return FALSE;
1287     }
1288
1289     timeControl_2 = tc2 * 1000;
1290   }
1291   else {
1292     timeControl_2 = 0;
1293   }
1294
1295   if( tc1 == 0 ) {
1296     return FALSE;
1297   }
1298
1299   timeControl = tc1 * 1000;
1300
1301   if (ti >= 0) {
1302     timeIncrement = ti * 1000;  /* convert to ms */
1303     movesPerSession = 0;
1304   } else {
1305     timeIncrement = 0;
1306     movesPerSession = mps;
1307   }
1308   return TRUE;
1309 }
1310
1311 void
1312 InitBackEnd2()
1313 {
1314     if (appData.debugMode) {
1315         fprintf(debugFP, "%s\n", programVersion);
1316     }
1317
1318     set_cont_sequence(appData.wrapContSeq);
1319     if (appData.matchGames > 0) {
1320         appData.matchMode = TRUE;
1321     } else if (appData.matchMode) {
1322         appData.matchGames = 1;
1323     }
1324     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1325         appData.matchGames = appData.sameColorGames;
1326     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1327         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1328         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1329     }
1330     Reset(TRUE, FALSE);
1331     if (appData.noChessProgram || first.protocolVersion == 1) {
1332       InitBackEnd3();
1333     } else {
1334       /* kludge: allow timeout for initial "feature" commands */
1335       FreezeUI();
1336       DisplayMessage("", _("Starting chess program"));
1337       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1338     }
1339 }
1340
1341 int
1342 CalculateIndex(int index, int gameNr)
1343 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1344     int res;
1345     if(index > 0) return index; // fixed nmber
1346     if(index == 0) return 1;
1347     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1348     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1349     return res;
1350 }
1351
1352 int
1353 LoadGameOrPosition(int gameNr)
1354 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1355     if (*appData.loadGameFile != NULLCHAR) {
1356         if (!LoadGameFromFile(appData.loadGameFile,
1357                 CalculateIndex(appData.loadGameIndex, gameNr),
1358                               appData.loadGameFile, FALSE)) {
1359             DisplayFatalError(_("Bad game file"), 0, 1);
1360             return 0;
1361         }
1362     } else if (*appData.loadPositionFile != NULLCHAR) {
1363         if (!LoadPositionFromFile(appData.loadPositionFile,
1364                 CalculateIndex(appData.loadPositionIndex, gameNr),
1365                                   appData.loadPositionFile)) {
1366             DisplayFatalError(_("Bad position file"), 0, 1);
1367             return 0;
1368         }
1369     }
1370     return 1;
1371 }
1372
1373 void
1374 ReserveGame(int gameNr, char resChar)
1375 {
1376     FILE *tf = fopen(appData.tourneyFile, "r+");
1377     char *p, *q, c, buf[MSG_SIZ];
1378     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1379     safeStrCpy(buf, lastMsg, MSG_SIZ);
1380     DisplayMessage(_("Pick new game"), "");
1381     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1382     ParseArgsFromFile(tf);
1383     p = q = appData.results;
1384     if(appData.debugMode) {
1385       char *r = appData.participants;
1386       fprintf(debugFP, "results = '%s'\n", p);
1387       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1388       fprintf(debugFP, "\n");
1389     }
1390     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1391     nextGame = q - p;
1392     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1393     safeStrCpy(q, p, strlen(p) + 2);
1394     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1395     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1396     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1397         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1398         q[nextGame] = '*';
1399     }
1400     fseek(tf, -(strlen(p)+4), SEEK_END);
1401     c = fgetc(tf);
1402     if(c != '"') // depending on DOS or Unix line endings we can be one off
1403          fseek(tf, -(strlen(p)+2), SEEK_END);
1404     else fseek(tf, -(strlen(p)+3), SEEK_END);
1405     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1406     DisplayMessage(buf, "");
1407     free(p); appData.results = q;
1408     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1409        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1410         UnloadEngine(&first);  // next game belongs to other pairing;
1411         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1412     }
1413 }
1414
1415 void
1416 MatchEvent(int mode)
1417 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1418         int dummy;
1419         if(matchMode) { // already in match mode: switch it off
1420             abortMatch = TRUE;
1421             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1422             return;
1423         }
1424 //      if(gameMode != BeginningOfGame) {
1425 //          DisplayError(_("You can only start a match from the initial position."), 0);
1426 //          return;
1427 //      }
1428         abortMatch = FALSE;
1429         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1430         /* Set up machine vs. machine match */
1431         nextGame = 0;
1432         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1433         if(appData.tourneyFile[0]) {
1434             ReserveGame(-1, 0);
1435             if(nextGame > appData.matchGames) {
1436                 char buf[MSG_SIZ];
1437                 if(strchr(appData.results, '*') == NULL) {
1438                     FILE *f;
1439                     appData.tourneyCycles++;
1440                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1441                         fclose(f);
1442                         NextTourneyGame(-1, &dummy);
1443                         ReserveGame(-1, 0);
1444                         if(nextGame <= appData.matchGames) {
1445                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1446                             matchMode = mode;
1447                             ScheduleDelayedEvent(NextMatchGame, 10000);
1448                             return;
1449                         }
1450                     }
1451                 }
1452                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1453                 DisplayError(buf, 0);
1454                 appData.tourneyFile[0] = 0;
1455                 return;
1456             }
1457         } else
1458         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1459             DisplayFatalError(_("Can't have a match with no chess programs"),
1460                               0, 2);
1461             return;
1462         }
1463         matchMode = mode;
1464         matchGame = roundNr = 1;
1465         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1466         NextMatchGame();
1467 }
1468
1469 void
1470 InitBackEnd3 P((void))
1471 {
1472     GameMode initialMode;
1473     char buf[MSG_SIZ];
1474     int err, len;
1475
1476     InitChessProgram(&first, startedFromSetupPosition);
1477
1478     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1479         free(programVersion);
1480         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1481         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1482     }
1483
1484     if (appData.icsActive) {
1485 #ifdef WIN32
1486         /* [DM] Make a console window if needed [HGM] merged ifs */
1487         ConsoleCreate();
1488 #endif
1489         err = establish();
1490         if (err != 0)
1491           {
1492             if (*appData.icsCommPort != NULLCHAR)
1493               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1494                              appData.icsCommPort);
1495             else
1496               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1497                         appData.icsHost, appData.icsPort);
1498
1499             if( (len > MSG_SIZ) && appData.debugMode )
1500               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1501
1502             DisplayFatalError(buf, err, 1);
1503             return;
1504         }
1505         SetICSMode();
1506         telnetISR =
1507           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1508         fromUserISR =
1509           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1510         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1511             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1512     } else if (appData.noChessProgram) {
1513         SetNCPMode();
1514     } else {
1515         SetGNUMode();
1516     }
1517
1518     if (*appData.cmailGameName != NULLCHAR) {
1519         SetCmailMode();
1520         OpenLoopback(&cmailPR);
1521         cmailISR =
1522           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1523     }
1524
1525     ThawUI();
1526     DisplayMessage("", "");
1527     if (StrCaseCmp(appData.initialMode, "") == 0) {
1528       initialMode = BeginningOfGame;
1529       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1530         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1531         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1532         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1533         ModeHighlight();
1534       }
1535     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1536       initialMode = TwoMachinesPlay;
1537     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1538       initialMode = AnalyzeFile;
1539     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1540       initialMode = AnalyzeMode;
1541     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1542       initialMode = MachinePlaysWhite;
1543     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1544       initialMode = MachinePlaysBlack;
1545     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1546       initialMode = EditGame;
1547     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1548       initialMode = EditPosition;
1549     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1550       initialMode = Training;
1551     } else {
1552       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1553       if( (len > MSG_SIZ) && appData.debugMode )
1554         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1555
1556       DisplayFatalError(buf, 0, 2);
1557       return;
1558     }
1559
1560     if (appData.matchMode) {
1561         if(appData.tourneyFile[0]) { // start tourney from command line
1562             FILE *f;
1563             if(f = fopen(appData.tourneyFile, "r")) {
1564                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1565                 fclose(f);
1566                 appData.clockMode = TRUE;
1567                 SetGNUMode();
1568             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1569         }
1570         MatchEvent(TRUE);
1571     } else if (*appData.cmailGameName != NULLCHAR) {
1572         /* Set up cmail mode */
1573         ReloadCmailMsgEvent(TRUE);
1574     } else {
1575         /* Set up other modes */
1576         if (initialMode == AnalyzeFile) {
1577           if (*appData.loadGameFile == NULLCHAR) {
1578             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1579             return;
1580           }
1581         }
1582         if (*appData.loadGameFile != NULLCHAR) {
1583             (void) LoadGameFromFile(appData.loadGameFile,
1584                                     appData.loadGameIndex,
1585                                     appData.loadGameFile, TRUE);
1586         } else if (*appData.loadPositionFile != NULLCHAR) {
1587             (void) LoadPositionFromFile(appData.loadPositionFile,
1588                                         appData.loadPositionIndex,
1589                                         appData.loadPositionFile);
1590             /* [HGM] try to make self-starting even after FEN load */
1591             /* to allow automatic setup of fairy variants with wtm */
1592             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1593                 gameMode = BeginningOfGame;
1594                 setboardSpoiledMachineBlack = 1;
1595             }
1596             /* [HGM] loadPos: make that every new game uses the setup */
1597             /* from file as long as we do not switch variant          */
1598             if(!blackPlaysFirst) {
1599                 startedFromPositionFile = TRUE;
1600                 CopyBoard(filePosition, boards[0]);
1601             }
1602         }
1603         if (initialMode == AnalyzeMode) {
1604           if (appData.noChessProgram) {
1605             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1606             return;
1607           }
1608           if (appData.icsActive) {
1609             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1610             return;
1611           }
1612           AnalyzeModeEvent();
1613         } else if (initialMode == AnalyzeFile) {
1614           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1615           ShowThinkingEvent();
1616           AnalyzeFileEvent();
1617           AnalysisPeriodicEvent(1);
1618         } else if (initialMode == MachinePlaysWhite) {
1619           if (appData.noChessProgram) {
1620             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1621                               0, 2);
1622             return;
1623           }
1624           if (appData.icsActive) {
1625             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1626                               0, 2);
1627             return;
1628           }
1629           MachineWhiteEvent();
1630         } else if (initialMode == MachinePlaysBlack) {
1631           if (appData.noChessProgram) {
1632             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1633                               0, 2);
1634             return;
1635           }
1636           if (appData.icsActive) {
1637             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1638                               0, 2);
1639             return;
1640           }
1641           MachineBlackEvent();
1642         } else if (initialMode == TwoMachinesPlay) {
1643           if (appData.noChessProgram) {
1644             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1645                               0, 2);
1646             return;
1647           }
1648           if (appData.icsActive) {
1649             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1650                               0, 2);
1651             return;
1652           }
1653           TwoMachinesEvent();
1654         } else if (initialMode == EditGame) {
1655           EditGameEvent();
1656         } else if (initialMode == EditPosition) {
1657           EditPositionEvent();
1658         } else if (initialMode == Training) {
1659           if (*appData.loadGameFile == NULLCHAR) {
1660             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1661             return;
1662           }
1663           TrainingEvent();
1664         }
1665     }
1666 }
1667
1668 /*
1669  * Establish will establish a contact to a remote host.port.
1670  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1671  *  used to talk to the host.
1672  * Returns 0 if okay, error code if not.
1673  */
1674 int
1675 establish()
1676 {
1677     char buf[MSG_SIZ];
1678
1679     if (*appData.icsCommPort != NULLCHAR) {
1680         /* Talk to the host through a serial comm port */
1681         return OpenCommPort(appData.icsCommPort, &icsPR);
1682
1683     } else if (*appData.gateway != NULLCHAR) {
1684         if (*appData.remoteShell == NULLCHAR) {
1685             /* Use the rcmd protocol to run telnet program on a gateway host */
1686             snprintf(buf, sizeof(buf), "%s %s %s",
1687                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1688             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1689
1690         } else {
1691             /* Use the rsh program to run telnet program on a gateway host */
1692             if (*appData.remoteUser == NULLCHAR) {
1693                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1694                         appData.gateway, appData.telnetProgram,
1695                         appData.icsHost, appData.icsPort);
1696             } else {
1697                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1698                         appData.remoteShell, appData.gateway,
1699                         appData.remoteUser, appData.telnetProgram,
1700                         appData.icsHost, appData.icsPort);
1701             }
1702             return StartChildProcess(buf, "", &icsPR);
1703
1704         }
1705     } else if (appData.useTelnet) {
1706         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1707
1708     } else {
1709         /* TCP socket interface differs somewhat between
1710            Unix and NT; handle details in the front end.
1711            */
1712         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1713     }
1714 }
1715
1716 void EscapeExpand(char *p, char *q)
1717 {       // [HGM] initstring: routine to shape up string arguments
1718         while(*p++ = *q++) if(p[-1] == '\\')
1719             switch(*q++) {
1720                 case 'n': p[-1] = '\n'; break;
1721                 case 'r': p[-1] = '\r'; break;
1722                 case 't': p[-1] = '\t'; break;
1723                 case '\\': p[-1] = '\\'; break;
1724                 case 0: *p = 0; return;
1725                 default: p[-1] = q[-1]; break;
1726             }
1727 }
1728
1729 void
1730 show_bytes(fp, buf, count)
1731      FILE *fp;
1732      char *buf;
1733      int count;
1734 {
1735     while (count--) {
1736         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1737             fprintf(fp, "\\%03o", *buf & 0xff);
1738         } else {
1739             putc(*buf, fp);
1740         }
1741         buf++;
1742     }
1743     fflush(fp);
1744 }
1745
1746 /* Returns an errno value */
1747 int
1748 OutputMaybeTelnet(pr, message, count, outError)
1749      ProcRef pr;
1750      char *message;
1751      int count;
1752      int *outError;
1753 {
1754     char buf[8192], *p, *q, *buflim;
1755     int left, newcount, outcount;
1756
1757     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1758         *appData.gateway != NULLCHAR) {
1759         if (appData.debugMode) {
1760             fprintf(debugFP, ">ICS: ");
1761             show_bytes(debugFP, message, count);
1762             fprintf(debugFP, "\n");
1763         }
1764         return OutputToProcess(pr, message, count, outError);
1765     }
1766
1767     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1768     p = message;
1769     q = buf;
1770     left = count;
1771     newcount = 0;
1772     while (left) {
1773         if (q >= buflim) {
1774             if (appData.debugMode) {
1775                 fprintf(debugFP, ">ICS: ");
1776                 show_bytes(debugFP, buf, newcount);
1777                 fprintf(debugFP, "\n");
1778             }
1779             outcount = OutputToProcess(pr, buf, newcount, outError);
1780             if (outcount < newcount) return -1; /* to be sure */
1781             q = buf;
1782             newcount = 0;
1783         }
1784         if (*p == '\n') {
1785             *q++ = '\r';
1786             newcount++;
1787         } else if (((unsigned char) *p) == TN_IAC) {
1788             *q++ = (char) TN_IAC;
1789             newcount ++;
1790         }
1791         *q++ = *p++;
1792         newcount++;
1793         left--;
1794     }
1795     if (appData.debugMode) {
1796         fprintf(debugFP, ">ICS: ");
1797         show_bytes(debugFP, buf, newcount);
1798         fprintf(debugFP, "\n");
1799     }
1800     outcount = OutputToProcess(pr, buf, newcount, outError);
1801     if (outcount < newcount) return -1; /* to be sure */
1802     return count;
1803 }
1804
1805 void
1806 read_from_player(isr, closure, message, count, error)
1807      InputSourceRef isr;
1808      VOIDSTAR closure;
1809      char *message;
1810      int count;
1811      int error;
1812 {
1813     int outError, outCount;
1814     static int gotEof = 0;
1815
1816     /* Pass data read from player on to ICS */
1817     if (count > 0) {
1818         gotEof = 0;
1819         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1820         if (outCount < count) {
1821             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1822         }
1823     } else if (count < 0) {
1824         RemoveInputSource(isr);
1825         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1826     } else if (gotEof++ > 0) {
1827         RemoveInputSource(isr);
1828         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1829     }
1830 }
1831
1832 void
1833 KeepAlive()
1834 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1835     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1836     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1837     SendToICS("date\n");
1838     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1839 }
1840
1841 /* added routine for printf style output to ics */
1842 void ics_printf(char *format, ...)
1843 {
1844     char buffer[MSG_SIZ];
1845     va_list args;
1846
1847     va_start(args, format);
1848     vsnprintf(buffer, sizeof(buffer), format, args);
1849     buffer[sizeof(buffer)-1] = '\0';
1850     SendToICS(buffer);
1851     va_end(args);
1852 }
1853
1854 void
1855 SendToICS(s)
1856      char *s;
1857 {
1858     int count, outCount, outError;
1859
1860     if (icsPR == NULL) return;
1861
1862     count = strlen(s);
1863     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1864     if (outCount < count) {
1865         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1866     }
1867 }
1868
1869 /* This is used for sending logon scripts to the ICS. Sending
1870    without a delay causes problems when using timestamp on ICC
1871    (at least on my machine). */
1872 void
1873 SendToICSDelayed(s,msdelay)
1874      char *s;
1875      long msdelay;
1876 {
1877     int count, outCount, outError;
1878
1879     if (icsPR == NULL) return;
1880
1881     count = strlen(s);
1882     if (appData.debugMode) {
1883         fprintf(debugFP, ">ICS: ");
1884         show_bytes(debugFP, s, count);
1885         fprintf(debugFP, "\n");
1886     }
1887     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1888                                       msdelay);
1889     if (outCount < count) {
1890         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1891     }
1892 }
1893
1894
1895 /* Remove all highlighting escape sequences in s
1896    Also deletes any suffix starting with '('
1897    */
1898 char *
1899 StripHighlightAndTitle(s)
1900      char *s;
1901 {
1902     static char retbuf[MSG_SIZ];
1903     char *p = retbuf;
1904
1905     while (*s != NULLCHAR) {
1906         while (*s == '\033') {
1907             while (*s != NULLCHAR && !isalpha(*s)) s++;
1908             if (*s != NULLCHAR) s++;
1909         }
1910         while (*s != NULLCHAR && *s != '\033') {
1911             if (*s == '(' || *s == '[') {
1912                 *p = NULLCHAR;
1913                 return retbuf;
1914             }
1915             *p++ = *s++;
1916         }
1917     }
1918     *p = NULLCHAR;
1919     return retbuf;
1920 }
1921
1922 /* Remove all highlighting escape sequences in s */
1923 char *
1924 StripHighlight(s)
1925      char *s;
1926 {
1927     static char retbuf[MSG_SIZ];
1928     char *p = retbuf;
1929
1930     while (*s != NULLCHAR) {
1931         while (*s == '\033') {
1932             while (*s != NULLCHAR && !isalpha(*s)) s++;
1933             if (*s != NULLCHAR) s++;
1934         }
1935         while (*s != NULLCHAR && *s != '\033') {
1936             *p++ = *s++;
1937         }
1938     }
1939     *p = NULLCHAR;
1940     return retbuf;
1941 }
1942
1943 char *variantNames[] = VARIANT_NAMES;
1944 char *
1945 VariantName(v)
1946      VariantClass v;
1947 {
1948     return variantNames[v];
1949 }
1950
1951
1952 /* Identify a variant from the strings the chess servers use or the
1953    PGN Variant tag names we use. */
1954 VariantClass
1955 StringToVariant(e)
1956      char *e;
1957 {
1958     char *p;
1959     int wnum = -1;
1960     VariantClass v = VariantNormal;
1961     int i, found = FALSE;
1962     char buf[MSG_SIZ];
1963     int len;
1964
1965     if (!e) return v;
1966
1967     /* [HGM] skip over optional board-size prefixes */
1968     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1969         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1970         while( *e++ != '_');
1971     }
1972
1973     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1974         v = VariantNormal;
1975         found = TRUE;
1976     } else
1977     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1978       if (StrCaseStr(e, variantNames[i])) {
1979         v = (VariantClass) i;
1980         found = TRUE;
1981         break;
1982       }
1983     }
1984
1985     if (!found) {
1986       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1987           || StrCaseStr(e, "wild/fr")
1988           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1989         v = VariantFischeRandom;
1990       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1991                  (i = 1, p = StrCaseStr(e, "w"))) {
1992         p += i;
1993         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1994         if (isdigit(*p)) {
1995           wnum = atoi(p);
1996         } else {
1997           wnum = -1;
1998         }
1999         switch (wnum) {
2000         case 0: /* FICS only, actually */
2001         case 1:
2002           /* Castling legal even if K starts on d-file */
2003           v = VariantWildCastle;
2004           break;
2005         case 2:
2006         case 3:
2007         case 4:
2008           /* Castling illegal even if K & R happen to start in
2009              normal positions. */
2010           v = VariantNoCastle;
2011           break;
2012         case 5:
2013         case 7:
2014         case 8:
2015         case 10:
2016         case 11:
2017         case 12:
2018         case 13:
2019         case 14:
2020         case 15:
2021         case 18:
2022         case 19:
2023           /* Castling legal iff K & R start in normal positions */
2024           v = VariantNormal;
2025           break;
2026         case 6:
2027         case 20:
2028         case 21:
2029           /* Special wilds for position setup; unclear what to do here */
2030           v = VariantLoadable;
2031           break;
2032         case 9:
2033           /* Bizarre ICC game */
2034           v = VariantTwoKings;
2035           break;
2036         case 16:
2037           v = VariantKriegspiel;
2038           break;
2039         case 17:
2040           v = VariantLosers;
2041           break;
2042         case 22:
2043           v = VariantFischeRandom;
2044           break;
2045         case 23:
2046           v = VariantCrazyhouse;
2047           break;
2048         case 24:
2049           v = VariantBughouse;
2050           break;
2051         case 25:
2052           v = Variant3Check;
2053           break;
2054         case 26:
2055           /* Not quite the same as FICS suicide! */
2056           v = VariantGiveaway;
2057           break;
2058         case 27:
2059           v = VariantAtomic;
2060           break;
2061         case 28:
2062           v = VariantShatranj;
2063           break;
2064
2065         /* Temporary names for future ICC types.  The name *will* change in
2066            the next xboard/WinBoard release after ICC defines it. */
2067         case 29:
2068           v = Variant29;
2069           break;
2070         case 30:
2071           v = Variant30;
2072           break;
2073         case 31:
2074           v = Variant31;
2075           break;
2076         case 32:
2077           v = Variant32;
2078           break;
2079         case 33:
2080           v = Variant33;
2081           break;
2082         case 34:
2083           v = Variant34;
2084           break;
2085         case 35:
2086           v = Variant35;
2087           break;
2088         case 36:
2089           v = Variant36;
2090           break;
2091         case 37:
2092           v = VariantShogi;
2093           break;
2094         case 38:
2095           v = VariantXiangqi;
2096           break;
2097         case 39:
2098           v = VariantCourier;
2099           break;
2100         case 40:
2101           v = VariantGothic;
2102           break;
2103         case 41:
2104           v = VariantCapablanca;
2105           break;
2106         case 42:
2107           v = VariantKnightmate;
2108           break;
2109         case 43:
2110           v = VariantFairy;
2111           break;
2112         case 44:
2113           v = VariantCylinder;
2114           break;
2115         case 45:
2116           v = VariantFalcon;
2117           break;
2118         case 46:
2119           v = VariantCapaRandom;
2120           break;
2121         case 47:
2122           v = VariantBerolina;
2123           break;
2124         case 48:
2125           v = VariantJanus;
2126           break;
2127         case 49:
2128           v = VariantSuper;
2129           break;
2130         case 50:
2131           v = VariantGreat;
2132           break;
2133         case -1:
2134           /* Found "wild" or "w" in the string but no number;
2135              must assume it's normal chess. */
2136           v = VariantNormal;
2137           break;
2138         default:
2139           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2140           if( (len > MSG_SIZ) && appData.debugMode )
2141             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2142
2143           DisplayError(buf, 0);
2144           v = VariantUnknown;
2145           break;
2146         }
2147       }
2148     }
2149     if (appData.debugMode) {
2150       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2151               e, wnum, VariantName(v));
2152     }
2153     return v;
2154 }
2155
2156 static int leftover_start = 0, leftover_len = 0;
2157 char star_match[STAR_MATCH_N][MSG_SIZ];
2158
2159 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2160    advance *index beyond it, and set leftover_start to the new value of
2161    *index; else return FALSE.  If pattern contains the character '*', it
2162    matches any sequence of characters not containing '\r', '\n', or the
2163    character following the '*' (if any), and the matched sequence(s) are
2164    copied into star_match.
2165    */
2166 int
2167 looking_at(buf, index, pattern)
2168      char *buf;
2169      int *index;
2170      char *pattern;
2171 {
2172     char *bufp = &buf[*index], *patternp = pattern;
2173     int star_count = 0;
2174     char *matchp = star_match[0];
2175
2176     for (;;) {
2177         if (*patternp == NULLCHAR) {
2178             *index = leftover_start = bufp - buf;
2179             *matchp = NULLCHAR;
2180             return TRUE;
2181         }
2182         if (*bufp == NULLCHAR) return FALSE;
2183         if (*patternp == '*') {
2184             if (*bufp == *(patternp + 1)) {
2185                 *matchp = NULLCHAR;
2186                 matchp = star_match[++star_count];
2187                 patternp += 2;
2188                 bufp++;
2189                 continue;
2190             } else if (*bufp == '\n' || *bufp == '\r') {
2191                 patternp++;
2192                 if (*patternp == NULLCHAR)
2193                   continue;
2194                 else
2195                   return FALSE;
2196             } else {
2197                 *matchp++ = *bufp++;
2198                 continue;
2199             }
2200         }
2201         if (*patternp != *bufp) return FALSE;
2202         patternp++;
2203         bufp++;
2204     }
2205 }
2206
2207 void
2208 SendToPlayer(data, length)
2209      char *data;
2210      int length;
2211 {
2212     int error, outCount;
2213     outCount = OutputToProcess(NoProc, data, length, &error);
2214     if (outCount < length) {
2215         DisplayFatalError(_("Error writing to display"), error, 1);
2216     }
2217 }
2218
2219 void
2220 PackHolding(packed, holding)
2221      char packed[];
2222      char *holding;
2223 {
2224     char *p = holding;
2225     char *q = packed;
2226     int runlength = 0;
2227     int curr = 9999;
2228     do {
2229         if (*p == curr) {
2230             runlength++;
2231         } else {
2232             switch (runlength) {
2233               case 0:
2234                 break;
2235               case 1:
2236                 *q++ = curr;
2237                 break;
2238               case 2:
2239                 *q++ = curr;
2240                 *q++ = curr;
2241                 break;
2242               default:
2243                 sprintf(q, "%d", runlength);
2244                 while (*q) q++;
2245                 *q++ = curr;
2246                 break;
2247             }
2248             runlength = 1;
2249             curr = *p;
2250         }
2251     } while (*p++);
2252     *q = NULLCHAR;
2253 }
2254
2255 /* Telnet protocol requests from the front end */
2256 void
2257 TelnetRequest(ddww, option)
2258      unsigned char ddww, option;
2259 {
2260     unsigned char msg[3];
2261     int outCount, outError;
2262
2263     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2264
2265     if (appData.debugMode) {
2266         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2267         switch (ddww) {
2268           case TN_DO:
2269             ddwwStr = "DO";
2270             break;
2271           case TN_DONT:
2272             ddwwStr = "DONT";
2273             break;
2274           case TN_WILL:
2275             ddwwStr = "WILL";
2276             break;
2277           case TN_WONT:
2278             ddwwStr = "WONT";
2279             break;
2280           default:
2281             ddwwStr = buf1;
2282             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2283             break;
2284         }
2285         switch (option) {
2286           case TN_ECHO:
2287             optionStr = "ECHO";
2288             break;
2289           default:
2290             optionStr = buf2;
2291             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2292             break;
2293         }
2294         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2295     }
2296     msg[0] = TN_IAC;
2297     msg[1] = ddww;
2298     msg[2] = option;
2299     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2300     if (outCount < 3) {
2301         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2302     }
2303 }
2304
2305 void
2306 DoEcho()
2307 {
2308     if (!appData.icsActive) return;
2309     TelnetRequest(TN_DO, TN_ECHO);
2310 }
2311
2312 void
2313 DontEcho()
2314 {
2315     if (!appData.icsActive) return;
2316     TelnetRequest(TN_DONT, TN_ECHO);
2317 }
2318
2319 void
2320 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2321 {
2322     /* put the holdings sent to us by the server on the board holdings area */
2323     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2324     char p;
2325     ChessSquare piece;
2326
2327     if(gameInfo.holdingsWidth < 2)  return;
2328     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2329         return; // prevent overwriting by pre-board holdings
2330
2331     if( (int)lowestPiece >= BlackPawn ) {
2332         holdingsColumn = 0;
2333         countsColumn = 1;
2334         holdingsStartRow = BOARD_HEIGHT-1;
2335         direction = -1;
2336     } else {
2337         holdingsColumn = BOARD_WIDTH-1;
2338         countsColumn = BOARD_WIDTH-2;
2339         holdingsStartRow = 0;
2340         direction = 1;
2341     }
2342
2343     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2344         board[i][holdingsColumn] = EmptySquare;
2345         board[i][countsColumn]   = (ChessSquare) 0;
2346     }
2347     while( (p=*holdings++) != NULLCHAR ) {
2348         piece = CharToPiece( ToUpper(p) );
2349         if(piece == EmptySquare) continue;
2350         /*j = (int) piece - (int) WhitePawn;*/
2351         j = PieceToNumber(piece);
2352         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2353         if(j < 0) continue;               /* should not happen */
2354         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2355         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2356         board[holdingsStartRow+j*direction][countsColumn]++;
2357     }
2358 }
2359
2360
2361 void
2362 VariantSwitch(Board board, VariantClass newVariant)
2363 {
2364    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2365    static Board oldBoard;
2366
2367    startedFromPositionFile = FALSE;
2368    if(gameInfo.variant == newVariant) return;
2369
2370    /* [HGM] This routine is called each time an assignment is made to
2371     * gameInfo.variant during a game, to make sure the board sizes
2372     * are set to match the new variant. If that means adding or deleting
2373     * holdings, we shift the playing board accordingly
2374     * This kludge is needed because in ICS observe mode, we get boards
2375     * of an ongoing game without knowing the variant, and learn about the
2376     * latter only later. This can be because of the move list we requested,
2377     * in which case the game history is refilled from the beginning anyway,
2378     * but also when receiving holdings of a crazyhouse game. In the latter
2379     * case we want to add those holdings to the already received position.
2380     */
2381
2382
2383    if (appData.debugMode) {
2384      fprintf(debugFP, "Switch board from %s to %s\n",
2385              VariantName(gameInfo.variant), VariantName(newVariant));
2386      setbuf(debugFP, NULL);
2387    }
2388    shuffleOpenings = 0;       /* [HGM] shuffle */
2389    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2390    switch(newVariant)
2391      {
2392      case VariantShogi:
2393        newWidth = 9;  newHeight = 9;
2394        gameInfo.holdingsSize = 7;
2395      case VariantBughouse:
2396      case VariantCrazyhouse:
2397        newHoldingsWidth = 2; break;
2398      case VariantGreat:
2399        newWidth = 10;
2400      case VariantSuper:
2401        newHoldingsWidth = 2;
2402        gameInfo.holdingsSize = 8;
2403        break;
2404      case VariantGothic:
2405      case VariantCapablanca:
2406      case VariantCapaRandom:
2407        newWidth = 10;
2408      default:
2409        newHoldingsWidth = gameInfo.holdingsSize = 0;
2410      };
2411
2412    if(newWidth  != gameInfo.boardWidth  ||
2413       newHeight != gameInfo.boardHeight ||
2414       newHoldingsWidth != gameInfo.holdingsWidth ) {
2415
2416      /* shift position to new playing area, if needed */
2417      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2418        for(i=0; i<BOARD_HEIGHT; i++)
2419          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2420            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2421              board[i][j];
2422        for(i=0; i<newHeight; i++) {
2423          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2424          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2425        }
2426      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2427        for(i=0; i<BOARD_HEIGHT; i++)
2428          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2429            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2430              board[i][j];
2431      }
2432      gameInfo.boardWidth  = newWidth;
2433      gameInfo.boardHeight = newHeight;
2434      gameInfo.holdingsWidth = newHoldingsWidth;
2435      gameInfo.variant = newVariant;
2436      InitDrawingSizes(-2, 0);
2437    } else gameInfo.variant = newVariant;
2438    CopyBoard(oldBoard, board);   // remember correctly formatted board
2439      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2440    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2441 }
2442
2443 static int loggedOn = FALSE;
2444
2445 /*-- Game start info cache: --*/
2446 int gs_gamenum;
2447 char gs_kind[MSG_SIZ];
2448 static char player1Name[128] = "";
2449 static char player2Name[128] = "";
2450 static char cont_seq[] = "\n\\   ";
2451 static int player1Rating = -1;
2452 static int player2Rating = -1;
2453 /*----------------------------*/
2454
2455 ColorClass curColor = ColorNormal;
2456 int suppressKibitz = 0;
2457
2458 // [HGM] seekgraph
2459 Boolean soughtPending = FALSE;
2460 Boolean seekGraphUp;
2461 #define MAX_SEEK_ADS 200
2462 #define SQUARE 0x80
2463 char *seekAdList[MAX_SEEK_ADS];
2464 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2465 float tcList[MAX_SEEK_ADS];
2466 char colorList[MAX_SEEK_ADS];
2467 int nrOfSeekAds = 0;
2468 int minRating = 1010, maxRating = 2800;
2469 int hMargin = 10, vMargin = 20, h, w;
2470 extern int squareSize, lineGap;
2471
2472 void
2473 PlotSeekAd(int i)
2474 {
2475         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2476         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2477         if(r < minRating+100 && r >=0 ) r = minRating+100;
2478         if(r > maxRating) r = maxRating;
2479         if(tc < 1.) tc = 1.;
2480         if(tc > 95.) tc = 95.;
2481         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2482         y = ((double)r - minRating)/(maxRating - minRating)
2483             * (h-vMargin-squareSize/8-1) + vMargin;
2484         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2485         if(strstr(seekAdList[i], " u ")) color = 1;
2486         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2487            !strstr(seekAdList[i], "bullet") &&
2488            !strstr(seekAdList[i], "blitz") &&
2489            !strstr(seekAdList[i], "standard") ) color = 2;
2490         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2491         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2492 }
2493
2494 void
2495 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2496 {
2497         char buf[MSG_SIZ], *ext = "";
2498         VariantClass v = StringToVariant(type);
2499         if(strstr(type, "wild")) {
2500             ext = type + 4; // append wild number
2501             if(v == VariantFischeRandom) type = "chess960"; else
2502             if(v == VariantLoadable) type = "setup"; else
2503             type = VariantName(v);
2504         }
2505         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2506         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2507             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2508             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2509             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2510             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2511             seekNrList[nrOfSeekAds] = nr;
2512             zList[nrOfSeekAds] = 0;
2513             seekAdList[nrOfSeekAds++] = StrSave(buf);
2514             if(plot) PlotSeekAd(nrOfSeekAds-1);
2515         }
2516 }
2517
2518 void
2519 EraseSeekDot(int i)
2520 {
2521     int x = xList[i], y = yList[i], d=squareSize/4, k;
2522     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2523     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2524     // now replot every dot that overlapped
2525     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2526         int xx = xList[k], yy = yList[k];
2527         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2528             DrawSeekDot(xx, yy, colorList[k]);
2529     }
2530 }
2531
2532 void
2533 RemoveSeekAd(int nr)
2534 {
2535         int i;
2536         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2537             EraseSeekDot(i);
2538             if(seekAdList[i]) free(seekAdList[i]);
2539             seekAdList[i] = seekAdList[--nrOfSeekAds];
2540             seekNrList[i] = seekNrList[nrOfSeekAds];
2541             ratingList[i] = ratingList[nrOfSeekAds];
2542             colorList[i]  = colorList[nrOfSeekAds];
2543             tcList[i] = tcList[nrOfSeekAds];
2544             xList[i]  = xList[nrOfSeekAds];
2545             yList[i]  = yList[nrOfSeekAds];
2546             zList[i]  = zList[nrOfSeekAds];
2547             seekAdList[nrOfSeekAds] = NULL;
2548             break;
2549         }
2550 }
2551
2552 Boolean
2553 MatchSoughtLine(char *line)
2554 {
2555     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2556     int nr, base, inc, u=0; char dummy;
2557
2558     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2559        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2560        (u=1) &&
2561        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2562         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2563         // match: compact and save the line
2564         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2565         return TRUE;
2566     }
2567     return FALSE;
2568 }
2569
2570 int
2571 DrawSeekGraph()
2572 {
2573     int i;
2574     if(!seekGraphUp) return FALSE;
2575     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2576     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2577
2578     DrawSeekBackground(0, 0, w, h);
2579     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2580     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2581     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2582         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2583         yy = h-1-yy;
2584         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2585         if(i%500 == 0) {
2586             char buf[MSG_SIZ];
2587             snprintf(buf, MSG_SIZ, "%d", i);
2588             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2589         }
2590     }
2591     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2592     for(i=1; i<100; i+=(i<10?1:5)) {
2593         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2594         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2595         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2596             char buf[MSG_SIZ];
2597             snprintf(buf, MSG_SIZ, "%d", i);
2598             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2599         }
2600     }
2601     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2602     return TRUE;
2603 }
2604
2605 int SeekGraphClick(ClickType click, int x, int y, int moving)
2606 {
2607     static int lastDown = 0, displayed = 0, lastSecond;
2608     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2609         if(click == Release || moving) return FALSE;
2610         nrOfSeekAds = 0;
2611         soughtPending = TRUE;
2612         SendToICS(ics_prefix);
2613         SendToICS("sought\n"); // should this be "sought all"?
2614     } else { // issue challenge based on clicked ad
2615         int dist = 10000; int i, closest = 0, second = 0;
2616         for(i=0; i<nrOfSeekAds; i++) {
2617             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2618             if(d < dist) { dist = d; closest = i; }
2619             second += (d - zList[i] < 120); // count in-range ads
2620             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2621         }
2622         if(dist < 120) {
2623             char buf[MSG_SIZ];
2624             second = (second > 1);
2625             if(displayed != closest || second != lastSecond) {
2626                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2627                 lastSecond = second; displayed = closest;
2628             }
2629             if(click == Press) {
2630                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2631                 lastDown = closest;
2632                 return TRUE;
2633             } // on press 'hit', only show info
2634             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2635             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2636             SendToICS(ics_prefix);
2637             SendToICS(buf);
2638             return TRUE; // let incoming board of started game pop down the graph
2639         } else if(click == Release) { // release 'miss' is ignored
2640             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2641             if(moving == 2) { // right up-click
2642                 nrOfSeekAds = 0; // refresh graph
2643                 soughtPending = TRUE;
2644                 SendToICS(ics_prefix);
2645                 SendToICS("sought\n"); // should this be "sought all"?
2646             }
2647             return TRUE;
2648         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2649         // press miss or release hit 'pop down' seek graph
2650         seekGraphUp = FALSE;
2651         DrawPosition(TRUE, NULL);
2652     }
2653     return TRUE;
2654 }
2655
2656 void
2657 read_from_ics(isr, closure, data, count, error)
2658      InputSourceRef isr;
2659      VOIDSTAR closure;
2660      char *data;
2661      int count;
2662      int error;
2663 {
2664 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2665 #define STARTED_NONE 0
2666 #define STARTED_MOVES 1
2667 #define STARTED_BOARD 2
2668 #define STARTED_OBSERVE 3
2669 #define STARTED_HOLDINGS 4
2670 #define STARTED_CHATTER 5
2671 #define STARTED_COMMENT 6
2672 #define STARTED_MOVES_NOHIDE 7
2673
2674     static int started = STARTED_NONE;
2675     static char parse[20000];
2676     static int parse_pos = 0;
2677     static char buf[BUF_SIZE + 1];
2678     static int firstTime = TRUE, intfSet = FALSE;
2679     static ColorClass prevColor = ColorNormal;
2680     static int savingComment = FALSE;
2681     static int cmatch = 0; // continuation sequence match
2682     char *bp;
2683     char str[MSG_SIZ];
2684     int i, oldi;
2685     int buf_len;
2686     int next_out;
2687     int tkind;
2688     int backup;    /* [DM] For zippy color lines */
2689     char *p;
2690     char talker[MSG_SIZ]; // [HGM] chat
2691     int channel;
2692
2693     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2694
2695     if (appData.debugMode) {
2696       if (!error) {
2697         fprintf(debugFP, "<ICS: ");
2698         show_bytes(debugFP, data, count);
2699         fprintf(debugFP, "\n");
2700       }
2701     }
2702
2703     if (appData.debugMode) { int f = forwardMostMove;
2704         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2705                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2706                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2707     }
2708     if (count > 0) {
2709         /* If last read ended with a partial line that we couldn't parse,
2710            prepend it to the new read and try again. */
2711         if (leftover_len > 0) {
2712             for (i=0; i<leftover_len; i++)
2713               buf[i] = buf[leftover_start + i];
2714         }
2715
2716     /* copy new characters into the buffer */
2717     bp = buf + leftover_len;
2718     buf_len=leftover_len;
2719     for (i=0; i<count; i++)
2720     {
2721         // ignore these
2722         if (data[i] == '\r')
2723             continue;
2724
2725         // join lines split by ICS?
2726         if (!appData.noJoin)
2727         {
2728             /*
2729                 Joining just consists of finding matches against the
2730                 continuation sequence, and discarding that sequence
2731                 if found instead of copying it.  So, until a match
2732                 fails, there's nothing to do since it might be the
2733                 complete sequence, and thus, something we don't want
2734                 copied.
2735             */
2736             if (data[i] == cont_seq[cmatch])
2737             {
2738                 cmatch++;
2739                 if (cmatch == strlen(cont_seq))
2740                 {
2741                     cmatch = 0; // complete match.  just reset the counter
2742
2743                     /*
2744                         it's possible for the ICS to not include the space
2745                         at the end of the last word, making our [correct]
2746                         join operation fuse two separate words.  the server
2747                         does this when the space occurs at the width setting.
2748                     */
2749                     if (!buf_len || buf[buf_len-1] != ' ')
2750                     {
2751                         *bp++ = ' ';
2752                         buf_len++;
2753                     }
2754                 }
2755                 continue;
2756             }
2757             else if (cmatch)
2758             {
2759                 /*
2760                     match failed, so we have to copy what matched before
2761                     falling through and copying this character.  In reality,
2762                     this will only ever be just the newline character, but
2763                     it doesn't hurt to be precise.
2764                 */
2765                 strncpy(bp, cont_seq, cmatch);
2766                 bp += cmatch;
2767                 buf_len += cmatch;
2768                 cmatch = 0;
2769             }
2770         }
2771
2772         // copy this char
2773         *bp++ = data[i];
2774         buf_len++;
2775     }
2776
2777         buf[buf_len] = NULLCHAR;
2778 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2779         next_out = 0;
2780         leftover_start = 0;
2781
2782         i = 0;
2783         while (i < buf_len) {
2784             /* Deal with part of the TELNET option negotiation
2785                protocol.  We refuse to do anything beyond the
2786                defaults, except that we allow the WILL ECHO option,
2787                which ICS uses to turn off password echoing when we are
2788                directly connected to it.  We reject this option
2789                if localLineEditing mode is on (always on in xboard)
2790                and we are talking to port 23, which might be a real
2791                telnet server that will try to keep WILL ECHO on permanently.
2792              */
2793             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2794                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2795                 unsigned char option;
2796                 oldi = i;
2797                 switch ((unsigned char) buf[++i]) {
2798                   case TN_WILL:
2799                     if (appData.debugMode)
2800                       fprintf(debugFP, "\n<WILL ");
2801                     switch (option = (unsigned char) buf[++i]) {
2802                       case TN_ECHO:
2803                         if (appData.debugMode)
2804                           fprintf(debugFP, "ECHO ");
2805                         /* Reply only if this is a change, according
2806                            to the protocol rules. */
2807                         if (remoteEchoOption) break;
2808                         if (appData.localLineEditing &&
2809                             atoi(appData.icsPort) == TN_PORT) {
2810                             TelnetRequest(TN_DONT, TN_ECHO);
2811                         } else {
2812                             EchoOff();
2813                             TelnetRequest(TN_DO, TN_ECHO);
2814                             remoteEchoOption = TRUE;
2815                         }
2816                         break;
2817                       default:
2818                         if (appData.debugMode)
2819                           fprintf(debugFP, "%d ", option);
2820                         /* Whatever this is, we don't want it. */
2821                         TelnetRequest(TN_DONT, option);
2822                         break;
2823                     }
2824                     break;
2825                   case TN_WONT:
2826                     if (appData.debugMode)
2827                       fprintf(debugFP, "\n<WONT ");
2828                     switch (option = (unsigned char) buf[++i]) {
2829                       case TN_ECHO:
2830                         if (appData.debugMode)
2831                           fprintf(debugFP, "ECHO ");
2832                         /* Reply only if this is a change, according
2833                            to the protocol rules. */
2834                         if (!remoteEchoOption) break;
2835                         EchoOn();
2836                         TelnetRequest(TN_DONT, TN_ECHO);
2837                         remoteEchoOption = FALSE;
2838                         break;
2839                       default:
2840                         if (appData.debugMode)
2841                           fprintf(debugFP, "%d ", (unsigned char) option);
2842                         /* Whatever this is, it must already be turned
2843                            off, because we never agree to turn on
2844                            anything non-default, so according to the
2845                            protocol rules, we don't reply. */
2846                         break;
2847                     }
2848                     break;
2849                   case TN_DO:
2850                     if (appData.debugMode)
2851                       fprintf(debugFP, "\n<DO ");
2852                     switch (option = (unsigned char) buf[++i]) {
2853                       default:
2854                         /* Whatever this is, we refuse to do it. */
2855                         if (appData.debugMode)
2856                           fprintf(debugFP, "%d ", option);
2857                         TelnetRequest(TN_WONT, option);
2858                         break;
2859                     }
2860                     break;
2861                   case TN_DONT:
2862                     if (appData.debugMode)
2863                       fprintf(debugFP, "\n<DONT ");
2864                     switch (option = (unsigned char) buf[++i]) {
2865                       default:
2866                         if (appData.debugMode)
2867                           fprintf(debugFP, "%d ", option);
2868                         /* Whatever this is, we are already not doing
2869                            it, because we never agree to do anything
2870                            non-default, so according to the protocol
2871                            rules, we don't reply. */
2872                         break;
2873                     }
2874                     break;
2875                   case TN_IAC:
2876                     if (appData.debugMode)
2877                       fprintf(debugFP, "\n<IAC ");
2878                     /* Doubled IAC; pass it through */
2879                     i--;
2880                     break;
2881                   default:
2882                     if (appData.debugMode)
2883                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2884                     /* Drop all other telnet commands on the floor */
2885                     break;
2886                 }
2887                 if (oldi > next_out)
2888                   SendToPlayer(&buf[next_out], oldi - next_out);
2889                 if (++i > next_out)
2890                   next_out = i;
2891                 continue;
2892             }
2893
2894             /* OK, this at least will *usually* work */
2895             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2896                 loggedOn = TRUE;
2897             }
2898
2899             if (loggedOn && !intfSet) {
2900                 if (ics_type == ICS_ICC) {
2901                   snprintf(str, MSG_SIZ,
2902                           "/set-quietly interface %s\n/set-quietly style 12\n",
2903                           programVersion);
2904                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2905                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2906                 } else if (ics_type == ICS_CHESSNET) {
2907                   snprintf(str, MSG_SIZ, "/style 12\n");
2908                 } else {
2909                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2910                   strcat(str, programVersion);
2911                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2912                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2913                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2914 #ifdef WIN32
2915                   strcat(str, "$iset nohighlight 1\n");
2916 #endif
2917                   strcat(str, "$iset lock 1\n$style 12\n");
2918                 }
2919                 SendToICS(str);
2920                 NotifyFrontendLogin();
2921                 intfSet = TRUE;
2922             }
2923
2924             if (started == STARTED_COMMENT) {
2925                 /* Accumulate characters in comment */
2926                 parse[parse_pos++] = buf[i];
2927                 if (buf[i] == '\n') {
2928                     parse[parse_pos] = NULLCHAR;
2929                     if(chattingPartner>=0) {
2930                         char mess[MSG_SIZ];
2931                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2932                         OutputChatMessage(chattingPartner, mess);
2933                         chattingPartner = -1;
2934                         next_out = i+1; // [HGM] suppress printing in ICS window
2935                     } else
2936                     if(!suppressKibitz) // [HGM] kibitz
2937                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2938                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2939                         int nrDigit = 0, nrAlph = 0, j;
2940                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2941                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2942                         parse[parse_pos] = NULLCHAR;
2943                         // try to be smart: if it does not look like search info, it should go to
2944                         // ICS interaction window after all, not to engine-output window.
2945                         for(j=0; j<parse_pos; j++) { // count letters and digits
2946                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2947                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2948                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2949                         }
2950                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2951                             int depth=0; float score;
2952                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2953                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2954                                 pvInfoList[forwardMostMove-1].depth = depth;
2955                                 pvInfoList[forwardMostMove-1].score = 100*score;
2956                             }
2957                             OutputKibitz(suppressKibitz, parse);
2958                         } else {
2959                             char tmp[MSG_SIZ];
2960                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2961                             SendToPlayer(tmp, strlen(tmp));
2962                         }
2963                         next_out = i+1; // [HGM] suppress printing in ICS window
2964                     }
2965                     started = STARTED_NONE;
2966                 } else {
2967                     /* Don't match patterns against characters in comment */
2968                     i++;
2969                     continue;
2970                 }
2971             }
2972             if (started == STARTED_CHATTER) {
2973                 if (buf[i] != '\n') {
2974                     /* Don't match patterns against characters in chatter */
2975                     i++;
2976                     continue;
2977                 }
2978                 started = STARTED_NONE;
2979                 if(suppressKibitz) next_out = i+1;
2980             }
2981
2982             /* Kludge to deal with rcmd protocol */
2983             if (firstTime && looking_at(buf, &i, "\001*")) {
2984                 DisplayFatalError(&buf[1], 0, 1);
2985                 continue;
2986             } else {
2987                 firstTime = FALSE;
2988             }
2989
2990             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2991                 ics_type = ICS_ICC;
2992                 ics_prefix = "/";
2993                 if (appData.debugMode)
2994                   fprintf(debugFP, "ics_type %d\n", ics_type);
2995                 continue;
2996             }
2997             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2998                 ics_type = ICS_FICS;
2999                 ics_prefix = "$";
3000                 if (appData.debugMode)
3001                   fprintf(debugFP, "ics_type %d\n", ics_type);
3002                 continue;
3003             }
3004             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3005                 ics_type = ICS_CHESSNET;
3006                 ics_prefix = "/";
3007                 if (appData.debugMode)
3008                   fprintf(debugFP, "ics_type %d\n", ics_type);
3009                 continue;
3010             }
3011
3012             if (!loggedOn &&
3013                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3014                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3015                  looking_at(buf, &i, "will be \"*\""))) {
3016               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3017               continue;
3018             }
3019
3020             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3021               char buf[MSG_SIZ];
3022               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3023               DisplayIcsInteractionTitle(buf);
3024               have_set_title = TRUE;
3025             }
3026
3027             /* skip finger notes */
3028             if (started == STARTED_NONE &&
3029                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3030                  (buf[i] == '1' && buf[i+1] == '0')) &&
3031                 buf[i+2] == ':' && buf[i+3] == ' ') {
3032               started = STARTED_CHATTER;
3033               i += 3;
3034               continue;
3035             }
3036
3037             oldi = i;
3038             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3039             if(appData.seekGraph) {
3040                 if(soughtPending && MatchSoughtLine(buf+i)) {
3041                     i = strstr(buf+i, "rated") - buf;
3042                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3043                     next_out = leftover_start = i;
3044                     started = STARTED_CHATTER;
3045                     suppressKibitz = TRUE;
3046                     continue;
3047                 }
3048                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3049                         && looking_at(buf, &i, "* ads displayed")) {
3050                     soughtPending = FALSE;
3051                     seekGraphUp = TRUE;
3052                     DrawSeekGraph();
3053                     continue;
3054                 }
3055                 if(appData.autoRefresh) {
3056                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3057                         int s = (ics_type == ICS_ICC); // ICC format differs
3058                         if(seekGraphUp)
3059                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3060                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3061                         looking_at(buf, &i, "*% "); // eat prompt
3062                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3063                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3064                         next_out = i; // suppress
3065                         continue;
3066                     }
3067                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3068                         char *p = star_match[0];
3069                         while(*p) {
3070                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3071                             while(*p && *p++ != ' '); // next
3072                         }
3073                         looking_at(buf, &i, "*% "); // eat prompt
3074                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3075                         next_out = i;
3076                         continue;
3077                     }
3078                 }
3079             }
3080
3081             /* skip formula vars */
3082             if (started == STARTED_NONE &&
3083                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3084               started = STARTED_CHATTER;
3085               i += 3;
3086               continue;
3087             }
3088
3089             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3090             if (appData.autoKibitz && started == STARTED_NONE &&
3091                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3092                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3093                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3094                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3095                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3096                         suppressKibitz = TRUE;
3097                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3098                         next_out = i;
3099                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3100                                 && (gameMode == IcsPlayingWhite)) ||
3101                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3102                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3103                             started = STARTED_CHATTER; // own kibitz we simply discard
3104                         else {
3105                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3106                             parse_pos = 0; parse[0] = NULLCHAR;
3107                             savingComment = TRUE;
3108                             suppressKibitz = gameMode != IcsObserving ? 2 :
3109                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3110                         }
3111                         continue;
3112                 } else
3113                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3114                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3115                          && atoi(star_match[0])) {
3116                     // suppress the acknowledgements of our own autoKibitz
3117                     char *p;
3118                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3119                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3120                     SendToPlayer(star_match[0], strlen(star_match[0]));
3121                     if(looking_at(buf, &i, "*% ")) // eat prompt
3122                         suppressKibitz = FALSE;
3123                     next_out = i;
3124                     continue;
3125                 }
3126             } // [HGM] kibitz: end of patch
3127
3128             // [HGM] chat: intercept tells by users for which we have an open chat window
3129             channel = -1;
3130             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3131                                            looking_at(buf, &i, "* whispers:") ||
3132                                            looking_at(buf, &i, "* kibitzes:") ||
3133                                            looking_at(buf, &i, "* shouts:") ||
3134                                            looking_at(buf, &i, "* c-shouts:") ||
3135                                            looking_at(buf, &i, "--> * ") ||
3136                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3137                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3138                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3139                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3140                 int p;
3141                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3142                 chattingPartner = -1;
3143
3144                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3145                 for(p=0; p<MAX_CHAT; p++) {
3146                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3147                     talker[0] = '['; strcat(talker, "] ");
3148                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3149                     chattingPartner = p; break;
3150                     }
3151                 } else
3152                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3153                 for(p=0; p<MAX_CHAT; p++) {
3154                     if(!strcmp("kibitzes", chatPartner[p])) {
3155                         talker[0] = '['; strcat(talker, "] ");
3156                         chattingPartner = p; break;
3157                     }
3158                 } else
3159                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3160                 for(p=0; p<MAX_CHAT; p++) {
3161                     if(!strcmp("whispers", chatPartner[p])) {
3162                         talker[0] = '['; strcat(talker, "] ");
3163                         chattingPartner = p; break;
3164                     }
3165                 } else
3166                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3167                   if(buf[i-8] == '-' && buf[i-3] == 't')
3168                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3169                     if(!strcmp("c-shouts", chatPartner[p])) {
3170                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3171                         chattingPartner = p; break;
3172                     }
3173                   }
3174                   if(chattingPartner < 0)
3175                   for(p=0; p<MAX_CHAT; p++) {
3176                     if(!strcmp("shouts", chatPartner[p])) {
3177                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3178                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3179                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3180                         chattingPartner = p; break;
3181                     }
3182                   }
3183                 }
3184                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3185                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3186                     talker[0] = 0; Colorize(ColorTell, FALSE);
3187                     chattingPartner = p; break;
3188                 }
3189                 if(chattingPartner<0) i = oldi; else {
3190                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3191                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3192                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3193                     started = STARTED_COMMENT;
3194                     parse_pos = 0; parse[0] = NULLCHAR;
3195                     savingComment = 3 + chattingPartner; // counts as TRUE
3196                     suppressKibitz = TRUE;
3197                     continue;
3198                 }
3199             } // [HGM] chat: end of patch
3200
3201           backup = i;
3202             if (appData.zippyTalk || appData.zippyPlay) {
3203                 /* [DM] Backup address for color zippy lines */
3204 #if ZIPPY
3205                if (loggedOn == TRUE)
3206                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3207                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3208 #endif
3209             } // [DM] 'else { ' deleted
3210                 if (
3211                     /* Regular tells and says */
3212                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3213                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3214                     looking_at(buf, &i, "* says: ") ||
3215                     /* Don't color "message" or "messages" output */
3216                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3217                     looking_at(buf, &i, "*. * at *:*: ") ||
3218                     looking_at(buf, &i, "--* (*:*): ") ||
3219                     /* Message notifications (same color as tells) */
3220                     looking_at(buf, &i, "* has left a message ") ||
3221                     looking_at(buf, &i, "* just sent you a message:\n") ||
3222                     /* Whispers and kibitzes */
3223                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3224                     looking_at(buf, &i, "* kibitzes: ") ||
3225                     /* Channel tells */
3226                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3227
3228                   if (tkind == 1 && strchr(star_match[0], ':')) {
3229                       /* Avoid "tells you:" spoofs in channels */
3230                      tkind = 3;
3231                   }
3232                   if (star_match[0][0] == NULLCHAR ||
3233                       strchr(star_match[0], ' ') ||
3234                       (tkind == 3 && strchr(star_match[1], ' '))) {
3235                     /* Reject bogus matches */
3236                     i = oldi;
3237                   } else {
3238                     if (appData.colorize) {
3239                       if (oldi > next_out) {
3240                         SendToPlayer(&buf[next_out], oldi - next_out);
3241                         next_out = oldi;
3242                       }
3243                       switch (tkind) {
3244                       case 1:
3245                         Colorize(ColorTell, FALSE);
3246                         curColor = ColorTell;
3247                         break;
3248                       case 2:
3249                         Colorize(ColorKibitz, FALSE);
3250                         curColor = ColorKibitz;
3251                         break;
3252                       case 3:
3253                         p = strrchr(star_match[1], '(');
3254                         if (p == NULL) {
3255                           p = star_match[1];
3256                         } else {
3257                           p++;
3258                         }
3259                         if (atoi(p) == 1) {
3260                           Colorize(ColorChannel1, FALSE);
3261                           curColor = ColorChannel1;
3262                         } else {
3263                           Colorize(ColorChannel, FALSE);
3264                           curColor = ColorChannel;
3265                         }
3266                         break;
3267                       case 5:
3268                         curColor = ColorNormal;
3269                         break;
3270                       }
3271                     }
3272                     if (started == STARTED_NONE && appData.autoComment &&
3273                         (gameMode == IcsObserving ||
3274                          gameMode == IcsPlayingWhite ||
3275                          gameMode == IcsPlayingBlack)) {
3276                       parse_pos = i - oldi;
3277                       memcpy(parse, &buf[oldi], parse_pos);
3278                       parse[parse_pos] = NULLCHAR;
3279                       started = STARTED_COMMENT;
3280                       savingComment = TRUE;
3281                     } else {
3282                       started = STARTED_CHATTER;
3283                       savingComment = FALSE;
3284                     }
3285                     loggedOn = TRUE;
3286                     continue;
3287                   }
3288                 }
3289
3290                 if (looking_at(buf, &i, "* s-shouts: ") ||
3291                     looking_at(buf, &i, "* c-shouts: ")) {
3292                     if (appData.colorize) {
3293                         if (oldi > next_out) {
3294                             SendToPlayer(&buf[next_out], oldi - next_out);
3295                             next_out = oldi;
3296                         }
3297                         Colorize(ColorSShout, FALSE);
3298                         curColor = ColorSShout;
3299                     }
3300                     loggedOn = TRUE;
3301                     started = STARTED_CHATTER;
3302                     continue;
3303                 }
3304
3305                 if (looking_at(buf, &i, "--->")) {
3306                     loggedOn = TRUE;
3307                     continue;
3308                 }
3309
3310                 if (looking_at(buf, &i, "* shouts: ") ||
3311                     looking_at(buf, &i, "--> ")) {
3312                     if (appData.colorize) {
3313                         if (oldi > next_out) {
3314                             SendToPlayer(&buf[next_out], oldi - next_out);
3315                             next_out = oldi;
3316                         }
3317                         Colorize(ColorShout, FALSE);
3318                         curColor = ColorShout;
3319                     }
3320                     loggedOn = TRUE;
3321                     started = STARTED_CHATTER;
3322                     continue;
3323                 }
3324
3325                 if (looking_at( buf, &i, "Challenge:")) {
3326                     if (appData.colorize) {
3327                         if (oldi > next_out) {
3328                             SendToPlayer(&buf[next_out], oldi - next_out);
3329                             next_out = oldi;
3330                         }
3331                         Colorize(ColorChallenge, FALSE);
3332                         curColor = ColorChallenge;
3333                     }
3334                     loggedOn = TRUE;
3335                     continue;
3336                 }
3337
3338                 if (looking_at(buf, &i, "* offers you") ||
3339                     looking_at(buf, &i, "* offers to be") ||
3340                     looking_at(buf, &i, "* would like to") ||
3341                     looking_at(buf, &i, "* requests to") ||
3342                     looking_at(buf, &i, "Your opponent offers") ||
3343                     looking_at(buf, &i, "Your opponent requests")) {
3344
3345                     if (appData.colorize) {
3346                         if (oldi > next_out) {
3347                             SendToPlayer(&buf[next_out], oldi - next_out);
3348                             next_out = oldi;
3349                         }
3350                         Colorize(ColorRequest, FALSE);
3351                         curColor = ColorRequest;
3352                     }
3353                     continue;
3354                 }
3355
3356                 if (looking_at(buf, &i, "* (*) seeking")) {
3357                     if (appData.colorize) {
3358                         if (oldi > next_out) {
3359                             SendToPlayer(&buf[next_out], oldi - next_out);
3360                             next_out = oldi;
3361                         }
3362                         Colorize(ColorSeek, FALSE);
3363                         curColor = ColorSeek;
3364                     }
3365                     continue;
3366             }
3367
3368           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3369
3370             if (looking_at(buf, &i, "\\   ")) {
3371                 if (prevColor != ColorNormal) {
3372                     if (oldi > next_out) {
3373                         SendToPlayer(&buf[next_out], oldi - next_out);
3374                         next_out = oldi;
3375                     }
3376                     Colorize(prevColor, TRUE);
3377                     curColor = prevColor;
3378                 }
3379                 if (savingComment) {
3380                     parse_pos = i - oldi;
3381                     memcpy(parse, &buf[oldi], parse_pos);
3382                     parse[parse_pos] = NULLCHAR;
3383                     started = STARTED_COMMENT;
3384                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3385                         chattingPartner = savingComment - 3; // kludge to remember the box
3386                 } else {
3387                     started = STARTED_CHATTER;
3388                 }
3389                 continue;
3390             }
3391
3392             if (looking_at(buf, &i, "Black Strength :") ||
3393                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3394                 looking_at(buf, &i, "<10>") ||
3395                 looking_at(buf, &i, "#@#")) {
3396                 /* Wrong board style */
3397                 loggedOn = TRUE;
3398                 SendToICS(ics_prefix);
3399                 SendToICS("set style 12\n");
3400                 SendToICS(ics_prefix);
3401                 SendToICS("refresh\n");
3402                 continue;
3403             }
3404
3405             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3406                 ICSInitScript();
3407                 have_sent_ICS_logon = 1;
3408                 continue;
3409             }
3410
3411             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3412                 (looking_at(buf, &i, "\n<12> ") ||
3413                  looking_at(buf, &i, "<12> "))) {
3414                 loggedOn = TRUE;
3415                 if (oldi > next_out) {
3416                     SendToPlayer(&buf[next_out], oldi - next_out);
3417                 }
3418                 next_out = i;
3419                 started = STARTED_BOARD;
3420                 parse_pos = 0;
3421                 continue;
3422             }
3423
3424             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3425                 looking_at(buf, &i, "<b1> ")) {
3426                 if (oldi > next_out) {
3427                     SendToPlayer(&buf[next_out], oldi - next_out);
3428                 }
3429                 next_out = i;
3430                 started = STARTED_HOLDINGS;
3431                 parse_pos = 0;
3432                 continue;
3433             }
3434
3435             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3436                 loggedOn = TRUE;
3437                 /* Header for a move list -- first line */
3438
3439                 switch (ics_getting_history) {
3440                   case H_FALSE:
3441                     switch (gameMode) {
3442                       case IcsIdle:
3443                       case BeginningOfGame:
3444                         /* User typed "moves" or "oldmoves" while we
3445                            were idle.  Pretend we asked for these
3446                            moves and soak them up so user can step
3447                            through them and/or save them.
3448                            */
3449                         Reset(FALSE, TRUE);
3450                         gameMode = IcsObserving;
3451                         ModeHighlight();
3452                         ics_gamenum = -1;
3453                         ics_getting_history = H_GOT_UNREQ_HEADER;
3454                         break;
3455                       case EditGame: /*?*/
3456                       case EditPosition: /*?*/
3457                         /* Should above feature work in these modes too? */
3458                         /* For now it doesn't */
3459                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3460                         break;
3461                       default:
3462                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3463                         break;
3464                     }
3465                     break;
3466                   case H_REQUESTED:
3467                     /* Is this the right one? */
3468                     if (gameInfo.white && gameInfo.black &&
3469                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3470                         strcmp(gameInfo.black, star_match[2]) == 0) {
3471                         /* All is well */
3472                         ics_getting_history = H_GOT_REQ_HEADER;
3473                     }
3474                     break;
3475                   case H_GOT_REQ_HEADER:
3476                   case H_GOT_UNREQ_HEADER:
3477                   case H_GOT_UNWANTED_HEADER:
3478                   case H_GETTING_MOVES:
3479                     /* Should not happen */
3480                     DisplayError(_("Error gathering move list: two headers"), 0);
3481                     ics_getting_history = H_FALSE;
3482                     break;
3483                 }
3484
3485                 /* Save player ratings into gameInfo if needed */
3486                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3487                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3488                     (gameInfo.whiteRating == -1 ||
3489                      gameInfo.blackRating == -1)) {
3490
3491                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3492                     gameInfo.blackRating = string_to_rating(star_match[3]);
3493                     if (appData.debugMode)
3494                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3495                               gameInfo.whiteRating, gameInfo.blackRating);
3496                 }
3497                 continue;
3498             }
3499
3500             if (looking_at(buf, &i,
3501               "* * match, initial time: * minute*, increment: * second")) {
3502                 /* Header for a move list -- second line */
3503                 /* Initial board will follow if this is a wild game */
3504                 if (gameInfo.event != NULL) free(gameInfo.event);
3505                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3506                 gameInfo.event = StrSave(str);
3507                 /* [HGM] we switched variant. Translate boards if needed. */
3508                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3509                 continue;
3510             }
3511
3512             if (looking_at(buf, &i, "Move  ")) {
3513                 /* Beginning of a move list */
3514                 switch (ics_getting_history) {
3515                   case H_FALSE:
3516                     /* Normally should not happen */
3517                     /* Maybe user hit reset while we were parsing */
3518                     break;
3519                   case H_REQUESTED:
3520                     /* Happens if we are ignoring a move list that is not
3521                      * the one we just requested.  Common if the user
3522                      * tries to observe two games without turning off
3523                      * getMoveList */
3524                     break;
3525                   case H_GETTING_MOVES:
3526                     /* Should not happen */
3527                     DisplayError(_("Error gathering move list: nested"), 0);
3528                     ics_getting_history = H_FALSE;
3529                     break;
3530                   case H_GOT_REQ_HEADER:
3531                     ics_getting_history = H_GETTING_MOVES;
3532                     started = STARTED_MOVES;
3533                     parse_pos = 0;
3534                     if (oldi > next_out) {
3535                         SendToPlayer(&buf[next_out], oldi - next_out);
3536                     }
3537                     break;
3538                   case H_GOT_UNREQ_HEADER:
3539                     ics_getting_history = H_GETTING_MOVES;
3540                     started = STARTED_MOVES_NOHIDE;
3541                     parse_pos = 0;
3542                     break;
3543                   case H_GOT_UNWANTED_HEADER:
3544                     ics_getting_history = H_FALSE;
3545                     break;
3546                 }
3547                 continue;
3548             }
3549
3550             if (looking_at(buf, &i, "% ") ||
3551                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3552                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3553                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3554                     soughtPending = FALSE;
3555                     seekGraphUp = TRUE;
3556                     DrawSeekGraph();
3557                 }
3558                 if(suppressKibitz) next_out = i;
3559                 savingComment = FALSE;
3560                 suppressKibitz = 0;
3561                 switch (started) {
3562                   case STARTED_MOVES:
3563                   case STARTED_MOVES_NOHIDE:
3564                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3565                     parse[parse_pos + i - oldi] = NULLCHAR;
3566                     ParseGameHistory(parse);
3567 #if ZIPPY
3568                     if (appData.zippyPlay && first.initDone) {
3569                         FeedMovesToProgram(&first, forwardMostMove);
3570                         if (gameMode == IcsPlayingWhite) {
3571                             if (WhiteOnMove(forwardMostMove)) {
3572                                 if (first.sendTime) {
3573                                   if (first.useColors) {
3574                                     SendToProgram("black\n", &first);
3575                                   }
3576                                   SendTimeRemaining(&first, TRUE);
3577                                 }
3578                                 if (first.useColors) {
3579                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3580                                 }
3581                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3582                                 first.maybeThinking = TRUE;
3583                             } else {
3584                                 if (first.usePlayother) {
3585                                   if (first.sendTime) {
3586                                     SendTimeRemaining(&first, TRUE);
3587                                   }
3588                                   SendToProgram("playother\n", &first);
3589                                   firstMove = FALSE;
3590                                 } else {
3591                                   firstMove = TRUE;
3592                                 }
3593                             }
3594                         } else if (gameMode == IcsPlayingBlack) {
3595                             if (!WhiteOnMove(forwardMostMove)) {
3596                                 if (first.sendTime) {
3597                                   if (first.useColors) {
3598                                     SendToProgram("white\n", &first);
3599                                   }
3600                                   SendTimeRemaining(&first, FALSE);
3601                                 }
3602                                 if (first.useColors) {
3603                                   SendToProgram("black\n", &first);
3604                                 }
3605                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3606                                 first.maybeThinking = TRUE;
3607                             } else {
3608                                 if (first.usePlayother) {
3609                                   if (first.sendTime) {
3610                                     SendTimeRemaining(&first, FALSE);
3611                                   }
3612                                   SendToProgram("playother\n", &first);
3613                                   firstMove = FALSE;
3614                                 } else {
3615                                   firstMove = TRUE;
3616                                 }
3617                             }
3618                         }
3619                     }
3620 #endif
3621                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3622                         /* Moves came from oldmoves or moves command
3623                            while we weren't doing anything else.
3624                            */
3625                         currentMove = forwardMostMove;
3626                         ClearHighlights();/*!!could figure this out*/
3627                         flipView = appData.flipView;
3628                         DrawPosition(TRUE, boards[currentMove]);
3629                         DisplayBothClocks();
3630                         snprintf(str, MSG_SIZ, "%s vs. %s",
3631                                 gameInfo.white, gameInfo.black);
3632                         DisplayTitle(str);
3633                         gameMode = IcsIdle;
3634                     } else {
3635                         /* Moves were history of an active game */
3636                         if (gameInfo.resultDetails != NULL) {
3637                             free(gameInfo.resultDetails);
3638                             gameInfo.resultDetails = NULL;
3639                         }
3640                     }
3641                     HistorySet(parseList, backwardMostMove,
3642                                forwardMostMove, currentMove-1);
3643                     DisplayMove(currentMove - 1);
3644                     if (started == STARTED_MOVES) next_out = i;
3645                     started = STARTED_NONE;
3646                     ics_getting_history = H_FALSE;
3647                     break;
3648
3649                   case STARTED_OBSERVE:
3650                     started = STARTED_NONE;
3651                     SendToICS(ics_prefix);
3652                     SendToICS("refresh\n");
3653                     break;
3654
3655                   default:
3656                     break;
3657                 }
3658                 if(bookHit) { // [HGM] book: simulate book reply
3659                     static char bookMove[MSG_SIZ]; // a bit generous?
3660
3661                     programStats.nodes = programStats.depth = programStats.time =
3662                     programStats.score = programStats.got_only_move = 0;
3663                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3664
3665                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3666                     strcat(bookMove, bookHit);
3667                     HandleMachineMove(bookMove, &first);
3668                 }
3669                 continue;
3670             }
3671
3672             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3673                  started == STARTED_HOLDINGS ||
3674                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3675                 /* Accumulate characters in move list or board */
3676                 parse[parse_pos++] = buf[i];
3677             }
3678
3679             /* Start of game messages.  Mostly we detect start of game
3680                when the first board image arrives.  On some versions
3681                of the ICS, though, we need to do a "refresh" after starting
3682                to observe in order to get the current board right away. */
3683             if (looking_at(buf, &i, "Adding game * to observation list")) {
3684                 started = STARTED_OBSERVE;
3685                 continue;
3686             }
3687
3688             /* Handle auto-observe */
3689             if (appData.autoObserve &&
3690                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3691                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3692                 char *player;
3693                 /* Choose the player that was highlighted, if any. */
3694                 if (star_match[0][0] == '\033' ||
3695                     star_match[1][0] != '\033') {
3696                     player = star_match[0];
3697                 } else {
3698                     player = star_match[2];
3699                 }
3700                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3701                         ics_prefix, StripHighlightAndTitle(player));
3702                 SendToICS(str);
3703
3704                 /* Save ratings from notify string */
3705                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3706                 player1Rating = string_to_rating(star_match[1]);
3707                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3708                 player2Rating = string_to_rating(star_match[3]);
3709
3710                 if (appData.debugMode)
3711                   fprintf(debugFP,
3712                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3713                           player1Name, player1Rating,
3714                           player2Name, player2Rating);
3715
3716                 continue;
3717             }
3718
3719             /* Deal with automatic examine mode after a game,
3720                and with IcsObserving -> IcsExamining transition */
3721             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3722                 looking_at(buf, &i, "has made you an examiner of game *")) {
3723
3724                 int gamenum = atoi(star_match[0]);
3725                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3726                     gamenum == ics_gamenum) {
3727                     /* We were already playing or observing this game;
3728                        no need to refetch history */
3729                     gameMode = IcsExamining;
3730                     if (pausing) {
3731                         pauseExamForwardMostMove = forwardMostMove;
3732                     } else if (currentMove < forwardMostMove) {
3733                         ForwardInner(forwardMostMove);
3734                     }
3735                 } else {
3736                     /* I don't think this case really can happen */
3737                     SendToICS(ics_prefix);
3738                     SendToICS("refresh\n");
3739                 }
3740                 continue;
3741             }
3742
3743             /* Error messages */
3744 //          if (ics_user_moved) {
3745             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3746                 if (looking_at(buf, &i, "Illegal move") ||
3747                     looking_at(buf, &i, "Not a legal move") ||
3748                     looking_at(buf, &i, "Your king is in check") ||
3749                     looking_at(buf, &i, "It isn't your turn") ||
3750                     looking_at(buf, &i, "It is not your move")) {
3751                     /* Illegal move */
3752                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3753                         currentMove = forwardMostMove-1;
3754                         DisplayMove(currentMove - 1); /* before DMError */
3755                         DrawPosition(FALSE, boards[currentMove]);
3756                         SwitchClocks(forwardMostMove-1); // [HGM] race
3757                         DisplayBothClocks();
3758                     }
3759                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3760                     ics_user_moved = 0;
3761                     continue;
3762                 }
3763             }
3764
3765             if (looking_at(buf, &i, "still have time") ||
3766                 looking_at(buf, &i, "not out of time") ||
3767                 looking_at(buf, &i, "either player is out of time") ||
3768                 looking_at(buf, &i, "has timeseal; checking")) {
3769                 /* We must have called his flag a little too soon */
3770                 whiteFlag = blackFlag = FALSE;
3771                 continue;
3772             }
3773
3774             if (looking_at(buf, &i, "added * seconds to") ||
3775                 looking_at(buf, &i, "seconds were added to")) {
3776                 /* Update the clocks */
3777                 SendToICS(ics_prefix);
3778                 SendToICS("refresh\n");
3779                 continue;
3780             }
3781
3782             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3783                 ics_clock_paused = TRUE;
3784                 StopClocks();
3785                 continue;
3786             }
3787
3788             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3789                 ics_clock_paused = FALSE;
3790                 StartClocks();
3791                 continue;
3792             }
3793
3794             /* Grab player ratings from the Creating: message.
3795                Note we have to check for the special case when
3796                the ICS inserts things like [white] or [black]. */
3797             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3798                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3799                 /* star_matches:
3800                    0    player 1 name (not necessarily white)
3801                    1    player 1 rating
3802                    2    empty, white, or black (IGNORED)
3803                    3    player 2 name (not necessarily black)
3804                    4    player 2 rating
3805
3806                    The names/ratings are sorted out when the game
3807                    actually starts (below).
3808                 */
3809                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3810                 player1Rating = string_to_rating(star_match[1]);
3811                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3812                 player2Rating = string_to_rating(star_match[4]);
3813
3814                 if (appData.debugMode)
3815                   fprintf(debugFP,
3816                           "Ratings from 'Creating:' %s %d, %s %d\n",
3817                           player1Name, player1Rating,
3818                           player2Name, player2Rating);
3819
3820                 continue;
3821             }
3822
3823             /* Improved generic start/end-of-game messages */
3824             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3825                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3826                 /* If tkind == 0: */
3827                 /* star_match[0] is the game number */
3828                 /*           [1] is the white player's name */
3829                 /*           [2] is the black player's name */
3830                 /* For end-of-game: */
3831                 /*           [3] is the reason for the game end */
3832                 /*           [4] is a PGN end game-token, preceded by " " */
3833                 /* For start-of-game: */
3834                 /*           [3] begins with "Creating" or "Continuing" */
3835                 /*           [4] is " *" or empty (don't care). */
3836                 int gamenum = atoi(star_match[0]);
3837                 char *whitename, *blackname, *why, *endtoken;
3838                 ChessMove endtype = EndOfFile;
3839
3840                 if (tkind == 0) {
3841                   whitename = star_match[1];
3842                   blackname = star_match[2];
3843                   why = star_match[3];
3844                   endtoken = star_match[4];
3845                 } else {
3846                   whitename = star_match[1];
3847                   blackname = star_match[3];
3848                   why = star_match[5];
3849                   endtoken = star_match[6];
3850                 }
3851
3852                 /* Game start messages */
3853                 if (strncmp(why, "Creating ", 9) == 0 ||
3854                     strncmp(why, "Continuing ", 11) == 0) {
3855                     gs_gamenum = gamenum;
3856                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3857                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3858 #if ZIPPY
3859                     if (appData.zippyPlay) {
3860                         ZippyGameStart(whitename, blackname);
3861                     }
3862 #endif /*ZIPPY*/
3863                     partnerBoardValid = FALSE; // [HGM] bughouse
3864                     continue;
3865                 }
3866
3867                 /* Game end messages */
3868                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3869                     ics_gamenum != gamenum) {
3870                     continue;
3871                 }
3872                 while (endtoken[0] == ' ') endtoken++;
3873                 switch (endtoken[0]) {
3874                   case '*':
3875                   default:
3876                     endtype = GameUnfinished;
3877                     break;
3878                   case '0':
3879                     endtype = BlackWins;
3880                     break;
3881                   case '1':
3882                     if (endtoken[1] == '/')
3883                       endtype = GameIsDrawn;
3884                     else
3885                       endtype = WhiteWins;
3886                     break;
3887                 }
3888                 GameEnds(endtype, why, GE_ICS);
3889 #if ZIPPY
3890                 if (appData.zippyPlay && first.initDone) {
3891                     ZippyGameEnd(endtype, why);
3892                     if (first.pr == NULL) {
3893                       /* Start the next process early so that we'll
3894                          be ready for the next challenge */
3895                       StartChessProgram(&first);
3896                     }
3897                     /* Send "new" early, in case this command takes
3898                        a long time to finish, so that we'll be ready
3899                        for the next challenge. */
3900                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3901                     Reset(TRUE, TRUE);
3902                 }
3903 #endif /*ZIPPY*/
3904                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3905                 continue;
3906             }
3907
3908             if (looking_at(buf, &i, "Removing game * from observation") ||
3909                 looking_at(buf, &i, "no longer observing game *") ||
3910                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3911                 if (gameMode == IcsObserving &&
3912                     atoi(star_match[0]) == ics_gamenum)
3913                   {
3914                       /* icsEngineAnalyze */
3915                       if (appData.icsEngineAnalyze) {
3916                             ExitAnalyzeMode();
3917                             ModeHighlight();
3918                       }
3919                       StopClocks();
3920                       gameMode = IcsIdle;
3921                       ics_gamenum = -1;
3922                       ics_user_moved = FALSE;
3923                   }
3924                 continue;
3925             }
3926
3927             if (looking_at(buf, &i, "no longer examining game *")) {
3928                 if (gameMode == IcsExamining &&
3929                     atoi(star_match[0]) == ics_gamenum)
3930                   {
3931                       gameMode = IcsIdle;
3932                       ics_gamenum = -1;
3933                       ics_user_moved = FALSE;
3934                   }
3935                 continue;
3936             }
3937
3938             /* Advance leftover_start past any newlines we find,
3939                so only partial lines can get reparsed */
3940             if (looking_at(buf, &i, "\n")) {
3941                 prevColor = curColor;
3942                 if (curColor != ColorNormal) {
3943                     if (oldi > next_out) {
3944                         SendToPlayer(&buf[next_out], oldi - next_out);
3945                         next_out = oldi;
3946                     }
3947                     Colorize(ColorNormal, FALSE);
3948                     curColor = ColorNormal;
3949                 }
3950                 if (started == STARTED_BOARD) {
3951                     started = STARTED_NONE;
3952                     parse[parse_pos] = NULLCHAR;
3953                     ParseBoard12(parse);
3954                     ics_user_moved = 0;
3955
3956                     /* Send premove here */
3957                     if (appData.premove) {
3958                       char str[MSG_SIZ];
3959                       if (currentMove == 0 &&
3960                           gameMode == IcsPlayingWhite &&
3961                           appData.premoveWhite) {
3962                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3963                         if (appData.debugMode)
3964                           fprintf(debugFP, "Sending premove:\n");
3965                         SendToICS(str);
3966                       } else if (currentMove == 1 &&
3967                                  gameMode == IcsPlayingBlack &&
3968                                  appData.premoveBlack) {
3969                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3970                         if (appData.debugMode)
3971                           fprintf(debugFP, "Sending premove:\n");
3972                         SendToICS(str);
3973                       } else if (gotPremove) {
3974                         gotPremove = 0;
3975                         ClearPremoveHighlights();
3976                         if (appData.debugMode)
3977                           fprintf(debugFP, "Sending premove:\n");
3978                           UserMoveEvent(premoveFromX, premoveFromY,
3979                                         premoveToX, premoveToY,
3980                                         premovePromoChar);
3981                       }
3982                     }
3983
3984                     /* Usually suppress following prompt */
3985                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3986                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3987                         if (looking_at(buf, &i, "*% ")) {
3988                             savingComment = FALSE;
3989                             suppressKibitz = 0;
3990                         }
3991                     }
3992                     next_out = i;
3993                 } else if (started == STARTED_HOLDINGS) {
3994                     int gamenum;
3995                     char new_piece[MSG_SIZ];
3996                     started = STARTED_NONE;
3997                     parse[parse_pos] = NULLCHAR;
3998                     if (appData.debugMode)
3999                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4000                                                         parse, currentMove);
4001                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4002                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4003                         if (gameInfo.variant == VariantNormal) {
4004                           /* [HGM] We seem to switch variant during a game!
4005                            * Presumably no holdings were displayed, so we have
4006                            * to move the position two files to the right to
4007                            * create room for them!
4008                            */
4009                           VariantClass newVariant;
4010                           switch(gameInfo.boardWidth) { // base guess on board width
4011                                 case 9:  newVariant = VariantShogi; break;
4012                                 case 10: newVariant = VariantGreat; break;
4013                                 default: newVariant = VariantCrazyhouse; break;
4014                           }
4015                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4016                           /* Get a move list just to see the header, which
4017                              will tell us whether this is really bug or zh */
4018                           if (ics_getting_history == H_FALSE) {
4019                             ics_getting_history = H_REQUESTED;
4020                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4021                             SendToICS(str);
4022                           }
4023                         }
4024                         new_piece[0] = NULLCHAR;
4025                         sscanf(parse, "game %d white [%s black [%s <- %s",
4026                                &gamenum, white_holding, black_holding,
4027                                new_piece);
4028                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4029                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4030                         /* [HGM] copy holdings to board holdings area */
4031                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4032                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4033                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4034 #if ZIPPY
4035                         if (appData.zippyPlay && first.initDone) {
4036                             ZippyHoldings(white_holding, black_holding,
4037                                           new_piece);
4038                         }
4039 #endif /*ZIPPY*/
4040                         if (tinyLayout || smallLayout) {
4041                             char wh[16], bh[16];
4042                             PackHolding(wh, white_holding);
4043                             PackHolding(bh, black_holding);
4044                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4045                                     gameInfo.white, gameInfo.black);
4046                         } else {
4047                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4048                                     gameInfo.white, white_holding,
4049                                     gameInfo.black, black_holding);
4050                         }
4051                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4052                         DrawPosition(FALSE, boards[currentMove]);
4053                         DisplayTitle(str);
4054                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4055                         sscanf(parse, "game %d white [%s black [%s <- %s",
4056                                &gamenum, white_holding, black_holding,
4057                                new_piece);
4058                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4059                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4060                         /* [HGM] copy holdings to partner-board holdings area */
4061                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4062                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4063                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4064                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4065                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4066                       }
4067                     }
4068                     /* Suppress following prompt */
4069                     if (looking_at(buf, &i, "*% ")) {
4070                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4071                         savingComment = FALSE;
4072                         suppressKibitz = 0;
4073                     }
4074                     next_out = i;
4075                 }
4076                 continue;
4077             }
4078
4079             i++;                /* skip unparsed character and loop back */
4080         }
4081
4082         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4083 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4084 //          SendToPlayer(&buf[next_out], i - next_out);
4085             started != STARTED_HOLDINGS && leftover_start > next_out) {
4086             SendToPlayer(&buf[next_out], leftover_start - next_out);
4087             next_out = i;
4088         }
4089
4090         leftover_len = buf_len - leftover_start;
4091         /* if buffer ends with something we couldn't parse,
4092            reparse it after appending the next read */
4093
4094     } else if (count == 0) {
4095         RemoveInputSource(isr);
4096         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4097     } else {
4098         DisplayFatalError(_("Error reading from ICS"), error, 1);
4099     }
4100 }
4101
4102
4103 /* Board style 12 looks like this:
4104
4105    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4106
4107  * The "<12> " is stripped before it gets to this routine.  The two
4108  * trailing 0's (flip state and clock ticking) are later addition, and
4109  * some chess servers may not have them, or may have only the first.
4110  * Additional trailing fields may be added in the future.
4111  */
4112
4113 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4114
4115 #define RELATION_OBSERVING_PLAYED    0
4116 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4117 #define RELATION_PLAYING_MYMOVE      1
4118 #define RELATION_PLAYING_NOTMYMOVE  -1
4119 #define RELATION_EXAMINING           2
4120 #define RELATION_ISOLATED_BOARD     -3
4121 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4122
4123 void
4124 ParseBoard12(string)
4125      char *string;
4126 {
4127     GameMode newGameMode;
4128     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4129     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4130     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4131     char to_play, board_chars[200];
4132     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4133     char black[32], white[32];
4134     Board board;
4135     int prevMove = currentMove;
4136     int ticking = 2;
4137     ChessMove moveType;
4138     int fromX, fromY, toX, toY;
4139     char promoChar;
4140     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4141     char *bookHit = NULL; // [HGM] book
4142     Boolean weird = FALSE, reqFlag = FALSE;
4143
4144     fromX = fromY = toX = toY = -1;
4145
4146     newGame = FALSE;
4147
4148     if (appData.debugMode)
4149       fprintf(debugFP, _("Parsing board: %s\n"), string);
4150
4151     move_str[0] = NULLCHAR;
4152     elapsed_time[0] = NULLCHAR;
4153     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4154         int  i = 0, j;
4155         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4156             if(string[i] == ' ') { ranks++; files = 0; }
4157             else files++;
4158             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4159             i++;
4160         }
4161         for(j = 0; j <i; j++) board_chars[j] = string[j];
4162         board_chars[i] = '\0';
4163         string += i + 1;
4164     }
4165     n = sscanf(string, PATTERN, &to_play, &double_push,
4166                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4167                &gamenum, white, black, &relation, &basetime, &increment,
4168                &white_stren, &black_stren, &white_time, &black_time,
4169                &moveNum, str, elapsed_time, move_str, &ics_flip,
4170                &ticking);
4171
4172     if (n < 21) {
4173         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4174         DisplayError(str, 0);
4175         return;
4176     }
4177
4178     /* Convert the move number to internal form */
4179     moveNum = (moveNum - 1) * 2;
4180     if (to_play == 'B') moveNum++;
4181     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4182       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4183                         0, 1);
4184       return;
4185     }
4186
4187     switch (relation) {
4188       case RELATION_OBSERVING_PLAYED:
4189       case RELATION_OBSERVING_STATIC:
4190         if (gamenum == -1) {
4191             /* Old ICC buglet */
4192             relation = RELATION_OBSERVING_STATIC;
4193         }
4194         newGameMode = IcsObserving;
4195         break;
4196       case RELATION_PLAYING_MYMOVE:
4197       case RELATION_PLAYING_NOTMYMOVE:
4198         newGameMode =
4199           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4200             IcsPlayingWhite : IcsPlayingBlack;
4201         break;
4202       case RELATION_EXAMINING:
4203         newGameMode = IcsExamining;
4204         break;
4205       case RELATION_ISOLATED_BOARD:
4206       default:
4207         /* Just display this board.  If user was doing something else,
4208            we will forget about it until the next board comes. */
4209         newGameMode = IcsIdle;
4210         break;
4211       case RELATION_STARTING_POSITION:
4212         newGameMode = gameMode;
4213         break;
4214     }
4215
4216     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4217          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4218       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4219       char *toSqr;
4220       for (k = 0; k < ranks; k++) {
4221         for (j = 0; j < files; j++)
4222           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4223         if(gameInfo.holdingsWidth > 1) {
4224              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4225              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4226         }
4227       }
4228       CopyBoard(partnerBoard, board);
4229       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4230         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4231         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4232       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4233       if(toSqr = strchr(str, '-')) {
4234         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4235         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4236       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4237       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4238       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4239       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4240       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4241       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4242                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4243       DisplayMessage(partnerStatus, "");
4244         partnerBoardValid = TRUE;
4245       return;
4246     }
4247
4248     /* Modify behavior for initial board display on move listing
4249        of wild games.
4250        */
4251     switch (ics_getting_history) {
4252       case H_FALSE:
4253       case H_REQUESTED:
4254         break;
4255       case H_GOT_REQ_HEADER:
4256       case H_GOT_UNREQ_HEADER:
4257         /* This is the initial position of the current game */
4258         gamenum = ics_gamenum;
4259         moveNum = 0;            /* old ICS bug workaround */
4260         if (to_play == 'B') {
4261           startedFromSetupPosition = TRUE;
4262           blackPlaysFirst = TRUE;
4263           moveNum = 1;
4264           if (forwardMostMove == 0) forwardMostMove = 1;
4265           if (backwardMostMove == 0) backwardMostMove = 1;
4266           if (currentMove == 0) currentMove = 1;
4267         }
4268         newGameMode = gameMode;
4269         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4270         break;
4271       case H_GOT_UNWANTED_HEADER:
4272         /* This is an initial board that we don't want */
4273         return;
4274       case H_GETTING_MOVES:
4275         /* Should not happen */
4276         DisplayError(_("Error gathering move list: extra board"), 0);
4277         ics_getting_history = H_FALSE;
4278         return;
4279     }
4280
4281    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4282                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4283      /* [HGM] We seem to have switched variant unexpectedly
4284       * Try to guess new variant from board size
4285       */
4286           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4287           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4288           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4289           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4290           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4291           if(!weird) newVariant = VariantNormal;
4292           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4293           /* Get a move list just to see the header, which
4294              will tell us whether this is really bug or zh */
4295           if (ics_getting_history == H_FALSE) {
4296             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4297             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4298             SendToICS(str);
4299           }
4300     }
4301
4302     /* Take action if this is the first board of a new game, or of a
4303        different game than is currently being displayed.  */
4304     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4305         relation == RELATION_ISOLATED_BOARD) {
4306
4307         /* Forget the old game and get the history (if any) of the new one */
4308         if (gameMode != BeginningOfGame) {
4309           Reset(TRUE, TRUE);
4310         }
4311         newGame = TRUE;
4312         if (appData.autoRaiseBoard) BoardToTop();
4313         prevMove = -3;
4314         if (gamenum == -1) {
4315             newGameMode = IcsIdle;
4316         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4317                    appData.getMoveList && !reqFlag) {
4318             /* Need to get game history */
4319             ics_getting_history = H_REQUESTED;
4320             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4321             SendToICS(str);
4322         }
4323
4324         /* Initially flip the board to have black on the bottom if playing
4325            black or if the ICS flip flag is set, but let the user change
4326            it with the Flip View button. */
4327         flipView = appData.autoFlipView ?
4328           (newGameMode == IcsPlayingBlack) || ics_flip :
4329           appData.flipView;
4330
4331         /* Done with values from previous mode; copy in new ones */
4332         gameMode = newGameMode;
4333         ModeHighlight();
4334         ics_gamenum = gamenum;
4335         if (gamenum == gs_gamenum) {
4336             int klen = strlen(gs_kind);
4337             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4338             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4339             gameInfo.event = StrSave(str);
4340         } else {
4341             gameInfo.event = StrSave("ICS game");
4342         }
4343         gameInfo.site = StrSave(appData.icsHost);
4344         gameInfo.date = PGNDate();
4345         gameInfo.round = StrSave("-");
4346         gameInfo.white = StrSave(white);
4347         gameInfo.black = StrSave(black);
4348         timeControl = basetime * 60 * 1000;
4349         timeControl_2 = 0;
4350         timeIncrement = increment * 1000;
4351         movesPerSession = 0;
4352         gameInfo.timeControl = TimeControlTagValue();
4353         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4354   if (appData.debugMode) {
4355     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4356     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4357     setbuf(debugFP, NULL);
4358   }
4359
4360         gameInfo.outOfBook = NULL;
4361
4362         /* Do we have the ratings? */
4363         if (strcmp(player1Name, white) == 0 &&
4364             strcmp(player2Name, black) == 0) {
4365             if (appData.debugMode)
4366               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4367                       player1Rating, player2Rating);
4368             gameInfo.whiteRating = player1Rating;
4369             gameInfo.blackRating = player2Rating;
4370         } else if (strcmp(player2Name, white) == 0 &&
4371                    strcmp(player1Name, black) == 0) {
4372             if (appData.debugMode)
4373               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4374                       player2Rating, player1Rating);
4375             gameInfo.whiteRating = player2Rating;
4376             gameInfo.blackRating = player1Rating;
4377         }
4378         player1Name[0] = player2Name[0] = NULLCHAR;
4379
4380         /* Silence shouts if requested */
4381         if (appData.quietPlay &&
4382             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4383             SendToICS(ics_prefix);
4384             SendToICS("set shout 0\n");
4385         }
4386     }
4387
4388     /* Deal with midgame name changes */
4389     if (!newGame) {
4390         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4391             if (gameInfo.white) free(gameInfo.white);
4392             gameInfo.white = StrSave(white);
4393         }
4394         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4395             if (gameInfo.black) free(gameInfo.black);
4396             gameInfo.black = StrSave(black);
4397         }
4398     }
4399
4400     /* Throw away game result if anything actually changes in examine mode */
4401     if (gameMode == IcsExamining && !newGame) {
4402         gameInfo.result = GameUnfinished;
4403         if (gameInfo.resultDetails != NULL) {
4404             free(gameInfo.resultDetails);
4405             gameInfo.resultDetails = NULL;
4406         }
4407     }
4408
4409     /* In pausing && IcsExamining mode, we ignore boards coming
4410        in if they are in a different variation than we are. */
4411     if (pauseExamInvalid) return;
4412     if (pausing && gameMode == IcsExamining) {
4413         if (moveNum <= pauseExamForwardMostMove) {
4414             pauseExamInvalid = TRUE;
4415             forwardMostMove = pauseExamForwardMostMove;
4416             return;
4417         }
4418     }
4419
4420   if (appData.debugMode) {
4421     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4422   }
4423     /* Parse the board */
4424     for (k = 0; k < ranks; k++) {
4425       for (j = 0; j < files; j++)
4426         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4427       if(gameInfo.holdingsWidth > 1) {
4428            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4429            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4430       }
4431     }
4432     CopyBoard(boards[moveNum], board);
4433     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4434     if (moveNum == 0) {
4435         startedFromSetupPosition =
4436           !CompareBoards(board, initialPosition);
4437         if(startedFromSetupPosition)
4438             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4439     }
4440
4441     /* [HGM] Set castling rights. Take the outermost Rooks,
4442        to make it also work for FRC opening positions. Note that board12
4443        is really defective for later FRC positions, as it has no way to
4444        indicate which Rook can castle if they are on the same side of King.
4445        For the initial position we grant rights to the outermost Rooks,
4446        and remember thos rights, and we then copy them on positions
4447        later in an FRC game. This means WB might not recognize castlings with
4448        Rooks that have moved back to their original position as illegal,
4449        but in ICS mode that is not its job anyway.
4450     */
4451     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4452     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4453
4454         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4455             if(board[0][i] == WhiteRook) j = i;
4456         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4457         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4458             if(board[0][i] == WhiteRook) j = i;
4459         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4460         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4461             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4462         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4463         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4464             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4465         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4466
4467         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4468         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4469             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4470         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4471             if(board[BOARD_HEIGHT-1][k] == bKing)
4472                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4473         if(gameInfo.variant == VariantTwoKings) {
4474             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4475             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4476             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4477         }
4478     } else { int r;
4479         r = boards[moveNum][CASTLING][0] = initialRights[0];
4480         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4481         r = boards[moveNum][CASTLING][1] = initialRights[1];
4482         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4483         r = boards[moveNum][CASTLING][3] = initialRights[3];
4484         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4485         r = boards[moveNum][CASTLING][4] = initialRights[4];
4486         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4487         /* wildcastle kludge: always assume King has rights */
4488         r = boards[moveNum][CASTLING][2] = initialRights[2];
4489         r = boards[moveNum][CASTLING][5] = initialRights[5];
4490     }
4491     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4492     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4493
4494
4495     if (ics_getting_history == H_GOT_REQ_HEADER ||
4496         ics_getting_history == H_GOT_UNREQ_HEADER) {
4497         /* This was an initial position from a move list, not
4498            the current position */
4499         return;
4500     }
4501
4502     /* Update currentMove and known move number limits */
4503     newMove = newGame || moveNum > forwardMostMove;
4504
4505     if (newGame) {
4506         forwardMostMove = backwardMostMove = currentMove = moveNum;
4507         if (gameMode == IcsExamining && moveNum == 0) {
4508           /* Workaround for ICS limitation: we are not told the wild
4509              type when starting to examine a game.  But if we ask for
4510              the move list, the move list header will tell us */
4511             ics_getting_history = H_REQUESTED;
4512             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4513             SendToICS(str);
4514         }
4515     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4516                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4517 #if ZIPPY
4518         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4519         /* [HGM] applied this also to an engine that is silently watching        */
4520         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4521             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4522             gameInfo.variant == currentlyInitializedVariant) {
4523           takeback = forwardMostMove - moveNum;
4524           for (i = 0; i < takeback; i++) {
4525             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4526             SendToProgram("undo\n", &first);
4527           }
4528         }
4529 #endif
4530
4531         forwardMostMove = moveNum;
4532         if (!pausing || currentMove > forwardMostMove)
4533           currentMove = forwardMostMove;
4534     } else {
4535         /* New part of history that is not contiguous with old part */
4536         if (pausing && gameMode == IcsExamining) {
4537             pauseExamInvalid = TRUE;
4538             forwardMostMove = pauseExamForwardMostMove;
4539             return;
4540         }
4541         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4542 #if ZIPPY
4543             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4544                 // [HGM] when we will receive the move list we now request, it will be
4545                 // fed to the engine from the first move on. So if the engine is not
4546                 // in the initial position now, bring it there.
4547                 InitChessProgram(&first, 0);
4548             }
4549 #endif
4550             ics_getting_history = H_REQUESTED;
4551             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4552             SendToICS(str);
4553         }
4554         forwardMostMove = backwardMostMove = currentMove = moveNum;
4555     }
4556
4557     /* Update the clocks */
4558     if (strchr(elapsed_time, '.')) {
4559       /* Time is in ms */
4560       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4561       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4562     } else {
4563       /* Time is in seconds */
4564       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4565       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4566     }
4567
4568
4569 #if ZIPPY
4570     if (appData.zippyPlay && newGame &&
4571         gameMode != IcsObserving && gameMode != IcsIdle &&
4572         gameMode != IcsExamining)
4573       ZippyFirstBoard(moveNum, basetime, increment);
4574 #endif
4575
4576     /* Put the move on the move list, first converting
4577        to canonical algebraic form. */
4578     if (moveNum > 0) {
4579   if (appData.debugMode) {
4580     if (appData.debugMode) { int f = forwardMostMove;
4581         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4582                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4583                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4584     }
4585     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4586     fprintf(debugFP, "moveNum = %d\n", moveNum);
4587     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4588     setbuf(debugFP, NULL);
4589   }
4590         if (moveNum <= backwardMostMove) {
4591             /* We don't know what the board looked like before
4592                this move.  Punt. */
4593           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4594             strcat(parseList[moveNum - 1], " ");
4595             strcat(parseList[moveNum - 1], elapsed_time);
4596             moveList[moveNum - 1][0] = NULLCHAR;
4597         } else if (strcmp(move_str, "none") == 0) {
4598             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4599             /* Again, we don't know what the board looked like;
4600                this is really the start of the game. */
4601             parseList[moveNum - 1][0] = NULLCHAR;
4602             moveList[moveNum - 1][0] = NULLCHAR;
4603             backwardMostMove = moveNum;
4604             startedFromSetupPosition = TRUE;
4605             fromX = fromY = toX = toY = -1;
4606         } else {
4607           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4608           //                 So we parse the long-algebraic move string in stead of the SAN move
4609           int valid; char buf[MSG_SIZ], *prom;
4610
4611           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4612                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4613           // str looks something like "Q/a1-a2"; kill the slash
4614           if(str[1] == '/')
4615             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4616           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4617           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4618                 strcat(buf, prom); // long move lacks promo specification!
4619           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4620                 if(appData.debugMode)
4621                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4622                 safeStrCpy(move_str, buf, MSG_SIZ);
4623           }
4624           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4625                                 &fromX, &fromY, &toX, &toY, &promoChar)
4626                || ParseOneMove(buf, moveNum - 1, &moveType,
4627                                 &fromX, &fromY, &toX, &toY, &promoChar);
4628           // end of long SAN patch
4629           if (valid) {
4630             (void) CoordsToAlgebraic(boards[moveNum - 1],
4631                                      PosFlags(moveNum - 1),
4632                                      fromY, fromX, toY, toX, promoChar,
4633                                      parseList[moveNum-1]);
4634             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4635               case MT_NONE:
4636               case MT_STALEMATE:
4637               default:
4638                 break;
4639               case MT_CHECK:
4640                 if(gameInfo.variant != VariantShogi)
4641                     strcat(parseList[moveNum - 1], "+");
4642                 break;
4643               case MT_CHECKMATE:
4644               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4645                 strcat(parseList[moveNum - 1], "#");
4646                 break;
4647             }
4648             strcat(parseList[moveNum - 1], " ");
4649             strcat(parseList[moveNum - 1], elapsed_time);
4650             /* currentMoveString is set as a side-effect of ParseOneMove */
4651             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4652             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4653             strcat(moveList[moveNum - 1], "\n");
4654
4655             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4656                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4657               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4658                 ChessSquare old, new = boards[moveNum][k][j];
4659                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4660                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4661                   if(old == new) continue;
4662                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4663                   else if(new == WhiteWazir || new == BlackWazir) {
4664                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4665                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4666                       else boards[moveNum][k][j] = old; // preserve type of Gold
4667                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4668                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4669               }
4670           } else {
4671             /* Move from ICS was illegal!?  Punt. */
4672             if (appData.debugMode) {
4673               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4674               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4675             }
4676             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4677             strcat(parseList[moveNum - 1], " ");
4678             strcat(parseList[moveNum - 1], elapsed_time);
4679             moveList[moveNum - 1][0] = NULLCHAR;
4680             fromX = fromY = toX = toY = -1;
4681           }
4682         }
4683   if (appData.debugMode) {
4684     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4685     setbuf(debugFP, NULL);
4686   }
4687
4688 #if ZIPPY
4689         /* Send move to chess program (BEFORE animating it). */
4690         if (appData.zippyPlay && !newGame && newMove &&
4691            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4692
4693             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4694                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4695                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4696                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4697                             move_str);
4698                     DisplayError(str, 0);
4699                 } else {
4700                     if (first.sendTime) {
4701                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4702                     }
4703                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4704                     if (firstMove && !bookHit) {
4705                         firstMove = FALSE;
4706                         if (first.useColors) {
4707                           SendToProgram(gameMode == IcsPlayingWhite ?
4708                                         "white\ngo\n" :
4709                                         "black\ngo\n", &first);
4710                         } else {
4711                           SendToProgram("go\n", &first);
4712                         }
4713                         first.maybeThinking = TRUE;
4714                     }
4715                 }
4716             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4717               if (moveList[moveNum - 1][0] == NULLCHAR) {
4718                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4719                 DisplayError(str, 0);
4720               } else {
4721                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4722                 SendMoveToProgram(moveNum - 1, &first);
4723               }
4724             }
4725         }
4726 #endif
4727     }
4728
4729     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4730         /* If move comes from a remote source, animate it.  If it
4731            isn't remote, it will have already been animated. */
4732         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4733             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4734         }
4735         if (!pausing && appData.highlightLastMove) {
4736             SetHighlights(fromX, fromY, toX, toY);
4737         }
4738     }
4739
4740     /* Start the clocks */
4741     whiteFlag = blackFlag = FALSE;
4742     appData.clockMode = !(basetime == 0 && increment == 0);
4743     if (ticking == 0) {
4744       ics_clock_paused = TRUE;
4745       StopClocks();
4746     } else if (ticking == 1) {
4747       ics_clock_paused = FALSE;
4748     }
4749     if (gameMode == IcsIdle ||
4750         relation == RELATION_OBSERVING_STATIC ||
4751         relation == RELATION_EXAMINING ||
4752         ics_clock_paused)
4753       DisplayBothClocks();
4754     else
4755       StartClocks();
4756
4757     /* Display opponents and material strengths */
4758     if (gameInfo.variant != VariantBughouse &&
4759         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4760         if (tinyLayout || smallLayout) {
4761             if(gameInfo.variant == VariantNormal)
4762               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4763                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4764                     basetime, increment);
4765             else
4766               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4767                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4768                     basetime, increment, (int) gameInfo.variant);
4769         } else {
4770             if(gameInfo.variant == VariantNormal)
4771               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4772                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4773                     basetime, increment);
4774             else
4775               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4776                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4777                     basetime, increment, VariantName(gameInfo.variant));
4778         }
4779         DisplayTitle(str);
4780   if (appData.debugMode) {
4781     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4782   }
4783     }
4784
4785
4786     /* Display the board */
4787     if (!pausing && !appData.noGUI) {
4788
4789       if (appData.premove)
4790           if (!gotPremove ||
4791              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4792              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4793               ClearPremoveHighlights();
4794
4795       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4796         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4797       DrawPosition(j, boards[currentMove]);
4798
4799       DisplayMove(moveNum - 1);
4800       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4801             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4802               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4803         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4804       }
4805     }
4806
4807     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4808 #if ZIPPY
4809     if(bookHit) { // [HGM] book: simulate book reply
4810         static char bookMove[MSG_SIZ]; // a bit generous?
4811
4812         programStats.nodes = programStats.depth = programStats.time =
4813         programStats.score = programStats.got_only_move = 0;
4814         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4815
4816         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4817         strcat(bookMove, bookHit);
4818         HandleMachineMove(bookMove, &first);
4819     }
4820 #endif
4821 }
4822
4823 void
4824 GetMoveListEvent()
4825 {
4826     char buf[MSG_SIZ];
4827     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4828         ics_getting_history = H_REQUESTED;
4829         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4830         SendToICS(buf);
4831     }
4832 }
4833
4834 void
4835 AnalysisPeriodicEvent(force)
4836      int force;
4837 {
4838     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4839          && !force) || !appData.periodicUpdates)
4840       return;
4841
4842     /* Send . command to Crafty to collect stats */
4843     SendToProgram(".\n", &first);
4844
4845     /* Don't send another until we get a response (this makes
4846        us stop sending to old Crafty's which don't understand
4847        the "." command (sending illegal cmds resets node count & time,
4848        which looks bad)) */
4849     programStats.ok_to_send = 0;
4850 }
4851
4852 void ics_update_width(new_width)
4853         int new_width;
4854 {
4855         ics_printf("set width %d\n", new_width);
4856 }
4857
4858 void
4859 SendMoveToProgram(moveNum, cps)
4860      int moveNum;
4861      ChessProgramState *cps;
4862 {
4863     char buf[MSG_SIZ];
4864
4865     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4866         // null move in variant where engine does not understand it (for analysis purposes)
4867         SendBoard(cps, moveNum + 1); // send position after move in stead.
4868         return;
4869     }
4870     if (cps->useUsermove) {
4871       SendToProgram("usermove ", cps);
4872     }
4873     if (cps->useSAN) {
4874       char *space;
4875       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4876         int len = space - parseList[moveNum];
4877         memcpy(buf, parseList[moveNum], len);
4878         buf[len++] = '\n';
4879         buf[len] = NULLCHAR;
4880       } else {
4881         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4882       }
4883       SendToProgram(buf, cps);
4884     } else {
4885       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4886         AlphaRank(moveList[moveNum], 4);
4887         SendToProgram(moveList[moveNum], cps);
4888         AlphaRank(moveList[moveNum], 4); // and back
4889       } else
4890       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4891        * the engine. It would be nice to have a better way to identify castle
4892        * moves here. */
4893       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4894                                                                          && cps->useOOCastle) {
4895         int fromX = moveList[moveNum][0] - AAA;
4896         int fromY = moveList[moveNum][1] - ONE;
4897         int toX = moveList[moveNum][2] - AAA;
4898         int toY = moveList[moveNum][3] - ONE;
4899         if((boards[moveNum][fromY][fromX] == WhiteKing
4900             && boards[moveNum][toY][toX] == WhiteRook)
4901            || (boards[moveNum][fromY][fromX] == BlackKing
4902                && boards[moveNum][toY][toX] == BlackRook)) {
4903           if(toX > fromX) SendToProgram("O-O\n", cps);
4904           else SendToProgram("O-O-O\n", cps);
4905         }
4906         else SendToProgram(moveList[moveNum], cps);
4907       } else
4908       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4909         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4910           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4911           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4912                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4913         } else
4914           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4915                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4916         SendToProgram(buf, cps);
4917       }
4918       else SendToProgram(moveList[moveNum], cps);
4919       /* End of additions by Tord */
4920     }
4921
4922     /* [HGM] setting up the opening has brought engine in force mode! */
4923     /*       Send 'go' if we are in a mode where machine should play. */
4924     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4925         (gameMode == TwoMachinesPlay   ||
4926 #if ZIPPY
4927          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4928 #endif
4929          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4930         SendToProgram("go\n", cps);
4931   if (appData.debugMode) {
4932     fprintf(debugFP, "(extra)\n");
4933   }
4934     }
4935     setboardSpoiledMachineBlack = 0;
4936 }
4937
4938 void
4939 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4940      ChessMove moveType;
4941      int fromX, fromY, toX, toY;
4942      char promoChar;
4943 {
4944     char user_move[MSG_SIZ];
4945
4946     switch (moveType) {
4947       default:
4948         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4949                 (int)moveType, fromX, fromY, toX, toY);
4950         DisplayError(user_move + strlen("say "), 0);
4951         break;
4952       case WhiteKingSideCastle:
4953       case BlackKingSideCastle:
4954       case WhiteQueenSideCastleWild:
4955       case BlackQueenSideCastleWild:
4956       /* PUSH Fabien */
4957       case WhiteHSideCastleFR:
4958       case BlackHSideCastleFR:
4959       /* POP Fabien */
4960         snprintf(user_move, MSG_SIZ, "o-o\n");
4961         break;
4962       case WhiteQueenSideCastle:
4963       case BlackQueenSideCastle:
4964       case WhiteKingSideCastleWild:
4965       case BlackKingSideCastleWild:
4966       /* PUSH Fabien */
4967       case WhiteASideCastleFR:
4968       case BlackASideCastleFR:
4969       /* POP Fabien */
4970         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4971         break;
4972       case WhiteNonPromotion:
4973       case BlackNonPromotion:
4974         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4975         break;
4976       case WhitePromotion:
4977       case BlackPromotion:
4978         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4979           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4980                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4981                 PieceToChar(WhiteFerz));
4982         else if(gameInfo.variant == VariantGreat)
4983           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4984                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4985                 PieceToChar(WhiteMan));
4986         else
4987           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4988                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4989                 promoChar);
4990         break;
4991       case WhiteDrop:
4992       case BlackDrop:
4993       drop:
4994         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4995                  ToUpper(PieceToChar((ChessSquare) fromX)),
4996                  AAA + toX, ONE + toY);
4997         break;
4998       case IllegalMove:  /* could be a variant we don't quite understand */
4999         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5000       case NormalMove:
5001       case WhiteCapturesEnPassant:
5002       case BlackCapturesEnPassant:
5003         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5004                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5005         break;
5006     }
5007     SendToICS(user_move);
5008     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5009         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5010 }
5011
5012 void
5013 UploadGameEvent()
5014 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5015     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5016     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5017     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5018         DisplayError("You cannot do this while you are playing or observing", 0);
5019         return;
5020     }
5021     if(gameMode != IcsExamining) { // is this ever not the case?
5022         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5023
5024         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5025           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5026         } else { // on FICS we must first go to general examine mode
5027           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5028         }
5029         if(gameInfo.variant != VariantNormal) {
5030             // try figure out wild number, as xboard names are not always valid on ICS
5031             for(i=1; i<=36; i++) {
5032               snprintf(buf, MSG_SIZ, "wild/%d", i);
5033                 if(StringToVariant(buf) == gameInfo.variant) break;
5034             }
5035             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5036             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5037             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5038         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5039         SendToICS(ics_prefix);
5040         SendToICS(buf);
5041         if(startedFromSetupPosition || backwardMostMove != 0) {
5042           fen = PositionToFEN(backwardMostMove, NULL);
5043           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5044             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5045             SendToICS(buf);
5046           } else { // FICS: everything has to set by separate bsetup commands
5047             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5048             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5049             SendToICS(buf);
5050             if(!WhiteOnMove(backwardMostMove)) {
5051                 SendToICS("bsetup tomove black\n");
5052             }
5053             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5054             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5055             SendToICS(buf);
5056             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5057             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5058             SendToICS(buf);
5059             i = boards[backwardMostMove][EP_STATUS];
5060             if(i >= 0) { // set e.p.
5061               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5062                 SendToICS(buf);
5063             }
5064             bsetup++;
5065           }
5066         }
5067       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5068             SendToICS("bsetup done\n"); // switch to normal examining.
5069     }
5070     for(i = backwardMostMove; i<last; i++) {
5071         char buf[20];
5072         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5073         SendToICS(buf);
5074     }
5075     SendToICS(ics_prefix);
5076     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5077 }
5078
5079 void
5080 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5081      int rf, ff, rt, ft;
5082      char promoChar;
5083      char move[7];
5084 {
5085     if (rf == DROP_RANK) {
5086       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5087       sprintf(move, "%c@%c%c\n",
5088                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5089     } else {
5090         if (promoChar == 'x' || promoChar == NULLCHAR) {
5091           sprintf(move, "%c%c%c%c\n",
5092                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5093         } else {
5094             sprintf(move, "%c%c%c%c%c\n",
5095                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5096         }
5097     }
5098 }
5099
5100 void
5101 ProcessICSInitScript(f)
5102      FILE *f;
5103 {
5104     char buf[MSG_SIZ];
5105
5106     while (fgets(buf, MSG_SIZ, f)) {
5107         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5108     }
5109
5110     fclose(f);
5111 }
5112
5113
5114 static int lastX, lastY, selectFlag, dragging;
5115
5116 void
5117 Sweep(int step)
5118 {
5119     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5120     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5121     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5122     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5123     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5124     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5125     do {
5126         promoSweep -= step;
5127         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5128         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5129         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5130         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5131         if(!step) step = 1;
5132     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5133             appData.testLegality && (promoSweep == king ||
5134             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5135     ChangeDragPiece(promoSweep);
5136 }
5137
5138 int PromoScroll(int x, int y)
5139 {
5140   int step = 0;
5141
5142   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5143   if(abs(x - lastX) < 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             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8922             fromX = moveType == WhiteDrop ?
8923               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8924             (int) CharToPiece(ToLower(currentMoveString[0]));
8925             fromY = DROP_RANK;
8926             toX = currentMoveString[2] - AAA;
8927             toY = currentMoveString[3] - ONE;
8928             promoChar = NULLCHAR;
8929             break;
8930           case AmbiguousMove:
8931             /* bug? */
8932             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8933   if (appData.debugMode) {
8934     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8935     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8936     setbuf(debugFP, NULL);
8937   }
8938             DisplayError(buf, 0);
8939             return;
8940           case ImpossibleMove:
8941             /* bug? */
8942             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8943   if (appData.debugMode) {
8944     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8945     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8946     setbuf(debugFP, NULL);
8947   }
8948             DisplayError(buf, 0);
8949             return;
8950           case EndOfFile:
8951             if (boardIndex < backwardMostMove) {
8952                 /* Oops, gap.  How did that happen? */
8953                 DisplayError(_("Gap in move list"), 0);
8954                 return;
8955             }
8956             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8957             if (boardIndex > forwardMostMove) {
8958                 forwardMostMove = boardIndex;
8959             }
8960             return;
8961           case ElapsedTime:
8962             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8963                 strcat(parseList[boardIndex-1], " ");
8964                 strcat(parseList[boardIndex-1], yy_text);
8965             }
8966             continue;
8967           case Comment:
8968           case PGNTag:
8969           case NAG:
8970           default:
8971             /* ignore */
8972             continue;
8973           case WhiteWins:
8974           case BlackWins:
8975           case GameIsDrawn:
8976           case GameUnfinished:
8977             if (gameMode == IcsExamining) {
8978                 if (boardIndex < backwardMostMove) {
8979                     /* Oops, gap.  How did that happen? */
8980                     return;
8981                 }
8982                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8983                 return;
8984             }
8985             gameInfo.result = moveType;
8986             p = strchr(yy_text, '{');
8987             if (p == NULL) p = strchr(yy_text, '(');
8988             if (p == NULL) {
8989                 p = yy_text;
8990                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8991             } else {
8992                 q = strchr(p, *p == '{' ? '}' : ')');
8993                 if (q != NULL) *q = NULLCHAR;
8994                 p++;
8995             }
8996             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8997             gameInfo.resultDetails = StrSave(p);
8998             continue;
8999         }
9000         if (boardIndex >= forwardMostMove &&
9001             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9002             backwardMostMove = blackPlaysFirst ? 1 : 0;
9003             return;
9004         }
9005         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9006                                  fromY, fromX, toY, toX, promoChar,
9007                                  parseList[boardIndex]);
9008         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9009         /* currentMoveString is set as a side-effect of yylex */
9010         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9011         strcat(moveList[boardIndex], "\n");
9012         boardIndex++;
9013         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9014         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9015           case MT_NONE:
9016           case MT_STALEMATE:
9017           default:
9018             break;
9019           case MT_CHECK:
9020             if(gameInfo.variant != VariantShogi)
9021                 strcat(parseList[boardIndex - 1], "+");
9022             break;
9023           case MT_CHECKMATE:
9024           case MT_STAINMATE:
9025             strcat(parseList[boardIndex - 1], "#");
9026             break;
9027         }
9028     }
9029 }
9030
9031
9032 /* Apply a move to the given board  */
9033 void
9034 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9035      int fromX, fromY, toX, toY;
9036      int promoChar;
9037      Board board;
9038 {
9039   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9040   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9041
9042     /* [HGM] compute & store e.p. status and castling rights for new position */
9043     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9044
9045       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9046       oldEP = (signed char)board[EP_STATUS];
9047       board[EP_STATUS] = EP_NONE;
9048
9049   if (fromY == DROP_RANK) {
9050         /* must be first */
9051         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9052             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9053             return;
9054         }
9055         piece = board[toY][toX] = (ChessSquare) fromX;
9056   } else {
9057       int i;
9058
9059       if( board[toY][toX] != EmptySquare )
9060            board[EP_STATUS] = EP_CAPTURE;
9061
9062       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9063            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9064                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9065       } else
9066       if( board[fromY][fromX] == WhitePawn ) {
9067            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9068                board[EP_STATUS] = EP_PAWN_MOVE;
9069            if( toY-fromY==2) {
9070                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9071                         gameInfo.variant != VariantBerolina || toX < fromX)
9072                       board[EP_STATUS] = toX | berolina;
9073                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9074                         gameInfo.variant != VariantBerolina || toX > fromX)
9075                       board[EP_STATUS] = toX;
9076            }
9077       } else
9078       if( board[fromY][fromX] == BlackPawn ) {
9079            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9080                board[EP_STATUS] = EP_PAWN_MOVE;
9081            if( toY-fromY== -2) {
9082                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9083                         gameInfo.variant != VariantBerolina || toX < fromX)
9084                       board[EP_STATUS] = toX | berolina;
9085                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9086                         gameInfo.variant != VariantBerolina || toX > fromX)
9087                       board[EP_STATUS] = toX;
9088            }
9089        }
9090
9091        for(i=0; i<nrCastlingRights; i++) {
9092            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9093               board[CASTLING][i] == toX   && castlingRank[i] == toY
9094              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9095        }
9096
9097      if (fromX == toX && fromY == toY) return;
9098
9099      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9100      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9101      if(gameInfo.variant == VariantKnightmate)
9102          king += (int) WhiteUnicorn - (int) WhiteKing;
9103
9104     /* Code added by Tord: */
9105     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9106     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9107         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9108       board[fromY][fromX] = EmptySquare;
9109       board[toY][toX] = EmptySquare;
9110       if((toX > fromX) != (piece == WhiteRook)) {
9111         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9112       } else {
9113         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9114       }
9115     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9116                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9117       board[fromY][fromX] = EmptySquare;
9118       board[toY][toX] = EmptySquare;
9119       if((toX > fromX) != (piece == BlackRook)) {
9120         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9121       } else {
9122         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9123       }
9124     /* End of code added by Tord */
9125
9126     } else if (board[fromY][fromX] == king
9127         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9128         && toY == fromY && toX > fromX+1) {
9129         board[fromY][fromX] = EmptySquare;
9130         board[toY][toX] = king;
9131         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9132         board[fromY][BOARD_RGHT-1] = EmptySquare;
9133     } else if (board[fromY][fromX] == king
9134         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9135                && toY == fromY && toX < fromX-1) {
9136         board[fromY][fromX] = EmptySquare;
9137         board[toY][toX] = king;
9138         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9139         board[fromY][BOARD_LEFT] = EmptySquare;
9140     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9141                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9142                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9143                ) {
9144         /* white pawn promotion */
9145         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9146         if(gameInfo.variant==VariantBughouse ||
9147            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9148             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9149         board[fromY][fromX] = EmptySquare;
9150     } else if ((fromY >= BOARD_HEIGHT>>1)
9151                && (toX != fromX)
9152                && gameInfo.variant != VariantXiangqi
9153                && gameInfo.variant != VariantBerolina
9154                && (board[fromY][fromX] == WhitePawn)
9155                && (board[toY][toX] == EmptySquare)) {
9156         board[fromY][fromX] = EmptySquare;
9157         board[toY][toX] = WhitePawn;
9158         captured = board[toY - 1][toX];
9159         board[toY - 1][toX] = EmptySquare;
9160     } else if ((fromY == BOARD_HEIGHT-4)
9161                && (toX == fromX)
9162                && gameInfo.variant == VariantBerolina
9163                && (board[fromY][fromX] == WhitePawn)
9164                && (board[toY][toX] == EmptySquare)) {
9165         board[fromY][fromX] = EmptySquare;
9166         board[toY][toX] = WhitePawn;
9167         if(oldEP & EP_BEROLIN_A) {
9168                 captured = board[fromY][fromX-1];
9169                 board[fromY][fromX-1] = EmptySquare;
9170         }else{  captured = board[fromY][fromX+1];
9171                 board[fromY][fromX+1] = EmptySquare;
9172         }
9173     } else if (board[fromY][fromX] == king
9174         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9175                && toY == fromY && toX > fromX+1) {
9176         board[fromY][fromX] = EmptySquare;
9177         board[toY][toX] = king;
9178         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9179         board[fromY][BOARD_RGHT-1] = EmptySquare;
9180     } else if (board[fromY][fromX] == king
9181         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9182                && toY == fromY && toX < fromX-1) {
9183         board[fromY][fromX] = EmptySquare;
9184         board[toY][toX] = king;
9185         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9186         board[fromY][BOARD_LEFT] = EmptySquare;
9187     } else if (fromY == 7 && fromX == 3
9188                && board[fromY][fromX] == BlackKing
9189                && toY == 7 && toX == 5) {
9190         board[fromY][fromX] = EmptySquare;
9191         board[toY][toX] = BlackKing;
9192         board[fromY][7] = EmptySquare;
9193         board[toY][4] = BlackRook;
9194     } else if (fromY == 7 && fromX == 3
9195                && board[fromY][fromX] == BlackKing
9196                && toY == 7 && toX == 1) {
9197         board[fromY][fromX] = EmptySquare;
9198         board[toY][toX] = BlackKing;
9199         board[fromY][0] = EmptySquare;
9200         board[toY][2] = BlackRook;
9201     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9202                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9203                && toY < promoRank && promoChar
9204                ) {
9205         /* black pawn promotion */
9206         board[toY][toX] = CharToPiece(ToLower(promoChar));
9207         if(gameInfo.variant==VariantBughouse ||
9208            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9209             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9210         board[fromY][fromX] = EmptySquare;
9211     } else if ((fromY < BOARD_HEIGHT>>1)
9212                && (toX != fromX)
9213                && gameInfo.variant != VariantXiangqi
9214                && gameInfo.variant != VariantBerolina
9215                && (board[fromY][fromX] == BlackPawn)
9216                && (board[toY][toX] == EmptySquare)) {
9217         board[fromY][fromX] = EmptySquare;
9218         board[toY][toX] = BlackPawn;
9219         captured = board[toY + 1][toX];
9220         board[toY + 1][toX] = EmptySquare;
9221     } else if ((fromY == 3)
9222                && (toX == fromX)
9223                && gameInfo.variant == VariantBerolina
9224                && (board[fromY][fromX] == BlackPawn)
9225                && (board[toY][toX] == EmptySquare)) {
9226         board[fromY][fromX] = EmptySquare;
9227         board[toY][toX] = BlackPawn;
9228         if(oldEP & EP_BEROLIN_A) {
9229                 captured = board[fromY][fromX-1];
9230                 board[fromY][fromX-1] = EmptySquare;
9231         }else{  captured = board[fromY][fromX+1];
9232                 board[fromY][fromX+1] = EmptySquare;
9233         }
9234     } else {
9235         board[toY][toX] = board[fromY][fromX];
9236         board[fromY][fromX] = EmptySquare;
9237     }
9238   }
9239
9240     if (gameInfo.holdingsWidth != 0) {
9241
9242       /* !!A lot more code needs to be written to support holdings  */
9243       /* [HGM] OK, so I have written it. Holdings are stored in the */
9244       /* penultimate board files, so they are automaticlly stored   */
9245       /* in the game history.                                       */
9246       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9247                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9248         /* Delete from holdings, by decreasing count */
9249         /* and erasing image if necessary            */
9250         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9251         if(p < (int) BlackPawn) { /* white drop */
9252              p -= (int)WhitePawn;
9253                  p = PieceToNumber((ChessSquare)p);
9254              if(p >= gameInfo.holdingsSize) p = 0;
9255              if(--board[p][BOARD_WIDTH-2] <= 0)
9256                   board[p][BOARD_WIDTH-1] = EmptySquare;
9257              if((int)board[p][BOARD_WIDTH-2] < 0)
9258                         board[p][BOARD_WIDTH-2] = 0;
9259         } else {                  /* black drop */
9260              p -= (int)BlackPawn;
9261                  p = PieceToNumber((ChessSquare)p);
9262              if(p >= gameInfo.holdingsSize) p = 0;
9263              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9264                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9265              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9266                         board[BOARD_HEIGHT-1-p][1] = 0;
9267         }
9268       }
9269       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9270           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9271         /* [HGM] holdings: Add to holdings, if holdings exist */
9272         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9273                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9274                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9275         }
9276         p = (int) captured;
9277         if (p >= (int) BlackPawn) {
9278           p -= (int)BlackPawn;
9279           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9280                   /* in Shogi restore piece to its original  first */
9281                   captured = (ChessSquare) (DEMOTED captured);
9282                   p = DEMOTED p;
9283           }
9284           p = PieceToNumber((ChessSquare)p);
9285           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9286           board[p][BOARD_WIDTH-2]++;
9287           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9288         } else {
9289           p -= (int)WhitePawn;
9290           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9291                   captured = (ChessSquare) (DEMOTED captured);
9292                   p = DEMOTED p;
9293           }
9294           p = PieceToNumber((ChessSquare)p);
9295           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9296           board[BOARD_HEIGHT-1-p][1]++;
9297           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9298         }
9299       }
9300     } else if (gameInfo.variant == VariantAtomic) {
9301       if (captured != EmptySquare) {
9302         int y, x;
9303         for (y = toY-1; y <= toY+1; y++) {
9304           for (x = toX-1; x <= toX+1; x++) {
9305             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9306                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9307               board[y][x] = EmptySquare;
9308             }
9309           }
9310         }
9311         board[toY][toX] = EmptySquare;
9312       }
9313     }
9314     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9315         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9316     } else
9317     if(promoChar == '+') {
9318         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9319         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9320     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9321         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9322     }
9323     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9324                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9325         // [HGM] superchess: take promotion piece out of holdings
9326         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9327         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9328             if(!--board[k][BOARD_WIDTH-2])
9329                 board[k][BOARD_WIDTH-1] = EmptySquare;
9330         } else {
9331             if(!--board[BOARD_HEIGHT-1-k][1])
9332                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9333         }
9334     }
9335
9336 }
9337
9338 /* Updates forwardMostMove */
9339 void
9340 MakeMove(fromX, fromY, toX, toY, promoChar)
9341      int fromX, fromY, toX, toY;
9342      int promoChar;
9343 {
9344 //    forwardMostMove++; // [HGM] bare: moved downstream
9345
9346     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9347         int timeLeft; static int lastLoadFlag=0; int king, piece;
9348         piece = boards[forwardMostMove][fromY][fromX];
9349         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9350         if(gameInfo.variant == VariantKnightmate)
9351             king += (int) WhiteUnicorn - (int) WhiteKing;
9352         if(forwardMostMove == 0) {
9353             if(blackPlaysFirst)
9354                 fprintf(serverMoves, "%s;", second.tidy);
9355             fprintf(serverMoves, "%s;", first.tidy);
9356             if(!blackPlaysFirst)
9357                 fprintf(serverMoves, "%s;", second.tidy);
9358         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9359         lastLoadFlag = loadFlag;
9360         // print base move
9361         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9362         // print castling suffix
9363         if( toY == fromY && piece == king ) {
9364             if(toX-fromX > 1)
9365                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9366             if(fromX-toX >1)
9367                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9368         }
9369         // e.p. suffix
9370         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9371              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9372              boards[forwardMostMove][toY][toX] == EmptySquare
9373              && fromX != toX && fromY != toY)
9374                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9375         // promotion suffix
9376         if(promoChar != NULLCHAR)
9377                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9378         if(!loadFlag) {
9379             fprintf(serverMoves, "/%d/%d",
9380                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9381             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9382             else                      timeLeft = blackTimeRemaining/1000;
9383             fprintf(serverMoves, "/%d", timeLeft);
9384         }
9385         fflush(serverMoves);
9386     }
9387
9388     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9389       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9390                         0, 1);
9391       return;
9392     }
9393     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9394     if (commentList[forwardMostMove+1] != NULL) {
9395         free(commentList[forwardMostMove+1]);
9396         commentList[forwardMostMove+1] = NULL;
9397     }
9398     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9399     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9400     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9401     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9402     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9403     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9404     gameInfo.result = GameUnfinished;
9405     if (gameInfo.resultDetails != NULL) {
9406         free(gameInfo.resultDetails);
9407         gameInfo.resultDetails = NULL;
9408     }
9409     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9410                               moveList[forwardMostMove - 1]);
9411     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9412                              PosFlags(forwardMostMove - 1),
9413                              fromY, fromX, toY, toX, promoChar,
9414                              parseList[forwardMostMove - 1]);
9415     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9416       case MT_NONE:
9417       case MT_STALEMATE:
9418       default:
9419         break;
9420       case MT_CHECK:
9421         if(gameInfo.variant != VariantShogi)
9422             strcat(parseList[forwardMostMove - 1], "+");
9423         break;
9424       case MT_CHECKMATE:
9425       case MT_STAINMATE:
9426         strcat(parseList[forwardMostMove - 1], "#");
9427         break;
9428     }
9429     if (appData.debugMode) {
9430         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9431     }
9432
9433 }
9434
9435 /* Updates currentMove if not pausing */
9436 void
9437 ShowMove(fromX, fromY, toX, toY)
9438 {
9439     int instant = (gameMode == PlayFromGameFile) ?
9440         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9441     if(appData.noGUI) return;
9442     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9443         if (!instant) {
9444             if (forwardMostMove == currentMove + 1) {
9445                 AnimateMove(boards[forwardMostMove - 1],
9446                             fromX, fromY, toX, toY);
9447             }
9448             if (appData.highlightLastMove) {
9449                 SetHighlights(fromX, fromY, toX, toY);
9450             }
9451         }
9452         currentMove = forwardMostMove;
9453     }
9454
9455     if (instant) return;
9456
9457     DisplayMove(currentMove - 1);
9458     DrawPosition(FALSE, boards[currentMove]);
9459     DisplayBothClocks();
9460     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9461     DisplayBook(currentMove);
9462 }
9463
9464 void SendEgtPath(ChessProgramState *cps)
9465 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9466         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9467
9468         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9469
9470         while(*p) {
9471             char c, *q = name+1, *r, *s;
9472
9473             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9474             while(*p && *p != ',') *q++ = *p++;
9475             *q++ = ':'; *q = 0;
9476             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9477                 strcmp(name, ",nalimov:") == 0 ) {
9478                 // take nalimov path from the menu-changeable option first, if it is defined
9479               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9480                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9481             } else
9482             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9483                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9484                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9485                 s = r = StrStr(s, ":") + 1; // beginning of path info
9486                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9487                 c = *r; *r = 0;             // temporarily null-terminate path info
9488                     *--q = 0;               // strip of trailig ':' from name
9489                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9490                 *r = c;
9491                 SendToProgram(buf,cps);     // send egtbpath command for this format
9492             }
9493             if(*p == ',') p++; // read away comma to position for next format name
9494         }
9495 }
9496
9497 void
9498 InitChessProgram(cps, setup)
9499      ChessProgramState *cps;
9500      int setup; /* [HGM] needed to setup FRC opening position */
9501 {
9502     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9503     if (appData.noChessProgram) return;
9504     hintRequested = FALSE;
9505     bookRequested = FALSE;
9506
9507     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9508     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9509     if(cps->memSize) { /* [HGM] memory */
9510       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9511         SendToProgram(buf, cps);
9512     }
9513     SendEgtPath(cps); /* [HGM] EGT */
9514     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9515       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9516         SendToProgram(buf, cps);
9517     }
9518
9519     SendToProgram(cps->initString, cps);
9520     if (gameInfo.variant != VariantNormal &&
9521         gameInfo.variant != VariantLoadable
9522         /* [HGM] also send variant if board size non-standard */
9523         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9524                                             ) {
9525       char *v = VariantName(gameInfo.variant);
9526       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9527         /* [HGM] in protocol 1 we have to assume all variants valid */
9528         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9529         DisplayFatalError(buf, 0, 1);
9530         return;
9531       }
9532
9533       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9534       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9535       if( gameInfo.variant == VariantXiangqi )
9536            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9537       if( gameInfo.variant == VariantShogi )
9538            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9539       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9540            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9541       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9542           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9543            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9544       if( gameInfo.variant == VariantCourier )
9545            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9546       if( gameInfo.variant == VariantSuper )
9547            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9548       if( gameInfo.variant == VariantGreat )
9549            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9550       if( gameInfo.variant == VariantSChess )
9551            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9552       if( gameInfo.variant == VariantGrand )
9553            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9554
9555       if(overruled) {
9556         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9557                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9558            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9559            if(StrStr(cps->variants, b) == NULL) {
9560                // specific sized variant not known, check if general sizing allowed
9561                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9562                    if(StrStr(cps->variants, "boardsize") == NULL) {
9563                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9564                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9565                        DisplayFatalError(buf, 0, 1);
9566                        return;
9567                    }
9568                    /* [HGM] here we really should compare with the maximum supported board size */
9569                }
9570            }
9571       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9572       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9573       SendToProgram(buf, cps);
9574     }
9575     currentlyInitializedVariant = gameInfo.variant;
9576
9577     /* [HGM] send opening position in FRC to first engine */
9578     if(setup) {
9579           SendToProgram("force\n", cps);
9580           SendBoard(cps, 0);
9581           /* engine is now in force mode! Set flag to wake it up after first move. */
9582           setboardSpoiledMachineBlack = 1;
9583     }
9584
9585     if (cps->sendICS) {
9586       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9587       SendToProgram(buf, cps);
9588     }
9589     cps->maybeThinking = FALSE;
9590     cps->offeredDraw = 0;
9591     if (!appData.icsActive) {
9592         SendTimeControl(cps, movesPerSession, timeControl,
9593                         timeIncrement, appData.searchDepth,
9594                         searchTime);
9595     }
9596     if (appData.showThinking
9597         // [HGM] thinking: four options require thinking output to be sent
9598         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9599                                 ) {
9600         SendToProgram("post\n", cps);
9601     }
9602     SendToProgram("hard\n", cps);
9603     if (!appData.ponderNextMove) {
9604         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9605            it without being sure what state we are in first.  "hard"
9606            is not a toggle, so that one is OK.
9607          */
9608         SendToProgram("easy\n", cps);
9609     }
9610     if (cps->usePing) {
9611       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9612       SendToProgram(buf, cps);
9613     }
9614     cps->initDone = TRUE;
9615     ClearEngineOutputPane(cps == &second);
9616 }
9617
9618
9619 void
9620 StartChessProgram(cps)
9621      ChessProgramState *cps;
9622 {
9623     char buf[MSG_SIZ];
9624     int err;
9625
9626     if (appData.noChessProgram) return;
9627     cps->initDone = FALSE;
9628
9629     if (strcmp(cps->host, "localhost") == 0) {
9630         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9631     } else if (*appData.remoteShell == NULLCHAR) {
9632         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9633     } else {
9634         if (*appData.remoteUser == NULLCHAR) {
9635           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9636                     cps->program);
9637         } else {
9638           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9639                     cps->host, appData.remoteUser, cps->program);
9640         }
9641         err = StartChildProcess(buf, "", &cps->pr);
9642     }
9643
9644     if (err != 0) {
9645       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9646         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9647         if(cps != &first) return;
9648         appData.noChessProgram = TRUE;
9649         ThawUI();
9650         SetNCPMode();
9651 //      DisplayFatalError(buf, err, 1);
9652 //      cps->pr = NoProc;
9653 //      cps->isr = NULL;
9654         return;
9655     }
9656
9657     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9658     if (cps->protocolVersion > 1) {
9659       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9660       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9661       cps->comboCnt = 0;  //                and values of combo boxes
9662       SendToProgram(buf, cps);
9663     } else {
9664       SendToProgram("xboard\n", cps);
9665     }
9666 }
9667
9668 void
9669 TwoMachinesEventIfReady P((void))
9670 {
9671   static int curMess = 0;
9672   if (first.lastPing != first.lastPong) {
9673     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9674     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9675     return;
9676   }
9677   if (second.lastPing != second.lastPong) {
9678     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9679     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9680     return;
9681   }
9682   DisplayMessage("", ""); curMess = 0;
9683   ThawUI();
9684   TwoMachinesEvent();
9685 }
9686
9687 char *
9688 MakeName(char *template)
9689 {
9690     time_t clock;
9691     struct tm *tm;
9692     static char buf[MSG_SIZ];
9693     char *p = buf;
9694     int i;
9695
9696     clock = time((time_t *)NULL);
9697     tm = localtime(&clock);
9698
9699     while(*p++ = *template++) if(p[-1] == '%') {
9700         switch(*template++) {
9701           case 0:   *p = 0; return buf;
9702           case 'Y': i = tm->tm_year+1900; break;
9703           case 'y': i = tm->tm_year-100; break;
9704           case 'M': i = tm->tm_mon+1; break;
9705           case 'd': i = tm->tm_mday; break;
9706           case 'h': i = tm->tm_hour; break;
9707           case 'm': i = tm->tm_min; break;
9708           case 's': i = tm->tm_sec; break;
9709           default:  i = 0;
9710         }
9711         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9712     }
9713     return buf;
9714 }
9715
9716 int
9717 CountPlayers(char *p)
9718 {
9719     int n = 0;
9720     while(p = strchr(p, '\n')) p++, n++; // count participants
9721     return n;
9722 }
9723
9724 FILE *
9725 WriteTourneyFile(char *results, FILE *f)
9726 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9727     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9728     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9729         // create a file with tournament description
9730         fprintf(f, "-participants {%s}\n", appData.participants);
9731         fprintf(f, "-seedBase %d\n", appData.seedBase);
9732         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9733         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9734         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9735         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9736         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9737         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9738         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9739         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9740         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9741         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9742         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9743         if(searchTime > 0)
9744                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9745         else {
9746                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9747                 fprintf(f, "-tc %s\n", appData.timeControl);
9748                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9749         }
9750         fprintf(f, "-results \"%s\"\n", results);
9751     }
9752     return f;
9753 }
9754
9755 #define MAXENGINES 1000
9756 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9757
9758 void Substitute(char *participants, int expunge)
9759 {
9760     int i, changed, changes=0, nPlayers=0;
9761     char *p, *q, *r, buf[MSG_SIZ];
9762     if(participants == NULL) return;
9763     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9764     r = p = participants; q = appData.participants;
9765     while(*p && *p == *q) {
9766         if(*p == '\n') r = p+1, nPlayers++;
9767         p++; q++;
9768     }
9769     if(*p) { // difference
9770         while(*p && *p++ != '\n');
9771         while(*q && *q++ != '\n');
9772       changed = nPlayers;
9773         changes = 1 + (strcmp(p, q) != 0);
9774     }
9775     if(changes == 1) { // a single engine mnemonic was changed
9776         q = r; while(*q) nPlayers += (*q++ == '\n');
9777         p = buf; while(*r && (*p = *r++) != '\n') p++;
9778         *p = NULLCHAR;
9779         NamesToList(firstChessProgramNames, command, mnemonic);
9780         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9781         if(mnemonic[i]) { // The substitute is valid
9782             FILE *f;
9783             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9784                 flock(fileno(f), LOCK_EX);
9785                 ParseArgsFromFile(f);
9786                 fseek(f, 0, SEEK_SET);
9787                 FREE(appData.participants); appData.participants = participants;
9788                 if(expunge) { // erase results of replaced engine
9789                     int len = strlen(appData.results), w, b, dummy;
9790                     for(i=0; i<len; i++) {
9791                         Pairing(i, nPlayers, &w, &b, &dummy);
9792                         if((w == changed || b == changed) && appData.results[i] == '*') {
9793                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9794                             fclose(f);
9795                             return;
9796                         }
9797                     }
9798                     for(i=0; i<len; i++) {
9799                         Pairing(i, nPlayers, &w, &b, &dummy);
9800                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9801                     }
9802                 }
9803                 WriteTourneyFile(appData.results, f);
9804                 fclose(f); // release lock
9805                 return;
9806             }
9807         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9808     }
9809     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9810     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9811     free(participants);
9812     return;
9813 }
9814
9815 int
9816 CreateTourney(char *name)
9817 {
9818         FILE *f;
9819         if(matchMode && strcmp(name, appData.tourneyFile)) {
9820              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9821         }
9822         if(name[0] == NULLCHAR) {
9823             if(appData.participants[0])
9824                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9825             return 0;
9826         }
9827         f = fopen(name, "r");
9828         if(f) { // file exists
9829             ASSIGN(appData.tourneyFile, name);
9830             ParseArgsFromFile(f); // parse it
9831         } else {
9832             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9833             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9834                 DisplayError(_("Not enough participants"), 0);
9835                 return 0;
9836             }
9837             ASSIGN(appData.tourneyFile, name);
9838             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9839             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9840         }
9841         fclose(f);
9842         appData.noChessProgram = FALSE;
9843         appData.clockMode = TRUE;
9844         SetGNUMode();
9845         return 1;
9846 }
9847
9848 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9849 {
9850     char buf[MSG_SIZ], *p, *q;
9851     int i=1;
9852     while(*names) {
9853         p = names; q = buf;
9854         while(*p && *p != '\n') *q++ = *p++;
9855         *q = 0;
9856         if(engineList[i]) free(engineList[i]);
9857         engineList[i] = strdup(buf);
9858         if(*p == '\n') p++;
9859         TidyProgramName(engineList[i], "localhost", buf);
9860         if(engineMnemonic[i]) free(engineMnemonic[i]);
9861         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9862             strcat(buf, " (");
9863             sscanf(q + 8, "%s", buf + strlen(buf));
9864             strcat(buf, ")");
9865         }
9866         engineMnemonic[i] = strdup(buf);
9867         names = p; i++;
9868       if(i > MAXENGINES - 2) break;
9869     }
9870     engineList[i] = engineMnemonic[i] = NULL;
9871 }
9872
9873 // following implemented as macro to avoid type limitations
9874 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9875
9876 void SwapEngines(int n)
9877 {   // swap settings for first engine and other engine (so far only some selected options)
9878     int h;
9879     char *p;
9880     if(n == 0) return;
9881     SWAP(directory, p)
9882     SWAP(chessProgram, p)
9883     SWAP(isUCI, h)
9884     SWAP(hasOwnBookUCI, h)
9885     SWAP(protocolVersion, h)
9886     SWAP(reuse, h)
9887     SWAP(scoreIsAbsolute, h)
9888     SWAP(timeOdds, h)
9889     SWAP(logo, p)
9890     SWAP(pgnName, p)
9891     SWAP(pvSAN, h)
9892 }
9893
9894 void
9895 SetPlayer(int player)
9896 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9897     int i;
9898     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9899     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9900     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9901     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9902     if(mnemonic[i]) {
9903         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9904         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9905         ParseArgsFromString(buf);
9906     }
9907     free(engineName);
9908 }
9909
9910 int
9911 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9912 {   // determine players from game number
9913     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9914
9915     if(appData.tourneyType == 0) {
9916         roundsPerCycle = (nPlayers - 1) | 1;
9917         pairingsPerRound = nPlayers / 2;
9918     } else if(appData.tourneyType > 0) {
9919         roundsPerCycle = nPlayers - appData.tourneyType;
9920         pairingsPerRound = appData.tourneyType;
9921     }
9922     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9923     gamesPerCycle = gamesPerRound * roundsPerCycle;
9924     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9925     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9926     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9927     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9928     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9929     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9930
9931     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9932     if(appData.roundSync) *syncInterval = gamesPerRound;
9933
9934     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9935
9936     if(appData.tourneyType == 0) {
9937         if(curPairing == (nPlayers-1)/2 ) {
9938             *whitePlayer = curRound;
9939             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9940         } else {
9941             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9942             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9943             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9944             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9945         }
9946     } else if(appData.tourneyType > 0) {
9947         *whitePlayer = curPairing;
9948         *blackPlayer = curRound + appData.tourneyType;
9949     }
9950
9951     // take care of white/black alternation per round. 
9952     // For cycles and games this is already taken care of by default, derived from matchGame!
9953     return curRound & 1;
9954 }
9955
9956 int
9957 NextTourneyGame(int nr, int *swapColors)
9958 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9959     char *p, *q;
9960     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9961     FILE *tf;
9962     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9963     tf = fopen(appData.tourneyFile, "r");
9964     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9965     ParseArgsFromFile(tf); fclose(tf);
9966     InitTimeControls(); // TC might be altered from tourney file
9967
9968     nPlayers = CountPlayers(appData.participants); // count participants
9969     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9970     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9971
9972     if(syncInterval) {
9973         p = q = appData.results;
9974         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9975         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9976             DisplayMessage(_("Waiting for other game(s)"),"");
9977             waitingForGame = TRUE;
9978             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9979             return 0;
9980         }
9981         waitingForGame = FALSE;
9982     }
9983
9984     if(appData.tourneyType < 0) {
9985         if(nr>=0 && !pairingReceived) {
9986             char buf[1<<16];
9987             if(pairing.pr == NoProc) {
9988                 if(!appData.pairingEngine[0]) {
9989                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9990                     return 0;
9991                 }
9992                 StartChessProgram(&pairing); // starts the pairing engine
9993             }
9994             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9995             SendToProgram(buf, &pairing);
9996             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9997             SendToProgram(buf, &pairing);
9998             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9999         }
10000         pairingReceived = 0;                              // ... so we continue here 
10001         *swapColors = 0;
10002         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10003         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10004         matchGame = 1; roundNr = nr / syncInterval + 1;
10005     }
10006
10007     if(first.pr != NoProc) return 1; // engines already loaded
10008
10009     // redefine engines, engine dir, etc.
10010     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10011     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10012     SwapEngines(1);
10013     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10014     SwapEngines(1);         // and make that valid for second engine by swapping
10015     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10016     InitEngine(&second, 1);
10017     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10018     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10019     return 1;
10020 }
10021
10022 void
10023 NextMatchGame()
10024 {   // performs game initialization that does not invoke engines, and then tries to start the game
10025     int firstWhite, swapColors = 0;
10026     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10027     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10028     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10029     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10030     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10031     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10032     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10033     Reset(FALSE, first.pr != NoProc);
10034     appData.noChessProgram = FALSE;
10035     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
10036     TwoMachinesEvent();
10037 }
10038
10039 void UserAdjudicationEvent( int result )
10040 {
10041     ChessMove gameResult = GameIsDrawn;
10042
10043     if( result > 0 ) {
10044         gameResult = WhiteWins;
10045     }
10046     else if( result < 0 ) {
10047         gameResult = BlackWins;
10048     }
10049
10050     if( gameMode == TwoMachinesPlay ) {
10051         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10052     }
10053 }
10054
10055
10056 // [HGM] save: calculate checksum of game to make games easily identifiable
10057 int StringCheckSum(char *s)
10058 {
10059         int i = 0;
10060         if(s==NULL) return 0;
10061         while(*s) i = i*259 + *s++;
10062         return i;
10063 }
10064
10065 int GameCheckSum()
10066 {
10067         int i, sum=0;
10068         for(i=backwardMostMove; i<forwardMostMove; i++) {
10069                 sum += pvInfoList[i].depth;
10070                 sum += StringCheckSum(parseList[i]);
10071                 sum += StringCheckSum(commentList[i]);
10072                 sum *= 261;
10073         }
10074         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10075         return sum + StringCheckSum(commentList[i]);
10076 } // end of save patch
10077
10078 void
10079 GameEnds(result, resultDetails, whosays)
10080      ChessMove result;
10081      char *resultDetails;
10082      int whosays;
10083 {
10084     GameMode nextGameMode;
10085     int isIcsGame;
10086     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10087
10088     if(endingGame) return; /* [HGM] crash: forbid recursion */
10089     endingGame = 1;
10090     if(twoBoards) { // [HGM] dual: switch back to one board
10091         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10092         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10093     }
10094     if (appData.debugMode) {
10095       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10096               result, resultDetails ? resultDetails : "(null)", whosays);
10097     }
10098
10099     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10100
10101     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10102         /* If we are playing on ICS, the server decides when the
10103            game is over, but the engine can offer to draw, claim
10104            a draw, or resign.
10105          */
10106 #if ZIPPY
10107         if (appData.zippyPlay && first.initDone) {
10108             if (result == GameIsDrawn) {
10109                 /* In case draw still needs to be claimed */
10110                 SendToICS(ics_prefix);
10111                 SendToICS("draw\n");
10112             } else if (StrCaseStr(resultDetails, "resign")) {
10113                 SendToICS(ics_prefix);
10114                 SendToICS("resign\n");
10115             }
10116         }
10117 #endif
10118         endingGame = 0; /* [HGM] crash */
10119         return;
10120     }
10121
10122     /* If we're loading the game from a file, stop */
10123     if (whosays == GE_FILE) {
10124       (void) StopLoadGameTimer();
10125       gameFileFP = NULL;
10126     }
10127
10128     /* Cancel draw offers */
10129     first.offeredDraw = second.offeredDraw = 0;
10130
10131     /* If this is an ICS game, only ICS can really say it's done;
10132        if not, anyone can. */
10133     isIcsGame = (gameMode == IcsPlayingWhite ||
10134                  gameMode == IcsPlayingBlack ||
10135                  gameMode == IcsObserving    ||
10136                  gameMode == IcsExamining);
10137
10138     if (!isIcsGame || whosays == GE_ICS) {
10139         /* OK -- not an ICS game, or ICS said it was done */
10140         StopClocks();
10141         if (!isIcsGame && !appData.noChessProgram)
10142           SetUserThinkingEnables();
10143
10144         /* [HGM] if a machine claims the game end we verify this claim */
10145         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10146             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10147                 char claimer;
10148                 ChessMove trueResult = (ChessMove) -1;
10149
10150                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10151                                             first.twoMachinesColor[0] :
10152                                             second.twoMachinesColor[0] ;
10153
10154                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10155                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10156                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10157                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10158                 } else
10159                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10160                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10161                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10162                 } else
10163                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10164                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10165                 }
10166
10167                 // now verify win claims, but not in drop games, as we don't understand those yet
10168                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10169                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10170                     (result == WhiteWins && claimer == 'w' ||
10171                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10172                       if (appData.debugMode) {
10173                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10174                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10175                       }
10176                       if(result != trueResult) {
10177                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10178                               result = claimer == 'w' ? BlackWins : WhiteWins;
10179                               resultDetails = buf;
10180                       }
10181                 } else
10182                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10183                     && (forwardMostMove <= backwardMostMove ||
10184                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10185                         (claimer=='b')==(forwardMostMove&1))
10186                                                                                   ) {
10187                       /* [HGM] verify: draws that were not flagged are false claims */
10188                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10189                       result = claimer == 'w' ? BlackWins : WhiteWins;
10190                       resultDetails = buf;
10191                 }
10192                 /* (Claiming a loss is accepted no questions asked!) */
10193             }
10194             /* [HGM] bare: don't allow bare King to win */
10195             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10196                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10197                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10198                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10199                && result != GameIsDrawn)
10200             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10201                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10202                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10203                         if(p >= 0 && p <= (int)WhiteKing) k++;
10204                 }
10205                 if (appData.debugMode) {
10206                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10207                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10208                 }
10209                 if(k <= 1) {
10210                         result = GameIsDrawn;
10211                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10212                         resultDetails = buf;
10213                 }
10214             }
10215         }
10216
10217
10218         if(serverMoves != NULL && !loadFlag) { char c = '=';
10219             if(result==WhiteWins) c = '+';
10220             if(result==BlackWins) c = '-';
10221             if(resultDetails != NULL)
10222                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10223         }
10224         if (resultDetails != NULL) {
10225             gameInfo.result = result;
10226             gameInfo.resultDetails = StrSave(resultDetails);
10227
10228             /* display last move only if game was not loaded from file */
10229             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10230                 DisplayMove(currentMove - 1);
10231
10232             if (forwardMostMove != 0) {
10233                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10234                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10235                                                                 ) {
10236                     if (*appData.saveGameFile != NULLCHAR) {
10237                         SaveGameToFile(appData.saveGameFile, TRUE);
10238                     } else if (appData.autoSaveGames) {
10239                         AutoSaveGame();
10240                     }
10241                     if (*appData.savePositionFile != NULLCHAR) {
10242                         SavePositionToFile(appData.savePositionFile);
10243                     }
10244                 }
10245             }
10246
10247             /* Tell program how game ended in case it is learning */
10248             /* [HGM] Moved this to after saving the PGN, just in case */
10249             /* engine died and we got here through time loss. In that */
10250             /* case we will get a fatal error writing the pipe, which */
10251             /* would otherwise lose us the PGN.                       */
10252             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10253             /* output during GameEnds should never be fatal anymore   */
10254             if (gameMode == MachinePlaysWhite ||
10255                 gameMode == MachinePlaysBlack ||
10256                 gameMode == TwoMachinesPlay ||
10257                 gameMode == IcsPlayingWhite ||
10258                 gameMode == IcsPlayingBlack ||
10259                 gameMode == BeginningOfGame) {
10260                 char buf[MSG_SIZ];
10261                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10262                         resultDetails);
10263                 if (first.pr != NoProc) {
10264                     SendToProgram(buf, &first);
10265                 }
10266                 if (second.pr != NoProc &&
10267                     gameMode == TwoMachinesPlay) {
10268                     SendToProgram(buf, &second);
10269                 }
10270             }
10271         }
10272
10273         if (appData.icsActive) {
10274             if (appData.quietPlay &&
10275                 (gameMode == IcsPlayingWhite ||
10276                  gameMode == IcsPlayingBlack)) {
10277                 SendToICS(ics_prefix);
10278                 SendToICS("set shout 1\n");
10279             }
10280             nextGameMode = IcsIdle;
10281             ics_user_moved = FALSE;
10282             /* clean up premove.  It's ugly when the game has ended and the
10283              * premove highlights are still on the board.
10284              */
10285             if (gotPremove) {
10286               gotPremove = FALSE;
10287               ClearPremoveHighlights();
10288               DrawPosition(FALSE, boards[currentMove]);
10289             }
10290             if (whosays == GE_ICS) {
10291                 switch (result) {
10292                 case WhiteWins:
10293                     if (gameMode == IcsPlayingWhite)
10294                         PlayIcsWinSound();
10295                     else if(gameMode == IcsPlayingBlack)
10296                         PlayIcsLossSound();
10297                     break;
10298                 case BlackWins:
10299                     if (gameMode == IcsPlayingBlack)
10300                         PlayIcsWinSound();
10301                     else if(gameMode == IcsPlayingWhite)
10302                         PlayIcsLossSound();
10303                     break;
10304                 case GameIsDrawn:
10305                     PlayIcsDrawSound();
10306                     break;
10307                 default:
10308                     PlayIcsUnfinishedSound();
10309                 }
10310             }
10311         } else if (gameMode == EditGame ||
10312                    gameMode == PlayFromGameFile ||
10313                    gameMode == AnalyzeMode ||
10314                    gameMode == AnalyzeFile) {
10315             nextGameMode = gameMode;
10316         } else {
10317             nextGameMode = EndOfGame;
10318         }
10319         pausing = FALSE;
10320         ModeHighlight();
10321     } else {
10322         nextGameMode = gameMode;
10323     }
10324
10325     if (appData.noChessProgram) {
10326         gameMode = nextGameMode;
10327         ModeHighlight();
10328         endingGame = 0; /* [HGM] crash */
10329         return;
10330     }
10331
10332     if (first.reuse) {
10333         /* Put first chess program into idle state */
10334         if (first.pr != NoProc &&
10335             (gameMode == MachinePlaysWhite ||
10336              gameMode == MachinePlaysBlack ||
10337              gameMode == TwoMachinesPlay ||
10338              gameMode == IcsPlayingWhite ||
10339              gameMode == IcsPlayingBlack ||
10340              gameMode == BeginningOfGame)) {
10341             SendToProgram("force\n", &first);
10342             if (first.usePing) {
10343               char buf[MSG_SIZ];
10344               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10345               SendToProgram(buf, &first);
10346             }
10347         }
10348     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10349         /* Kill off first chess program */
10350         if (first.isr != NULL)
10351           RemoveInputSource(first.isr);
10352         first.isr = NULL;
10353
10354         if (first.pr != NoProc) {
10355             ExitAnalyzeMode();
10356             DoSleep( appData.delayBeforeQuit );
10357             SendToProgram("quit\n", &first);
10358             DoSleep( appData.delayAfterQuit );
10359             DestroyChildProcess(first.pr, first.useSigterm);
10360         }
10361         first.pr = NoProc;
10362     }
10363     if (second.reuse) {
10364         /* Put second chess program into idle state */
10365         if (second.pr != NoProc &&
10366             gameMode == TwoMachinesPlay) {
10367             SendToProgram("force\n", &second);
10368             if (second.usePing) {
10369               char buf[MSG_SIZ];
10370               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10371               SendToProgram(buf, &second);
10372             }
10373         }
10374     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10375         /* Kill off second chess program */
10376         if (second.isr != NULL)
10377           RemoveInputSource(second.isr);
10378         second.isr = NULL;
10379
10380         if (second.pr != NoProc) {
10381             DoSleep( appData.delayBeforeQuit );
10382             SendToProgram("quit\n", &second);
10383             DoSleep( appData.delayAfterQuit );
10384             DestroyChildProcess(second.pr, second.useSigterm);
10385         }
10386         second.pr = NoProc;
10387     }
10388
10389     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10390         char resChar = '=';
10391         switch (result) {
10392         case WhiteWins:
10393           resChar = '+';
10394           if (first.twoMachinesColor[0] == 'w') {
10395             first.matchWins++;
10396           } else {
10397             second.matchWins++;
10398           }
10399           break;
10400         case BlackWins:
10401           resChar = '-';
10402           if (first.twoMachinesColor[0] == 'b') {
10403             first.matchWins++;
10404           } else {
10405             second.matchWins++;
10406           }
10407           break;
10408         case GameUnfinished:
10409           resChar = ' ';
10410         default:
10411           break;
10412         }
10413
10414         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10415         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10416             ReserveGame(nextGame, resChar); // sets nextGame
10417             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10418             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10419         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10420
10421         if (nextGame <= appData.matchGames && !abortMatch) {
10422             gameMode = nextGameMode;
10423             matchGame = nextGame; // this will be overruled in tourney mode!
10424             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10425             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10426             endingGame = 0; /* [HGM] crash */
10427             return;
10428         } else {
10429             gameMode = nextGameMode;
10430             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10431                      first.tidy, second.tidy,
10432                      first.matchWins, second.matchWins,
10433                      appData.matchGames - (first.matchWins + second.matchWins));
10434             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10435             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10436             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10437                 first.twoMachinesColor = "black\n";
10438                 second.twoMachinesColor = "white\n";
10439             } else {
10440                 first.twoMachinesColor = "white\n";
10441                 second.twoMachinesColor = "black\n";
10442             }
10443         }
10444     }
10445     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10446         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10447       ExitAnalyzeMode();
10448     gameMode = nextGameMode;
10449     ModeHighlight();
10450     endingGame = 0;  /* [HGM] crash */
10451     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10452         if(matchMode == TRUE) { // match through command line: exit with or without popup
10453             if(ranking) {
10454                 ToNrEvent(forwardMostMove);
10455                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10456                 else ExitEvent(0);
10457             } else DisplayFatalError(buf, 0, 0);
10458         } else { // match through menu; just stop, with or without popup
10459             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10460             ModeHighlight();
10461             if(ranking){
10462                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10463             } else DisplayNote(buf);
10464       }
10465       if(ranking) free(ranking);
10466     }
10467 }
10468
10469 /* Assumes program was just initialized (initString sent).
10470    Leaves program in force mode. */
10471 void
10472 FeedMovesToProgram(cps, upto)
10473      ChessProgramState *cps;
10474      int upto;
10475 {
10476     int i;
10477
10478     if (appData.debugMode)
10479       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10480               startedFromSetupPosition ? "position and " : "",
10481               backwardMostMove, upto, cps->which);
10482     if(currentlyInitializedVariant != gameInfo.variant) {
10483       char buf[MSG_SIZ];
10484         // [HGM] variantswitch: make engine aware of new variant
10485         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10486                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10487         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10488         SendToProgram(buf, cps);
10489         currentlyInitializedVariant = gameInfo.variant;
10490     }
10491     SendToProgram("force\n", cps);
10492     if (startedFromSetupPosition) {
10493         SendBoard(cps, backwardMostMove);
10494     if (appData.debugMode) {
10495         fprintf(debugFP, "feedMoves\n");
10496     }
10497     }
10498     for (i = backwardMostMove; i < upto; i++) {
10499         SendMoveToProgram(i, cps);
10500     }
10501 }
10502
10503
10504 int
10505 ResurrectChessProgram()
10506 {
10507      /* The chess program may have exited.
10508         If so, restart it and feed it all the moves made so far. */
10509     static int doInit = 0;
10510
10511     if (appData.noChessProgram) return 1;
10512
10513     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10514         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10515         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10516         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10517     } else {
10518         if (first.pr != NoProc) return 1;
10519         StartChessProgram(&first);
10520     }
10521     InitChessProgram(&first, FALSE);
10522     FeedMovesToProgram(&first, currentMove);
10523
10524     if (!first.sendTime) {
10525         /* can't tell gnuchess what its clock should read,
10526            so we bow to its notion. */
10527         ResetClocks();
10528         timeRemaining[0][currentMove] = whiteTimeRemaining;
10529         timeRemaining[1][currentMove] = blackTimeRemaining;
10530     }
10531
10532     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10533                 appData.icsEngineAnalyze) && first.analysisSupport) {
10534       SendToProgram("analyze\n", &first);
10535       first.analyzing = TRUE;
10536     }
10537     return 1;
10538 }
10539
10540 /*
10541  * Button procedures
10542  */
10543 void
10544 Reset(redraw, init)
10545      int redraw, init;
10546 {
10547     int i;
10548
10549     if (appData.debugMode) {
10550         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10551                 redraw, init, gameMode);
10552     }
10553     CleanupTail(); // [HGM] vari: delete any stored variations
10554     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10555     pausing = pauseExamInvalid = FALSE;
10556     startedFromSetupPosition = blackPlaysFirst = FALSE;
10557     firstMove = TRUE;
10558     whiteFlag = blackFlag = FALSE;
10559     userOfferedDraw = FALSE;
10560     hintRequested = bookRequested = FALSE;
10561     first.maybeThinking = FALSE;
10562     second.maybeThinking = FALSE;
10563     first.bookSuspend = FALSE; // [HGM] book
10564     second.bookSuspend = FALSE;
10565     thinkOutput[0] = NULLCHAR;
10566     lastHint[0] = NULLCHAR;
10567     ClearGameInfo(&gameInfo);
10568     gameInfo.variant = StringToVariant(appData.variant);
10569     ics_user_moved = ics_clock_paused = FALSE;
10570     ics_getting_history = H_FALSE;
10571     ics_gamenum = -1;
10572     white_holding[0] = black_holding[0] = NULLCHAR;
10573     ClearProgramStats();
10574     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10575
10576     ResetFrontEnd();
10577     ClearHighlights();
10578     flipView = appData.flipView;
10579     ClearPremoveHighlights();
10580     gotPremove = FALSE;
10581     alarmSounded = FALSE;
10582
10583     GameEnds(EndOfFile, NULL, GE_PLAYER);
10584     if(appData.serverMovesName != NULL) {
10585         /* [HGM] prepare to make moves file for broadcasting */
10586         clock_t t = clock();
10587         if(serverMoves != NULL) fclose(serverMoves);
10588         serverMoves = fopen(appData.serverMovesName, "r");
10589         if(serverMoves != NULL) {
10590             fclose(serverMoves);
10591             /* delay 15 sec before overwriting, so all clients can see end */
10592             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10593         }
10594         serverMoves = fopen(appData.serverMovesName, "w");
10595     }
10596
10597     ExitAnalyzeMode();
10598     gameMode = BeginningOfGame;
10599     ModeHighlight();
10600     if(appData.icsActive) gameInfo.variant = VariantNormal;
10601     currentMove = forwardMostMove = backwardMostMove = 0;
10602     InitPosition(redraw);
10603     for (i = 0; i < MAX_MOVES; i++) {
10604         if (commentList[i] != NULL) {
10605             free(commentList[i]);
10606             commentList[i] = NULL;
10607         }
10608     }
10609     ResetClocks();
10610     timeRemaining[0][0] = whiteTimeRemaining;
10611     timeRemaining[1][0] = blackTimeRemaining;
10612
10613     if (first.pr == NULL) {
10614         StartChessProgram(&first);
10615     }
10616     if (init) {
10617             InitChessProgram(&first, startedFromSetupPosition);
10618     }
10619     DisplayTitle("");
10620     DisplayMessage("", "");
10621     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10622     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10623 }
10624
10625 void
10626 AutoPlayGameLoop()
10627 {
10628     for (;;) {
10629         if (!AutoPlayOneMove())
10630           return;
10631         if (matchMode || appData.timeDelay == 0)
10632           continue;
10633         if (appData.timeDelay < 0)
10634           return;
10635         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10636         break;
10637     }
10638 }
10639
10640
10641 int
10642 AutoPlayOneMove()
10643 {
10644     int fromX, fromY, toX, toY;
10645
10646     if (appData.debugMode) {
10647       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10648     }
10649
10650     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10651       return FALSE;
10652
10653     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10654       pvInfoList[currentMove].depth = programStats.depth;
10655       pvInfoList[currentMove].score = programStats.score;
10656       pvInfoList[currentMove].time  = 0;
10657       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10658     }
10659
10660     if (currentMove >= forwardMostMove) {
10661       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10662 //      gameMode = EndOfGame;
10663 //      ModeHighlight();
10664
10665       /* [AS] Clear current move marker at the end of a game */
10666       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10667
10668       return FALSE;
10669     }
10670
10671     toX = moveList[currentMove][2] - AAA;
10672     toY = moveList[currentMove][3] - ONE;
10673
10674     if (moveList[currentMove][1] == '@') {
10675         if (appData.highlightLastMove) {
10676             SetHighlights(-1, -1, toX, toY);
10677         }
10678     } else {
10679         fromX = moveList[currentMove][0] - AAA;
10680         fromY = moveList[currentMove][1] - ONE;
10681
10682         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10683
10684         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10685
10686         if (appData.highlightLastMove) {
10687             SetHighlights(fromX, fromY, toX, toY);
10688         }
10689     }
10690     DisplayMove(currentMove);
10691     SendMoveToProgram(currentMove++, &first);
10692     DisplayBothClocks();
10693     DrawPosition(FALSE, boards[currentMove]);
10694     // [HGM] PV info: always display, routine tests if empty
10695     DisplayComment(currentMove - 1, commentList[currentMove]);
10696     return TRUE;
10697 }
10698
10699
10700 int
10701 LoadGameOneMove(readAhead)
10702      ChessMove readAhead;
10703 {
10704     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10705     char promoChar = NULLCHAR;
10706     ChessMove moveType;
10707     char move[MSG_SIZ];
10708     char *p, *q;
10709
10710     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10711         gameMode != AnalyzeMode && gameMode != Training) {
10712         gameFileFP = NULL;
10713         return FALSE;
10714     }
10715
10716     yyboardindex = forwardMostMove;
10717     if (readAhead != EndOfFile) {
10718       moveType = readAhead;
10719     } else {
10720       if (gameFileFP == NULL)
10721           return FALSE;
10722       moveType = (ChessMove) Myylex();
10723     }
10724
10725     done = FALSE;
10726     switch (moveType) {
10727       case Comment:
10728         if (appData.debugMode)
10729           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10730         p = yy_text;
10731
10732         /* append the comment but don't display it */
10733         AppendComment(currentMove, p, FALSE);
10734         return TRUE;
10735
10736       case WhiteCapturesEnPassant:
10737       case BlackCapturesEnPassant:
10738       case WhitePromotion:
10739       case BlackPromotion:
10740       case WhiteNonPromotion:
10741       case BlackNonPromotion:
10742       case NormalMove:
10743       case WhiteKingSideCastle:
10744       case WhiteQueenSideCastle:
10745       case BlackKingSideCastle:
10746       case BlackQueenSideCastle:
10747       case WhiteKingSideCastleWild:
10748       case WhiteQueenSideCastleWild:
10749       case BlackKingSideCastleWild:
10750       case BlackQueenSideCastleWild:
10751       /* PUSH Fabien */
10752       case WhiteHSideCastleFR:
10753       case WhiteASideCastleFR:
10754       case BlackHSideCastleFR:
10755       case BlackASideCastleFR:
10756       /* POP Fabien */
10757         if (appData.debugMode)
10758           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10759         fromX = currentMoveString[0] - AAA;
10760         fromY = currentMoveString[1] - ONE;
10761         toX = currentMoveString[2] - AAA;
10762         toY = currentMoveString[3] - ONE;
10763         promoChar = currentMoveString[4];
10764         break;
10765
10766       case WhiteDrop:
10767       case BlackDrop:
10768         if (appData.debugMode)
10769           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10770         fromX = moveType == WhiteDrop ?
10771           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10772         (int) CharToPiece(ToLower(currentMoveString[0]));
10773         fromY = DROP_RANK;
10774         toX = currentMoveString[2] - AAA;
10775         toY = currentMoveString[3] - ONE;
10776         break;
10777
10778       case WhiteWins:
10779       case BlackWins:
10780       case GameIsDrawn:
10781       case GameUnfinished:
10782         if (appData.debugMode)
10783           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10784         p = strchr(yy_text, '{');
10785         if (p == NULL) p = strchr(yy_text, '(');
10786         if (p == NULL) {
10787             p = yy_text;
10788             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10789         } else {
10790             q = strchr(p, *p == '{' ? '}' : ')');
10791             if (q != NULL) *q = NULLCHAR;
10792             p++;
10793         }
10794         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10795         GameEnds(moveType, p, GE_FILE);
10796         done = TRUE;
10797         if (cmailMsgLoaded) {
10798             ClearHighlights();
10799             flipView = WhiteOnMove(currentMove);
10800             if (moveType == GameUnfinished) flipView = !flipView;
10801             if (appData.debugMode)
10802               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10803         }
10804         break;
10805
10806       case EndOfFile:
10807         if (appData.debugMode)
10808           fprintf(debugFP, "Parser hit end of file\n");
10809         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10810           case MT_NONE:
10811           case MT_CHECK:
10812             break;
10813           case MT_CHECKMATE:
10814           case MT_STAINMATE:
10815             if (WhiteOnMove(currentMove)) {
10816                 GameEnds(BlackWins, "Black mates", GE_FILE);
10817             } else {
10818                 GameEnds(WhiteWins, "White mates", GE_FILE);
10819             }
10820             break;
10821           case MT_STALEMATE:
10822             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10823             break;
10824         }
10825         done = TRUE;
10826         break;
10827
10828       case MoveNumberOne:
10829         if (lastLoadGameStart == GNUChessGame) {
10830             /* GNUChessGames have numbers, but they aren't move numbers */
10831             if (appData.debugMode)
10832               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10833                       yy_text, (int) moveType);
10834             return LoadGameOneMove(EndOfFile); /* tail recursion */
10835         }
10836         /* else fall thru */
10837
10838       case XBoardGame:
10839       case GNUChessGame:
10840       case PGNTag:
10841         /* Reached start of next game in file */
10842         if (appData.debugMode)
10843           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10844         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10845           case MT_NONE:
10846           case MT_CHECK:
10847             break;
10848           case MT_CHECKMATE:
10849           case MT_STAINMATE:
10850             if (WhiteOnMove(currentMove)) {
10851                 GameEnds(BlackWins, "Black mates", GE_FILE);
10852             } else {
10853                 GameEnds(WhiteWins, "White mates", GE_FILE);
10854             }
10855             break;
10856           case MT_STALEMATE:
10857             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10858             break;
10859         }
10860         done = TRUE;
10861         break;
10862
10863       case PositionDiagram:     /* should not happen; ignore */
10864       case ElapsedTime:         /* ignore */
10865       case NAG:                 /* ignore */
10866         if (appData.debugMode)
10867           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10868                   yy_text, (int) moveType);
10869         return LoadGameOneMove(EndOfFile); /* tail recursion */
10870
10871       case IllegalMove:
10872         if (appData.testLegality) {
10873             if (appData.debugMode)
10874               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10875             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10876                     (forwardMostMove / 2) + 1,
10877                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10878             DisplayError(move, 0);
10879             done = TRUE;
10880         } else {
10881             if (appData.debugMode)
10882               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10883                       yy_text, currentMoveString);
10884             fromX = currentMoveString[0] - AAA;
10885             fromY = currentMoveString[1] - ONE;
10886             toX = currentMoveString[2] - AAA;
10887             toY = currentMoveString[3] - ONE;
10888             promoChar = currentMoveString[4];
10889         }
10890         break;
10891
10892       case AmbiguousMove:
10893         if (appData.debugMode)
10894           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10895         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10896                 (forwardMostMove / 2) + 1,
10897                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10898         DisplayError(move, 0);
10899         done = TRUE;
10900         break;
10901
10902       default:
10903       case ImpossibleMove:
10904         if (appData.debugMode)
10905           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10906         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10907                 (forwardMostMove / 2) + 1,
10908                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10909         DisplayError(move, 0);
10910         done = TRUE;
10911         break;
10912     }
10913
10914     if (done) {
10915         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10916             DrawPosition(FALSE, boards[currentMove]);
10917             DisplayBothClocks();
10918             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10919               DisplayComment(currentMove - 1, commentList[currentMove]);
10920         }
10921         (void) StopLoadGameTimer();
10922         gameFileFP = NULL;
10923         cmailOldMove = forwardMostMove;
10924         return FALSE;
10925     } else {
10926         /* currentMoveString is set as a side-effect of yylex */
10927
10928         thinkOutput[0] = NULLCHAR;
10929         MakeMove(fromX, fromY, toX, toY, promoChar);
10930         currentMove = forwardMostMove;
10931         return TRUE;
10932     }
10933 }
10934
10935 /* Load the nth game from the given file */
10936 int
10937 LoadGameFromFile(filename, n, title, useList)
10938      char *filename;
10939      int n;
10940      char *title;
10941      /*Boolean*/ int useList;
10942 {
10943     FILE *f;
10944     char buf[MSG_SIZ];
10945
10946     if (strcmp(filename, "-") == 0) {
10947         f = stdin;
10948         title = "stdin";
10949     } else {
10950         f = fopen(filename, "rb");
10951         if (f == NULL) {
10952           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10953             DisplayError(buf, errno);
10954             return FALSE;
10955         }
10956     }
10957     if (fseek(f, 0, 0) == -1) {
10958         /* f is not seekable; probably a pipe */
10959         useList = FALSE;
10960     }
10961     if (useList && n == 0) {
10962         int error = GameListBuild(f);
10963         if (error) {
10964             DisplayError(_("Cannot build game list"), error);
10965         } else if (!ListEmpty(&gameList) &&
10966                    ((ListGame *) gameList.tailPred)->number > 1) {
10967             GameListPopUp(f, title);
10968             return TRUE;
10969         }
10970         GameListDestroy();
10971         n = 1;
10972     }
10973     if (n == 0) n = 1;
10974     return LoadGame(f, n, title, FALSE);
10975 }
10976
10977
10978 void
10979 MakeRegisteredMove()
10980 {
10981     int fromX, fromY, toX, toY;
10982     char promoChar;
10983     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10984         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10985           case CMAIL_MOVE:
10986           case CMAIL_DRAW:
10987             if (appData.debugMode)
10988               fprintf(debugFP, "Restoring %s for game %d\n",
10989                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10990
10991             thinkOutput[0] = NULLCHAR;
10992             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10993             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10994             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10995             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10996             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10997             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10998             MakeMove(fromX, fromY, toX, toY, promoChar);
10999             ShowMove(fromX, fromY, toX, toY);
11000
11001             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11002               case MT_NONE:
11003               case MT_CHECK:
11004                 break;
11005
11006               case MT_CHECKMATE:
11007               case MT_STAINMATE:
11008                 if (WhiteOnMove(currentMove)) {
11009                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11010                 } else {
11011                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11012                 }
11013                 break;
11014
11015               case MT_STALEMATE:
11016                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11017                 break;
11018             }
11019
11020             break;
11021
11022           case CMAIL_RESIGN:
11023             if (WhiteOnMove(currentMove)) {
11024                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11025             } else {
11026                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11027             }
11028             break;
11029
11030           case CMAIL_ACCEPT:
11031             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11032             break;
11033
11034           default:
11035             break;
11036         }
11037     }
11038
11039     return;
11040 }
11041
11042 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11043 int
11044 CmailLoadGame(f, gameNumber, title, useList)
11045      FILE *f;
11046      int gameNumber;
11047      char *title;
11048      int useList;
11049 {
11050     int retVal;
11051
11052     if (gameNumber > nCmailGames) {
11053         DisplayError(_("No more games in this message"), 0);
11054         return FALSE;
11055     }
11056     if (f == lastLoadGameFP) {
11057         int offset = gameNumber - lastLoadGameNumber;
11058         if (offset == 0) {
11059             cmailMsg[0] = NULLCHAR;
11060             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11061                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11062                 nCmailMovesRegistered--;
11063             }
11064             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11065             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11066                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11067             }
11068         } else {
11069             if (! RegisterMove()) return FALSE;
11070         }
11071     }
11072
11073     retVal = LoadGame(f, gameNumber, title, useList);
11074
11075     /* Make move registered during previous look at this game, if any */
11076     MakeRegisteredMove();
11077
11078     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11079         commentList[currentMove]
11080           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11081         DisplayComment(currentMove - 1, commentList[currentMove]);
11082     }
11083
11084     return retVal;
11085 }
11086
11087 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11088 int
11089 ReloadGame(offset)
11090      int offset;
11091 {
11092     int gameNumber = lastLoadGameNumber + offset;
11093     if (lastLoadGameFP == NULL) {
11094         DisplayError(_("No game has been loaded yet"), 0);
11095         return FALSE;
11096     }
11097     if (gameNumber <= 0) {
11098         DisplayError(_("Can't back up any further"), 0);
11099         return FALSE;
11100     }
11101     if (cmailMsgLoaded) {
11102         return CmailLoadGame(lastLoadGameFP, gameNumber,
11103                              lastLoadGameTitle, lastLoadGameUseList);
11104     } else {
11105         return LoadGame(lastLoadGameFP, gameNumber,
11106                         lastLoadGameTitle, lastLoadGameUseList);
11107     }
11108 }
11109
11110 int keys[EmptySquare+1];
11111
11112 int
11113 PositionMatches(Board b1, Board b2)
11114 {
11115     int r, f, sum=0;
11116     switch(appData.searchMode) {
11117         case 1: return CompareWithRights(b1, b2);
11118         case 2:
11119             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11120                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11121             }
11122             return TRUE;
11123         case 3:
11124             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11125               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11126                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11127             }
11128             return sum==0;
11129         case 4:
11130             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11131                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11132             }
11133             return sum==0;
11134     }
11135     return TRUE;
11136 }
11137
11138 GameInfo dummyInfo;
11139
11140 int GameContainsPosition(FILE *f, ListGame *lg)
11141 {
11142     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11143     int fromX, fromY, toX, toY;
11144     char promoChar;
11145     static int initDone=FALSE;
11146
11147     if(!initDone) {
11148         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11149         initDone = TRUE;
11150     }
11151     dummyInfo.variant = VariantNormal;
11152     FREE(dummyInfo.fen); dummyInfo.fen = NULL;
11153     dummyInfo.whiteRating = 0;
11154     dummyInfo.blackRating = 0;
11155     FREE(dummyInfo.date); dummyInfo.date = NULL;
11156     fseek(f, lg->offset, 0);
11157     yynewfile(f);
11158     CopyBoard(boards[scratch], initialPosition); // default start position
11159     while(1) {
11160         yyboardindex = scratch + (plyNr&1);
11161       quickFlag = 1;
11162         next = Myylex();
11163       quickFlag = 0;
11164         switch(next) {
11165             case PGNTag:
11166                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11167 #if 0
11168                 ParsePGNTag(yy_text, &dummyInfo); // this has a bad memory leak...
11169                 if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL;
11170 #else
11171                 // do it ourselves avoiding malloc
11172                 { char *p = yy_text+1, *q;
11173                   while(!isdigit(*p) && !isalpha(*p)) p++;
11174                   q  = p; while(*p != ' ' && *p != '\t' && *p != '\n') p++;
11175                   *p = NULLCHAR;
11176                   if(!StrCaseCmp(q, "Date") && (p = strchr(p+1, '"'))) { if(atoi(p+1) < appData.dateThreshold) return -1; } else
11177                   if(!StrCaseCmp(q, "Variant")  &&  (p = strchr(p+1, '"'))) dummyInfo.variant = StringToVariant(p+1); else
11178                   if(!StrCaseCmp(q, "WhiteElo")  && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11179                   if(!StrCaseCmp(q, "BlackElo")  && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11180                   if(!StrCaseCmp(q, "WhiteUSCF") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11181                   if(!StrCaseCmp(q, "BlackUSCF") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11182                   if(!StrCaseCmp(q, "FEN")  && (p = strchr(p+1, '"'))) ParseFEN(boards[scratch], &btm, p+1);
11183                 }
11184 #endif
11185             default:
11186                 continue;
11187
11188             case XBoardGame:
11189             case GNUChessGame:
11190                 if(plyNr) return -1; // after we have seen moves, this is for new game
11191               continue;
11192
11193             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11194             case ImpossibleMove:
11195             case WhiteWins: // game ends here with these four
11196             case BlackWins:
11197             case GameIsDrawn:
11198             case GameUnfinished:
11199                 return -1;
11200
11201             case IllegalMove:
11202                 if(appData.testLegality) return -1;
11203             case WhiteCapturesEnPassant:
11204             case BlackCapturesEnPassant:
11205             case WhitePromotion:
11206             case BlackPromotion:
11207             case WhiteNonPromotion:
11208             case BlackNonPromotion:
11209             case NormalMove:
11210             case WhiteKingSideCastle:
11211             case WhiteQueenSideCastle:
11212             case BlackKingSideCastle:
11213             case BlackQueenSideCastle:
11214             case WhiteKingSideCastleWild:
11215             case WhiteQueenSideCastleWild:
11216             case BlackKingSideCastleWild:
11217             case BlackQueenSideCastleWild:
11218             case WhiteHSideCastleFR:
11219             case WhiteASideCastleFR:
11220             case BlackHSideCastleFR:
11221             case BlackASideCastleFR:
11222                 fromX = currentMoveString[0] - AAA;
11223                 fromY = currentMoveString[1] - ONE;
11224                 toX = currentMoveString[2] - AAA;
11225                 toY = currentMoveString[3] - ONE;
11226                 promoChar = currentMoveString[4];
11227                 break;
11228             case WhiteDrop:
11229             case BlackDrop:
11230                 fromX = next == WhiteDrop ?
11231                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11232                   (int) CharToPiece(ToLower(currentMoveString[0]));
11233                 fromY = DROP_RANK;
11234                 toX = currentMoveString[2] - AAA;
11235                 toY = currentMoveString[3] - ONE;
11236                 break;
11237         }
11238         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11239         if(plyNr == 0) { // but first figure out variant and initial position
11240             if(dummyInfo.variant != gameInfo.variant) return -1; // wrong variant
11241             if(appData.eloThreshold1 && (dummyInfo.whiteRating < appData.eloThreshold1 && dummyInfo.blackRating < appData.eloThreshold1)) return -1;
11242             if(appData.eloThreshold2 && (dummyInfo.whiteRating < appData.eloThreshold2 || dummyInfo.blackRating < appData.eloThreshold2)) return -1;
11243             if(appData.dateThreshold && (!dummyInfo.date || atoi(dummyInfo.date) < appData.dateThreshold)) return -1;
11244             if(btm) CopyBoard(boards[scratch+1], boards[scratch]), plyNr++;
11245             if(PositionMatches(boards[scratch + plyNr], boards[currentMove])) return plyNr;
11246         }
11247         CopyBoard(boards[scratch + (plyNr+1&1)], boards[scratch + (plyNr&1)]);
11248         plyNr++;
11249         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch + (plyNr&1)]);
11250         if(PositionMatches(boards[scratch + (plyNr&1)], boards[currentMove])) return plyNr;
11251     }
11252 }
11253
11254 /* Load the nth game from open file f */
11255 int
11256 LoadGame(f, gameNumber, title, useList)
11257      FILE *f;
11258      int gameNumber;
11259      char *title;
11260      int useList;
11261 {
11262     ChessMove cm;
11263     char buf[MSG_SIZ];
11264     int gn = gameNumber;
11265     ListGame *lg = NULL;
11266     int numPGNTags = 0;
11267     int err, pos = -1;
11268     GameMode oldGameMode;
11269     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11270
11271     if (appData.debugMode)
11272         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11273
11274     if (gameMode == Training )
11275         SetTrainingModeOff();
11276
11277     oldGameMode = gameMode;
11278     if (gameMode != BeginningOfGame) {
11279       Reset(FALSE, TRUE);
11280     }
11281
11282     gameFileFP = f;
11283     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11284         fclose(lastLoadGameFP);
11285     }
11286
11287     if (useList) {
11288         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11289
11290         if (lg) {
11291             fseek(f, lg->offset, 0);
11292             GameListHighlight(gameNumber);
11293             pos = lg->position;
11294             gn = 1;
11295         }
11296         else {
11297             DisplayError(_("Game number out of range"), 0);
11298             return FALSE;
11299         }
11300     } else {
11301         GameListDestroy();
11302         if (fseek(f, 0, 0) == -1) {
11303             if (f == lastLoadGameFP ?
11304                 gameNumber == lastLoadGameNumber + 1 :
11305                 gameNumber == 1) {
11306                 gn = 1;
11307             } else {
11308                 DisplayError(_("Can't seek on game file"), 0);
11309                 return FALSE;
11310             }
11311         }
11312     }
11313     lastLoadGameFP = f;
11314     lastLoadGameNumber = gameNumber;
11315     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11316     lastLoadGameUseList = useList;
11317
11318     yynewfile(f);
11319
11320     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11321       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11322                 lg->gameInfo.black);
11323             DisplayTitle(buf);
11324     } else if (*title != NULLCHAR) {
11325         if (gameNumber > 1) {
11326           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11327             DisplayTitle(buf);
11328         } else {
11329             DisplayTitle(title);
11330         }
11331     }
11332
11333     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11334         gameMode = PlayFromGameFile;
11335         ModeHighlight();
11336     }
11337
11338     currentMove = forwardMostMove = backwardMostMove = 0;
11339     CopyBoard(boards[0], initialPosition);
11340     StopClocks();
11341
11342     /*
11343      * Skip the first gn-1 games in the file.
11344      * Also skip over anything that precedes an identifiable
11345      * start of game marker, to avoid being confused by
11346      * garbage at the start of the file.  Currently
11347      * recognized start of game markers are the move number "1",
11348      * the pattern "gnuchess .* game", the pattern
11349      * "^[#;%] [^ ]* game file", and a PGN tag block.
11350      * A game that starts with one of the latter two patterns
11351      * will also have a move number 1, possibly
11352      * following a position diagram.
11353      * 5-4-02: Let's try being more lenient and allowing a game to
11354      * start with an unnumbered move.  Does that break anything?
11355      */
11356     cm = lastLoadGameStart = EndOfFile;
11357     while (gn > 0) {
11358         yyboardindex = forwardMostMove;
11359         cm = (ChessMove) Myylex();
11360         switch (cm) {
11361           case EndOfFile:
11362             if (cmailMsgLoaded) {
11363                 nCmailGames = CMAIL_MAX_GAMES - gn;
11364             } else {
11365                 Reset(TRUE, TRUE);
11366                 DisplayError(_("Game not found in file"), 0);
11367             }
11368             return FALSE;
11369
11370           case GNUChessGame:
11371           case XBoardGame:
11372             gn--;
11373             lastLoadGameStart = cm;
11374             break;
11375
11376           case MoveNumberOne:
11377             switch (lastLoadGameStart) {
11378               case GNUChessGame:
11379               case XBoardGame:
11380               case PGNTag:
11381                 break;
11382               case MoveNumberOne:
11383               case EndOfFile:
11384                 gn--;           /* count this game */
11385                 lastLoadGameStart = cm;
11386                 break;
11387               default:
11388                 /* impossible */
11389                 break;
11390             }
11391             break;
11392
11393           case PGNTag:
11394             switch (lastLoadGameStart) {
11395               case GNUChessGame:
11396               case PGNTag:
11397               case MoveNumberOne:
11398               case EndOfFile:
11399                 gn--;           /* count this game */
11400                 lastLoadGameStart = cm;
11401                 break;
11402               case XBoardGame:
11403                 lastLoadGameStart = cm; /* game counted already */
11404                 break;
11405               default:
11406                 /* impossible */
11407                 break;
11408             }
11409             if (gn > 0) {
11410                 do {
11411                     yyboardindex = forwardMostMove;
11412                     cm = (ChessMove) Myylex();
11413                 } while (cm == PGNTag || cm == Comment);
11414             }
11415             break;
11416
11417           case WhiteWins:
11418           case BlackWins:
11419           case GameIsDrawn:
11420             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11421                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11422                     != CMAIL_OLD_RESULT) {
11423                     nCmailResults ++ ;
11424                     cmailResult[  CMAIL_MAX_GAMES
11425                                 - gn - 1] = CMAIL_OLD_RESULT;
11426                 }
11427             }
11428             break;
11429
11430           case NormalMove:
11431             /* Only a NormalMove can be at the start of a game
11432              * without a position diagram. */
11433             if (lastLoadGameStart == EndOfFile ) {
11434               gn--;
11435               lastLoadGameStart = MoveNumberOne;
11436             }
11437             break;
11438
11439           default:
11440             break;
11441         }
11442     }
11443
11444     if (appData.debugMode)
11445       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11446
11447     if (cm == XBoardGame) {
11448         /* Skip any header junk before position diagram and/or move 1 */
11449         for (;;) {
11450             yyboardindex = forwardMostMove;
11451             cm = (ChessMove) Myylex();
11452
11453             if (cm == EndOfFile ||
11454                 cm == GNUChessGame || cm == XBoardGame) {
11455                 /* Empty game; pretend end-of-file and handle later */
11456                 cm = EndOfFile;
11457                 break;
11458             }
11459
11460             if (cm == MoveNumberOne || cm == PositionDiagram ||
11461                 cm == PGNTag || cm == Comment)
11462               break;
11463         }
11464     } else if (cm == GNUChessGame) {
11465         if (gameInfo.event != NULL) {
11466             free(gameInfo.event);
11467         }
11468         gameInfo.event = StrSave(yy_text);
11469     }
11470
11471     startedFromSetupPosition = FALSE;
11472     while (cm == PGNTag) {
11473         if (appData.debugMode)
11474           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11475         err = ParsePGNTag(yy_text, &gameInfo);
11476         if (!err) numPGNTags++;
11477
11478         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11479         if(gameInfo.variant != oldVariant) {
11480             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11481             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11482             InitPosition(TRUE);
11483             oldVariant = gameInfo.variant;
11484             if (appData.debugMode)
11485               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11486         }
11487
11488
11489         if (gameInfo.fen != NULL) {
11490           Board initial_position;
11491           startedFromSetupPosition = TRUE;
11492           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11493             Reset(TRUE, TRUE);
11494             DisplayError(_("Bad FEN position in file"), 0);
11495             return FALSE;
11496           }
11497           CopyBoard(boards[0], initial_position);
11498           if (blackPlaysFirst) {
11499             currentMove = forwardMostMove = backwardMostMove = 1;
11500             CopyBoard(boards[1], initial_position);
11501             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11502             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11503             timeRemaining[0][1] = whiteTimeRemaining;
11504             timeRemaining[1][1] = blackTimeRemaining;
11505             if (commentList[0] != NULL) {
11506               commentList[1] = commentList[0];
11507               commentList[0] = NULL;
11508             }
11509           } else {
11510             currentMove = forwardMostMove = backwardMostMove = 0;
11511           }
11512           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11513           {   int i;
11514               initialRulePlies = FENrulePlies;
11515               for( i=0; i< nrCastlingRights; i++ )
11516                   initialRights[i] = initial_position[CASTLING][i];
11517           }
11518           yyboardindex = forwardMostMove;
11519           free(gameInfo.fen);
11520           gameInfo.fen = NULL;
11521         }
11522
11523         yyboardindex = forwardMostMove;
11524         cm = (ChessMove) Myylex();
11525
11526         /* Handle comments interspersed among the tags */
11527         while (cm == Comment) {
11528             char *p;
11529             if (appData.debugMode)
11530               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11531             p = yy_text;
11532             AppendComment(currentMove, p, FALSE);
11533             yyboardindex = forwardMostMove;
11534             cm = (ChessMove) Myylex();
11535         }
11536     }
11537
11538     /* don't rely on existence of Event tag since if game was
11539      * pasted from clipboard the Event tag may not exist
11540      */
11541     if (numPGNTags > 0){
11542         char *tags;
11543         if (gameInfo.variant == VariantNormal) {
11544           VariantClass v = StringToVariant(gameInfo.event);
11545           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11546           if(v < VariantShogi) gameInfo.variant = v;
11547         }
11548         if (!matchMode) {
11549           if( appData.autoDisplayTags ) {
11550             tags = PGNTags(&gameInfo);
11551             TagsPopUp(tags, CmailMsg());
11552             free(tags);
11553           }
11554         }
11555     } else {
11556         /* Make something up, but don't display it now */
11557         SetGameInfo();
11558         TagsPopDown();
11559     }
11560
11561     if (cm == PositionDiagram) {
11562         int i, j;
11563         char *p;
11564         Board initial_position;
11565
11566         if (appData.debugMode)
11567           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11568
11569         if (!startedFromSetupPosition) {
11570             p = yy_text;
11571             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11572               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11573                 switch (*p) {
11574                   case '{':
11575                   case '[':
11576                   case '-':
11577                   case ' ':
11578                   case '\t':
11579                   case '\n':
11580                   case '\r':
11581                     break;
11582                   default:
11583                     initial_position[i][j++] = CharToPiece(*p);
11584                     break;
11585                 }
11586             while (*p == ' ' || *p == '\t' ||
11587                    *p == '\n' || *p == '\r') p++;
11588
11589             if (strncmp(p, "black", strlen("black"))==0)
11590               blackPlaysFirst = TRUE;
11591             else
11592               blackPlaysFirst = FALSE;
11593             startedFromSetupPosition = TRUE;
11594
11595             CopyBoard(boards[0], initial_position);
11596             if (blackPlaysFirst) {
11597                 currentMove = forwardMostMove = backwardMostMove = 1;
11598                 CopyBoard(boards[1], initial_position);
11599                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11600                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11601                 timeRemaining[0][1] = whiteTimeRemaining;
11602                 timeRemaining[1][1] = blackTimeRemaining;
11603                 if (commentList[0] != NULL) {
11604                     commentList[1] = commentList[0];
11605                     commentList[0] = NULL;
11606                 }
11607             } else {
11608                 currentMove = forwardMostMove = backwardMostMove = 0;
11609             }
11610         }
11611         yyboardindex = forwardMostMove;
11612         cm = (ChessMove) Myylex();
11613     }
11614
11615     if (first.pr == NoProc) {
11616         StartChessProgram(&first);
11617     }
11618     InitChessProgram(&first, FALSE);
11619     SendToProgram("force\n", &first);
11620     if (startedFromSetupPosition) {
11621         SendBoard(&first, forwardMostMove);
11622     if (appData.debugMode) {
11623         fprintf(debugFP, "Load Game\n");
11624     }
11625         DisplayBothClocks();
11626     }
11627
11628     /* [HGM] server: flag to write setup moves in broadcast file as one */
11629     loadFlag = appData.suppressLoadMoves;
11630
11631     while (cm == Comment) {
11632         char *p;
11633         if (appData.debugMode)
11634           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11635         p = yy_text;
11636         AppendComment(currentMove, p, FALSE);
11637         yyboardindex = forwardMostMove;
11638         cm = (ChessMove) Myylex();
11639     }
11640
11641     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11642         cm == WhiteWins || cm == BlackWins ||
11643         cm == GameIsDrawn || cm == GameUnfinished) {
11644         DisplayMessage("", _("No moves in game"));
11645         if (cmailMsgLoaded) {
11646             if (appData.debugMode)
11647               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11648             ClearHighlights();
11649             flipView = FALSE;
11650         }
11651         DrawPosition(FALSE, boards[currentMove]);
11652         DisplayBothClocks();
11653         gameMode = EditGame;
11654         ModeHighlight();
11655         gameFileFP = NULL;
11656         cmailOldMove = 0;
11657         return TRUE;
11658     }
11659
11660     // [HGM] PV info: routine tests if comment empty
11661     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11662         DisplayComment(currentMove - 1, commentList[currentMove]);
11663     }
11664     if (!matchMode && appData.timeDelay != 0)
11665       DrawPosition(FALSE, boards[currentMove]);
11666
11667     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11668       programStats.ok_to_send = 1;
11669     }
11670
11671     /* if the first token after the PGN tags is a move
11672      * and not move number 1, retrieve it from the parser
11673      */
11674     if (cm != MoveNumberOne)
11675         LoadGameOneMove(cm);
11676
11677     /* load the remaining moves from the file */
11678     while (LoadGameOneMove(EndOfFile)) {
11679       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11680       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11681     }
11682
11683     /* rewind to the start of the game */
11684     currentMove = backwardMostMove;
11685
11686     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11687
11688     if (oldGameMode == AnalyzeFile ||
11689         oldGameMode == AnalyzeMode) {
11690       AnalyzeFileEvent();
11691     }
11692
11693     if (!matchMode && pos >= 0) {
11694         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11695     } else
11696     if (matchMode || appData.timeDelay == 0) {
11697       ToEndEvent();
11698     } else if (appData.timeDelay > 0) {
11699       AutoPlayGameLoop();
11700     }
11701
11702     if (appData.debugMode)
11703         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11704
11705     loadFlag = 0; /* [HGM] true game starts */
11706     return TRUE;
11707 }
11708
11709 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11710 int
11711 ReloadPosition(offset)
11712      int offset;
11713 {
11714     int positionNumber = lastLoadPositionNumber + offset;
11715     if (lastLoadPositionFP == NULL) {
11716         DisplayError(_("No position has been loaded yet"), 0);
11717         return FALSE;
11718     }
11719     if (positionNumber <= 0) {
11720         DisplayError(_("Can't back up any further"), 0);
11721         return FALSE;
11722     }
11723     return LoadPosition(lastLoadPositionFP, positionNumber,
11724                         lastLoadPositionTitle);
11725 }
11726
11727 /* Load the nth position from the given file */
11728 int
11729 LoadPositionFromFile(filename, n, title)
11730      char *filename;
11731      int n;
11732      char *title;
11733 {
11734     FILE *f;
11735     char buf[MSG_SIZ];
11736
11737     if (strcmp(filename, "-") == 0) {
11738         return LoadPosition(stdin, n, "stdin");
11739     } else {
11740         f = fopen(filename, "rb");
11741         if (f == NULL) {
11742             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11743             DisplayError(buf, errno);
11744             return FALSE;
11745         } else {
11746             return LoadPosition(f, n, title);
11747         }
11748     }
11749 }
11750
11751 /* Load the nth position from the given open file, and close it */
11752 int
11753 LoadPosition(f, positionNumber, title)
11754      FILE *f;
11755      int positionNumber;
11756      char *title;
11757 {
11758     char *p, line[MSG_SIZ];
11759     Board initial_position;
11760     int i, j, fenMode, pn;
11761
11762     if (gameMode == Training )
11763         SetTrainingModeOff();
11764
11765     if (gameMode != BeginningOfGame) {
11766         Reset(FALSE, TRUE);
11767     }
11768     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11769         fclose(lastLoadPositionFP);
11770     }
11771     if (positionNumber == 0) positionNumber = 1;
11772     lastLoadPositionFP = f;
11773     lastLoadPositionNumber = positionNumber;
11774     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11775     if (first.pr == NoProc) {
11776       StartChessProgram(&first);
11777       InitChessProgram(&first, FALSE);
11778     }
11779     pn = positionNumber;
11780     if (positionNumber < 0) {
11781         /* Negative position number means to seek to that byte offset */
11782         if (fseek(f, -positionNumber, 0) == -1) {
11783             DisplayError(_("Can't seek on position file"), 0);
11784             return FALSE;
11785         };
11786         pn = 1;
11787     } else {
11788         if (fseek(f, 0, 0) == -1) {
11789             if (f == lastLoadPositionFP ?
11790                 positionNumber == lastLoadPositionNumber + 1 :
11791                 positionNumber == 1) {
11792                 pn = 1;
11793             } else {
11794                 DisplayError(_("Can't seek on position file"), 0);
11795                 return FALSE;
11796             }
11797         }
11798     }
11799     /* See if this file is FEN or old-style xboard */
11800     if (fgets(line, MSG_SIZ, f) == NULL) {
11801         DisplayError(_("Position not found in file"), 0);
11802         return FALSE;
11803     }
11804     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11805     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11806
11807     if (pn >= 2) {
11808         if (fenMode || line[0] == '#') pn--;
11809         while (pn > 0) {
11810             /* skip positions before number pn */
11811             if (fgets(line, MSG_SIZ, f) == NULL) {
11812                 Reset(TRUE, TRUE);
11813                 DisplayError(_("Position not found in file"), 0);
11814                 return FALSE;
11815             }
11816             if (fenMode || line[0] == '#') pn--;
11817         }
11818     }
11819
11820     if (fenMode) {
11821         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11822             DisplayError(_("Bad FEN position in file"), 0);
11823             return FALSE;
11824         }
11825     } else {
11826         (void) fgets(line, MSG_SIZ, f);
11827         (void) fgets(line, MSG_SIZ, f);
11828
11829         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11830             (void) fgets(line, MSG_SIZ, f);
11831             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11832                 if (*p == ' ')
11833                   continue;
11834                 initial_position[i][j++] = CharToPiece(*p);
11835             }
11836         }
11837
11838         blackPlaysFirst = FALSE;
11839         if (!feof(f)) {
11840             (void) fgets(line, MSG_SIZ, f);
11841             if (strncmp(line, "black", strlen("black"))==0)
11842               blackPlaysFirst = TRUE;
11843         }
11844     }
11845     startedFromSetupPosition = TRUE;
11846
11847     SendToProgram("force\n", &first);
11848     CopyBoard(boards[0], initial_position);
11849     if (blackPlaysFirst) {
11850         currentMove = forwardMostMove = backwardMostMove = 1;
11851         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11852         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11853         CopyBoard(boards[1], initial_position);
11854         DisplayMessage("", _("Black to play"));
11855     } else {
11856         currentMove = forwardMostMove = backwardMostMove = 0;
11857         DisplayMessage("", _("White to play"));
11858     }
11859     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11860     SendBoard(&first, forwardMostMove);
11861     if (appData.debugMode) {
11862 int i, j;
11863   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11864   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11865         fprintf(debugFP, "Load Position\n");
11866     }
11867
11868     if (positionNumber > 1) {
11869       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11870         DisplayTitle(line);
11871     } else {
11872         DisplayTitle(title);
11873     }
11874     gameMode = EditGame;
11875     ModeHighlight();
11876     ResetClocks();
11877     timeRemaining[0][1] = whiteTimeRemaining;
11878     timeRemaining[1][1] = blackTimeRemaining;
11879     DrawPosition(FALSE, boards[currentMove]);
11880
11881     return TRUE;
11882 }
11883
11884
11885 void
11886 CopyPlayerNameIntoFileName(dest, src)
11887      char **dest, *src;
11888 {
11889     while (*src != NULLCHAR && *src != ',') {
11890         if (*src == ' ') {
11891             *(*dest)++ = '_';
11892             src++;
11893         } else {
11894             *(*dest)++ = *src++;
11895         }
11896     }
11897 }
11898
11899 char *DefaultFileName(ext)
11900      char *ext;
11901 {
11902     static char def[MSG_SIZ];
11903     char *p;
11904
11905     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11906         p = def;
11907         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11908         *p++ = '-';
11909         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11910         *p++ = '.';
11911         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11912     } else {
11913         def[0] = NULLCHAR;
11914     }
11915     return def;
11916 }
11917
11918 /* Save the current game to the given file */
11919 int
11920 SaveGameToFile(filename, append)
11921      char *filename;
11922      int append;
11923 {
11924     FILE *f;
11925     char buf[MSG_SIZ];
11926     int result;
11927
11928     if (strcmp(filename, "-") == 0) {
11929         return SaveGame(stdout, 0, NULL);
11930     } else {
11931         f = fopen(filename, append ? "a" : "w");
11932         if (f == NULL) {
11933             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11934             DisplayError(buf, errno);
11935             return FALSE;
11936         } else {
11937             safeStrCpy(buf, lastMsg, MSG_SIZ);
11938             DisplayMessage(_("Waiting for access to save file"), "");
11939             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11940             DisplayMessage(_("Saving game"), "");
11941             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11942             result = SaveGame(f, 0, NULL);
11943             DisplayMessage(buf, "");
11944             return result;
11945         }
11946     }
11947 }
11948
11949 char *
11950 SavePart(str)
11951      char *str;
11952 {
11953     static char buf[MSG_SIZ];
11954     char *p;
11955
11956     p = strchr(str, ' ');
11957     if (p == NULL) return str;
11958     strncpy(buf, str, p - str);
11959     buf[p - str] = NULLCHAR;
11960     return buf;
11961 }
11962
11963 #define PGN_MAX_LINE 75
11964
11965 #define PGN_SIDE_WHITE  0
11966 #define PGN_SIDE_BLACK  1
11967
11968 /* [AS] */
11969 static int FindFirstMoveOutOfBook( int side )
11970 {
11971     int result = -1;
11972
11973     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11974         int index = backwardMostMove;
11975         int has_book_hit = 0;
11976
11977         if( (index % 2) != side ) {
11978             index++;
11979         }
11980
11981         while( index < forwardMostMove ) {
11982             /* Check to see if engine is in book */
11983             int depth = pvInfoList[index].depth;
11984             int score = pvInfoList[index].score;
11985             int in_book = 0;
11986
11987             if( depth <= 2 ) {
11988                 in_book = 1;
11989             }
11990             else if( score == 0 && depth == 63 ) {
11991                 in_book = 1; /* Zappa */
11992             }
11993             else if( score == 2 && depth == 99 ) {
11994                 in_book = 1; /* Abrok */
11995             }
11996
11997             has_book_hit += in_book;
11998
11999             if( ! in_book ) {
12000                 result = index;
12001
12002                 break;
12003             }
12004
12005             index += 2;
12006         }
12007     }
12008
12009     return result;
12010 }
12011
12012 /* [AS] */
12013 void GetOutOfBookInfo( char * buf )
12014 {
12015     int oob[2];
12016     int i;
12017     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12018
12019     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12020     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12021
12022     *buf = '\0';
12023
12024     if( oob[0] >= 0 || oob[1] >= 0 ) {
12025         for( i=0; i<2; i++ ) {
12026             int idx = oob[i];
12027
12028             if( idx >= 0 ) {
12029                 if( i > 0 && oob[0] >= 0 ) {
12030                     strcat( buf, "   " );
12031                 }
12032
12033                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12034                 sprintf( buf+strlen(buf), "%s%.2f",
12035                     pvInfoList[idx].score >= 0 ? "+" : "",
12036                     pvInfoList[idx].score / 100.0 );
12037             }
12038         }
12039     }
12040 }
12041
12042 /* Save game in PGN style and close the file */
12043 int
12044 SaveGamePGN(f)
12045      FILE *f;
12046 {
12047     int i, offset, linelen, newblock;
12048     time_t tm;
12049 //    char *movetext;
12050     char numtext[32];
12051     int movelen, numlen, blank;
12052     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12053
12054     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12055
12056     tm = time((time_t *) NULL);
12057
12058     PrintPGNTags(f, &gameInfo);
12059
12060     if (backwardMostMove > 0 || startedFromSetupPosition) {
12061         char *fen = PositionToFEN(backwardMostMove, NULL);
12062         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12063         fprintf(f, "\n{--------------\n");
12064         PrintPosition(f, backwardMostMove);
12065         fprintf(f, "--------------}\n");
12066         free(fen);
12067     }
12068     else {
12069         /* [AS] Out of book annotation */
12070         if( appData.saveOutOfBookInfo ) {
12071             char buf[64];
12072
12073             GetOutOfBookInfo( buf );
12074
12075             if( buf[0] != '\0' ) {
12076                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12077             }
12078         }
12079
12080         fprintf(f, "\n");
12081     }
12082
12083     i = backwardMostMove;
12084     linelen = 0;
12085     newblock = TRUE;
12086
12087     while (i < forwardMostMove) {
12088         /* Print comments preceding this move */
12089         if (commentList[i] != NULL) {
12090             if (linelen > 0) fprintf(f, "\n");
12091             fprintf(f, "%s", commentList[i]);
12092             linelen = 0;
12093             newblock = TRUE;
12094         }
12095
12096         /* Format move number */
12097         if ((i % 2) == 0)
12098           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12099         else
12100           if (newblock)
12101             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12102           else
12103             numtext[0] = NULLCHAR;
12104
12105         numlen = strlen(numtext);
12106         newblock = FALSE;
12107
12108         /* Print move number */
12109         blank = linelen > 0 && numlen > 0;
12110         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12111             fprintf(f, "\n");
12112             linelen = 0;
12113             blank = 0;
12114         }
12115         if (blank) {
12116             fprintf(f, " ");
12117             linelen++;
12118         }
12119         fprintf(f, "%s", numtext);
12120         linelen += numlen;
12121
12122         /* Get move */
12123         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12124         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12125
12126         /* Print move */
12127         blank = linelen > 0 && movelen > 0;
12128         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12129             fprintf(f, "\n");
12130             linelen = 0;
12131             blank = 0;
12132         }
12133         if (blank) {
12134             fprintf(f, " ");
12135             linelen++;
12136         }
12137         fprintf(f, "%s", move_buffer);
12138         linelen += movelen;
12139
12140         /* [AS] Add PV info if present */
12141         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12142             /* [HGM] add time */
12143             char buf[MSG_SIZ]; int seconds;
12144
12145             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12146
12147             if( seconds <= 0)
12148               buf[0] = 0;
12149             else
12150               if( seconds < 30 )
12151                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12152               else
12153                 {
12154                   seconds = (seconds + 4)/10; // round to full seconds
12155                   if( seconds < 60 )
12156                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12157                   else
12158                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12159                 }
12160
12161             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12162                       pvInfoList[i].score >= 0 ? "+" : "",
12163                       pvInfoList[i].score / 100.0,
12164                       pvInfoList[i].depth,
12165                       buf );
12166
12167             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12168
12169             /* Print score/depth */
12170             blank = linelen > 0 && movelen > 0;
12171             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12172                 fprintf(f, "\n");
12173                 linelen = 0;
12174                 blank = 0;
12175             }
12176             if (blank) {
12177                 fprintf(f, " ");
12178                 linelen++;
12179             }
12180             fprintf(f, "%s", move_buffer);
12181             linelen += movelen;
12182         }
12183
12184         i++;
12185     }
12186
12187     /* Start a new line */
12188     if (linelen > 0) fprintf(f, "\n");
12189
12190     /* Print comments after last move */
12191     if (commentList[i] != NULL) {
12192         fprintf(f, "%s\n", commentList[i]);
12193     }
12194
12195     /* Print result */
12196     if (gameInfo.resultDetails != NULL &&
12197         gameInfo.resultDetails[0] != NULLCHAR) {
12198         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12199                 PGNResult(gameInfo.result));
12200     } else {
12201         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12202     }
12203
12204     fclose(f);
12205     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12206     return TRUE;
12207 }
12208
12209 /* Save game in old style and close the file */
12210 int
12211 SaveGameOldStyle(f)
12212      FILE *f;
12213 {
12214     int i, offset;
12215     time_t tm;
12216
12217     tm = time((time_t *) NULL);
12218
12219     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12220     PrintOpponents(f);
12221
12222     if (backwardMostMove > 0 || startedFromSetupPosition) {
12223         fprintf(f, "\n[--------------\n");
12224         PrintPosition(f, backwardMostMove);
12225         fprintf(f, "--------------]\n");
12226     } else {
12227         fprintf(f, "\n");
12228     }
12229
12230     i = backwardMostMove;
12231     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12232
12233     while (i < forwardMostMove) {
12234         if (commentList[i] != NULL) {
12235             fprintf(f, "[%s]\n", commentList[i]);
12236         }
12237
12238         if ((i % 2) == 1) {
12239             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12240             i++;
12241         } else {
12242             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12243             i++;
12244             if (commentList[i] != NULL) {
12245                 fprintf(f, "\n");
12246                 continue;
12247             }
12248             if (i >= forwardMostMove) {
12249                 fprintf(f, "\n");
12250                 break;
12251             }
12252             fprintf(f, "%s\n", parseList[i]);
12253             i++;
12254         }
12255     }
12256
12257     if (commentList[i] != NULL) {
12258         fprintf(f, "[%s]\n", commentList[i]);
12259     }
12260
12261     /* This isn't really the old style, but it's close enough */
12262     if (gameInfo.resultDetails != NULL &&
12263         gameInfo.resultDetails[0] != NULLCHAR) {
12264         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12265                 gameInfo.resultDetails);
12266     } else {
12267         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12268     }
12269
12270     fclose(f);
12271     return TRUE;
12272 }
12273
12274 /* Save the current game to open file f and close the file */
12275 int
12276 SaveGame(f, dummy, dummy2)
12277      FILE *f;
12278      int dummy;
12279      char *dummy2;
12280 {
12281     if (gameMode == EditPosition) EditPositionDone(TRUE);
12282     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12283     if (appData.oldSaveStyle)
12284       return SaveGameOldStyle(f);
12285     else
12286       return SaveGamePGN(f);
12287 }
12288
12289 /* Save the current position to the given file */
12290 int
12291 SavePositionToFile(filename)
12292      char *filename;
12293 {
12294     FILE *f;
12295     char buf[MSG_SIZ];
12296
12297     if (strcmp(filename, "-") == 0) {
12298         return SavePosition(stdout, 0, NULL);
12299     } else {
12300         f = fopen(filename, "a");
12301         if (f == NULL) {
12302             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12303             DisplayError(buf, errno);
12304             return FALSE;
12305         } else {
12306             safeStrCpy(buf, lastMsg, MSG_SIZ);
12307             DisplayMessage(_("Waiting for access to save file"), "");
12308             flock(fileno(f), LOCK_EX); // [HGM] lock
12309             DisplayMessage(_("Saving position"), "");
12310             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12311             SavePosition(f, 0, NULL);
12312             DisplayMessage(buf, "");
12313             return TRUE;
12314         }
12315     }
12316 }
12317
12318 /* Save the current position to the given open file and close the file */
12319 int
12320 SavePosition(f, dummy, dummy2)
12321      FILE *f;
12322      int dummy;
12323      char *dummy2;
12324 {
12325     time_t tm;
12326     char *fen;
12327
12328     if (gameMode == EditPosition) EditPositionDone(TRUE);
12329     if (appData.oldSaveStyle) {
12330         tm = time((time_t *) NULL);
12331
12332         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12333         PrintOpponents(f);
12334         fprintf(f, "[--------------\n");
12335         PrintPosition(f, currentMove);
12336         fprintf(f, "--------------]\n");
12337     } else {
12338         fen = PositionToFEN(currentMove, NULL);
12339         fprintf(f, "%s\n", fen);
12340         free(fen);
12341     }
12342     fclose(f);
12343     return TRUE;
12344 }
12345
12346 void
12347 ReloadCmailMsgEvent(unregister)
12348      int unregister;
12349 {
12350 #if !WIN32
12351     static char *inFilename = NULL;
12352     static char *outFilename;
12353     int i;
12354     struct stat inbuf, outbuf;
12355     int status;
12356
12357     /* Any registered moves are unregistered if unregister is set, */
12358     /* i.e. invoked by the signal handler */
12359     if (unregister) {
12360         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12361             cmailMoveRegistered[i] = FALSE;
12362             if (cmailCommentList[i] != NULL) {
12363                 free(cmailCommentList[i]);
12364                 cmailCommentList[i] = NULL;
12365             }
12366         }
12367         nCmailMovesRegistered = 0;
12368     }
12369
12370     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12371         cmailResult[i] = CMAIL_NOT_RESULT;
12372     }
12373     nCmailResults = 0;
12374
12375     if (inFilename == NULL) {
12376         /* Because the filenames are static they only get malloced once  */
12377         /* and they never get freed                                      */
12378         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12379         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12380
12381         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12382         sprintf(outFilename, "%s.out", appData.cmailGameName);
12383     }
12384
12385     status = stat(outFilename, &outbuf);
12386     if (status < 0) {
12387         cmailMailedMove = FALSE;
12388     } else {
12389         status = stat(inFilename, &inbuf);
12390         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12391     }
12392
12393     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12394        counts the games, notes how each one terminated, etc.
12395
12396        It would be nice to remove this kludge and instead gather all
12397        the information while building the game list.  (And to keep it
12398        in the game list nodes instead of having a bunch of fixed-size
12399        parallel arrays.)  Note this will require getting each game's
12400        termination from the PGN tags, as the game list builder does
12401        not process the game moves.  --mann
12402        */
12403     cmailMsgLoaded = TRUE;
12404     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12405
12406     /* Load first game in the file or popup game menu */
12407     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12408
12409 #endif /* !WIN32 */
12410     return;
12411 }
12412
12413 int
12414 RegisterMove()
12415 {
12416     FILE *f;
12417     char string[MSG_SIZ];
12418
12419     if (   cmailMailedMove
12420         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12421         return TRUE;            /* Allow free viewing  */
12422     }
12423
12424     /* Unregister move to ensure that we don't leave RegisterMove        */
12425     /* with the move registered when the conditions for registering no   */
12426     /* longer hold                                                       */
12427     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12428         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12429         nCmailMovesRegistered --;
12430
12431         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12432           {
12433               free(cmailCommentList[lastLoadGameNumber - 1]);
12434               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12435           }
12436     }
12437
12438     if (cmailOldMove == -1) {
12439         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12440         return FALSE;
12441     }
12442
12443     if (currentMove > cmailOldMove + 1) {
12444         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12445         return FALSE;
12446     }
12447
12448     if (currentMove < cmailOldMove) {
12449         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12450         return FALSE;
12451     }
12452
12453     if (forwardMostMove > currentMove) {
12454         /* Silently truncate extra moves */
12455         TruncateGame();
12456     }
12457
12458     if (   (currentMove == cmailOldMove + 1)
12459         || (   (currentMove == cmailOldMove)
12460             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12461                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12462         if (gameInfo.result != GameUnfinished) {
12463             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12464         }
12465
12466         if (commentList[currentMove] != NULL) {
12467             cmailCommentList[lastLoadGameNumber - 1]
12468               = StrSave(commentList[currentMove]);
12469         }
12470         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12471
12472         if (appData.debugMode)
12473           fprintf(debugFP, "Saving %s for game %d\n",
12474                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12475
12476         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12477
12478         f = fopen(string, "w");
12479         if (appData.oldSaveStyle) {
12480             SaveGameOldStyle(f); /* also closes the file */
12481
12482             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12483             f = fopen(string, "w");
12484             SavePosition(f, 0, NULL); /* also closes the file */
12485         } else {
12486             fprintf(f, "{--------------\n");
12487             PrintPosition(f, currentMove);
12488             fprintf(f, "--------------}\n\n");
12489
12490             SaveGame(f, 0, NULL); /* also closes the file*/
12491         }
12492
12493         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12494         nCmailMovesRegistered ++;
12495     } else if (nCmailGames == 1) {
12496         DisplayError(_("You have not made a move yet"), 0);
12497         return FALSE;
12498     }
12499
12500     return TRUE;
12501 }
12502
12503 void
12504 MailMoveEvent()
12505 {
12506 #if !WIN32
12507     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12508     FILE *commandOutput;
12509     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12510     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12511     int nBuffers;
12512     int i;
12513     int archived;
12514     char *arcDir;
12515
12516     if (! cmailMsgLoaded) {
12517         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12518         return;
12519     }
12520
12521     if (nCmailGames == nCmailResults) {
12522         DisplayError(_("No unfinished games"), 0);
12523         return;
12524     }
12525
12526 #if CMAIL_PROHIBIT_REMAIL
12527     if (cmailMailedMove) {
12528       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);
12529         DisplayError(msg, 0);
12530         return;
12531     }
12532 #endif
12533
12534     if (! (cmailMailedMove || RegisterMove())) return;
12535
12536     if (   cmailMailedMove
12537         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12538       snprintf(string, MSG_SIZ, partCommandString,
12539                appData.debugMode ? " -v" : "", appData.cmailGameName);
12540         commandOutput = popen(string, "r");
12541
12542         if (commandOutput == NULL) {
12543             DisplayError(_("Failed to invoke cmail"), 0);
12544         } else {
12545             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12546                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12547             }
12548             if (nBuffers > 1) {
12549                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12550                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12551                 nBytes = MSG_SIZ - 1;
12552             } else {
12553                 (void) memcpy(msg, buffer, nBytes);
12554             }
12555             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12556
12557             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12558                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12559
12560                 archived = TRUE;
12561                 for (i = 0; i < nCmailGames; i ++) {
12562                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12563                         archived = FALSE;
12564                     }
12565                 }
12566                 if (   archived
12567                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12568                         != NULL)) {
12569                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12570                            arcDir,
12571                            appData.cmailGameName,
12572                            gameInfo.date);
12573                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12574                     cmailMsgLoaded = FALSE;
12575                 }
12576             }
12577
12578             DisplayInformation(msg);
12579             pclose(commandOutput);
12580         }
12581     } else {
12582         if ((*cmailMsg) != '\0') {
12583             DisplayInformation(cmailMsg);
12584         }
12585     }
12586
12587     return;
12588 #endif /* !WIN32 */
12589 }
12590
12591 char *
12592 CmailMsg()
12593 {
12594 #if WIN32
12595     return NULL;
12596 #else
12597     int  prependComma = 0;
12598     char number[5];
12599     char string[MSG_SIZ];       /* Space for game-list */
12600     int  i;
12601
12602     if (!cmailMsgLoaded) return "";
12603
12604     if (cmailMailedMove) {
12605       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12606     } else {
12607         /* Create a list of games left */
12608       snprintf(string, MSG_SIZ, "[");
12609         for (i = 0; i < nCmailGames; i ++) {
12610             if (! (   cmailMoveRegistered[i]
12611                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12612                 if (prependComma) {
12613                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12614                 } else {
12615                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12616                     prependComma = 1;
12617                 }
12618
12619                 strcat(string, number);
12620             }
12621         }
12622         strcat(string, "]");
12623
12624         if (nCmailMovesRegistered + nCmailResults == 0) {
12625             switch (nCmailGames) {
12626               case 1:
12627                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12628                 break;
12629
12630               case 2:
12631                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12632                 break;
12633
12634               default:
12635                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12636                          nCmailGames);
12637                 break;
12638             }
12639         } else {
12640             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12641               case 1:
12642                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12643                          string);
12644                 break;
12645
12646               case 0:
12647                 if (nCmailResults == nCmailGames) {
12648                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12649                 } else {
12650                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12651                 }
12652                 break;
12653
12654               default:
12655                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12656                          string);
12657             }
12658         }
12659     }
12660     return cmailMsg;
12661 #endif /* WIN32 */
12662 }
12663
12664 void
12665 ResetGameEvent()
12666 {
12667     if (gameMode == Training)
12668       SetTrainingModeOff();
12669
12670     Reset(TRUE, TRUE);
12671     cmailMsgLoaded = FALSE;
12672     if (appData.icsActive) {
12673       SendToICS(ics_prefix);
12674       SendToICS("refresh\n");
12675     }
12676 }
12677
12678 void
12679 ExitEvent(status)
12680      int status;
12681 {
12682     exiting++;
12683     if (exiting > 2) {
12684       /* Give up on clean exit */
12685       exit(status);
12686     }
12687     if (exiting > 1) {
12688       /* Keep trying for clean exit */
12689       return;
12690     }
12691
12692     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12693
12694     if (telnetISR != NULL) {
12695       RemoveInputSource(telnetISR);
12696     }
12697     if (icsPR != NoProc) {
12698       DestroyChildProcess(icsPR, TRUE);
12699     }
12700
12701     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12702     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12703
12704     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12705     /* make sure this other one finishes before killing it!                  */
12706     if(endingGame) { int count = 0;
12707         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12708         while(endingGame && count++ < 10) DoSleep(1);
12709         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12710     }
12711
12712     /* Kill off chess programs */
12713     if (first.pr != NoProc) {
12714         ExitAnalyzeMode();
12715
12716         DoSleep( appData.delayBeforeQuit );
12717         SendToProgram("quit\n", &first);
12718         DoSleep( appData.delayAfterQuit );
12719         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12720     }
12721     if (second.pr != NoProc) {
12722         DoSleep( appData.delayBeforeQuit );
12723         SendToProgram("quit\n", &second);
12724         DoSleep( appData.delayAfterQuit );
12725         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12726     }
12727     if (first.isr != NULL) {
12728         RemoveInputSource(first.isr);
12729     }
12730     if (second.isr != NULL) {
12731         RemoveInputSource(second.isr);
12732     }
12733
12734     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12735     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12736
12737     ShutDownFrontEnd();
12738     exit(status);
12739 }
12740
12741 void
12742 PauseEvent()
12743 {
12744     if (appData.debugMode)
12745         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12746     if (pausing) {
12747         pausing = FALSE;
12748         ModeHighlight();
12749         if (gameMode == MachinePlaysWhite ||
12750             gameMode == MachinePlaysBlack) {
12751             StartClocks();
12752         } else {
12753             DisplayBothClocks();
12754         }
12755         if (gameMode == PlayFromGameFile) {
12756             if (appData.timeDelay >= 0)
12757                 AutoPlayGameLoop();
12758         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12759             Reset(FALSE, TRUE);
12760             SendToICS(ics_prefix);
12761             SendToICS("refresh\n");
12762         } else if (currentMove < forwardMostMove) {
12763             ForwardInner(forwardMostMove);
12764         }
12765         pauseExamInvalid = FALSE;
12766     } else {
12767         switch (gameMode) {
12768           default:
12769             return;
12770           case IcsExamining:
12771             pauseExamForwardMostMove = forwardMostMove;
12772             pauseExamInvalid = FALSE;
12773             /* fall through */
12774           case IcsObserving:
12775           case IcsPlayingWhite:
12776           case IcsPlayingBlack:
12777             pausing = TRUE;
12778             ModeHighlight();
12779             return;
12780           case PlayFromGameFile:
12781             (void) StopLoadGameTimer();
12782             pausing = TRUE;
12783             ModeHighlight();
12784             break;
12785           case BeginningOfGame:
12786             if (appData.icsActive) return;
12787             /* else fall through */
12788           case MachinePlaysWhite:
12789           case MachinePlaysBlack:
12790           case TwoMachinesPlay:
12791             if (forwardMostMove == 0)
12792               return;           /* don't pause if no one has moved */
12793             if ((gameMode == MachinePlaysWhite &&
12794                  !WhiteOnMove(forwardMostMove)) ||
12795                 (gameMode == MachinePlaysBlack &&
12796                  WhiteOnMove(forwardMostMove))) {
12797                 StopClocks();
12798             }
12799             pausing = TRUE;
12800             ModeHighlight();
12801             break;
12802         }
12803     }
12804 }
12805
12806 void
12807 EditCommentEvent()
12808 {
12809     char title[MSG_SIZ];
12810
12811     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12812       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12813     } else {
12814       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12815                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12816                parseList[currentMove - 1]);
12817     }
12818
12819     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12820 }
12821
12822
12823 void
12824 EditTagsEvent()
12825 {
12826     char *tags = PGNTags(&gameInfo);
12827     bookUp = FALSE;
12828     EditTagsPopUp(tags, NULL);
12829     free(tags);
12830 }
12831
12832 void
12833 AnalyzeModeEvent()
12834 {
12835     if (appData.noChessProgram || gameMode == AnalyzeMode)
12836       return;
12837
12838     if (gameMode != AnalyzeFile) {
12839         if (!appData.icsEngineAnalyze) {
12840                EditGameEvent();
12841                if (gameMode != EditGame) return;
12842         }
12843         ResurrectChessProgram();
12844         SendToProgram("analyze\n", &first);
12845         first.analyzing = TRUE;
12846         /*first.maybeThinking = TRUE;*/
12847         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12848         EngineOutputPopUp();
12849     }
12850     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12851     pausing = FALSE;
12852     ModeHighlight();
12853     SetGameInfo();
12854
12855     StartAnalysisClock();
12856     GetTimeMark(&lastNodeCountTime);
12857     lastNodeCount = 0;
12858 }
12859
12860 void
12861 AnalyzeFileEvent()
12862 {
12863     if (appData.noChessProgram || gameMode == AnalyzeFile)
12864       return;
12865
12866     if (gameMode != AnalyzeMode) {
12867         EditGameEvent();
12868         if (gameMode != EditGame) return;
12869         ResurrectChessProgram();
12870         SendToProgram("analyze\n", &first);
12871         first.analyzing = TRUE;
12872         /*first.maybeThinking = TRUE;*/
12873         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12874         EngineOutputPopUp();
12875     }
12876     gameMode = AnalyzeFile;
12877     pausing = FALSE;
12878     ModeHighlight();
12879     SetGameInfo();
12880
12881     StartAnalysisClock();
12882     GetTimeMark(&lastNodeCountTime);
12883     lastNodeCount = 0;
12884     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
12885 }
12886
12887 void
12888 MachineWhiteEvent()
12889 {
12890     char buf[MSG_SIZ];
12891     char *bookHit = NULL;
12892
12893     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12894       return;
12895
12896
12897     if (gameMode == PlayFromGameFile ||
12898         gameMode == TwoMachinesPlay  ||
12899         gameMode == Training         ||
12900         gameMode == AnalyzeMode      ||
12901         gameMode == EndOfGame)
12902         EditGameEvent();
12903
12904     if (gameMode == EditPosition)
12905         EditPositionDone(TRUE);
12906
12907     if (!WhiteOnMove(currentMove)) {
12908         DisplayError(_("It is not White's turn"), 0);
12909         return;
12910     }
12911
12912     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12913       ExitAnalyzeMode();
12914
12915     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12916         gameMode == AnalyzeFile)
12917         TruncateGame();
12918
12919     ResurrectChessProgram();    /* in case it isn't running */
12920     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12921         gameMode = MachinePlaysWhite;
12922         ResetClocks();
12923     } else
12924     gameMode = MachinePlaysWhite;
12925     pausing = FALSE;
12926     ModeHighlight();
12927     SetGameInfo();
12928     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12929     DisplayTitle(buf);
12930     if (first.sendName) {
12931       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12932       SendToProgram(buf, &first);
12933     }
12934     if (first.sendTime) {
12935       if (first.useColors) {
12936         SendToProgram("black\n", &first); /*gnu kludge*/
12937       }
12938       SendTimeRemaining(&first, TRUE);
12939     }
12940     if (first.useColors) {
12941       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12942     }
12943     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12944     SetMachineThinkingEnables();
12945     first.maybeThinking = TRUE;
12946     StartClocks();
12947     firstMove = FALSE;
12948
12949     if (appData.autoFlipView && !flipView) {
12950       flipView = !flipView;
12951       DrawPosition(FALSE, NULL);
12952       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12953     }
12954
12955     if(bookHit) { // [HGM] book: simulate book reply
12956         static char bookMove[MSG_SIZ]; // a bit generous?
12957
12958         programStats.nodes = programStats.depth = programStats.time =
12959         programStats.score = programStats.got_only_move = 0;
12960         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12961
12962         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12963         strcat(bookMove, bookHit);
12964         HandleMachineMove(bookMove, &first);
12965     }
12966 }
12967
12968 void
12969 MachineBlackEvent()
12970 {
12971   char buf[MSG_SIZ];
12972   char *bookHit = NULL;
12973
12974     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12975         return;
12976
12977
12978     if (gameMode == PlayFromGameFile ||
12979         gameMode == TwoMachinesPlay  ||
12980         gameMode == Training         ||
12981         gameMode == AnalyzeMode      ||
12982         gameMode == EndOfGame)
12983         EditGameEvent();
12984
12985     if (gameMode == EditPosition)
12986         EditPositionDone(TRUE);
12987
12988     if (WhiteOnMove(currentMove)) {
12989         DisplayError(_("It is not Black's turn"), 0);
12990         return;
12991     }
12992
12993     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12994       ExitAnalyzeMode();
12995
12996     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12997         gameMode == AnalyzeFile)
12998         TruncateGame();
12999
13000     ResurrectChessProgram();    /* in case it isn't running */
13001     gameMode = MachinePlaysBlack;
13002     pausing = FALSE;
13003     ModeHighlight();
13004     SetGameInfo();
13005     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13006     DisplayTitle(buf);
13007     if (first.sendName) {
13008       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13009       SendToProgram(buf, &first);
13010     }
13011     if (first.sendTime) {
13012       if (first.useColors) {
13013         SendToProgram("white\n", &first); /*gnu kludge*/
13014       }
13015       SendTimeRemaining(&first, FALSE);
13016     }
13017     if (first.useColors) {
13018       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13019     }
13020     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13021     SetMachineThinkingEnables();
13022     first.maybeThinking = TRUE;
13023     StartClocks();
13024
13025     if (appData.autoFlipView && flipView) {
13026       flipView = !flipView;
13027       DrawPosition(FALSE, NULL);
13028       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13029     }
13030     if(bookHit) { // [HGM] book: simulate book reply
13031         static char bookMove[MSG_SIZ]; // a bit generous?
13032
13033         programStats.nodes = programStats.depth = programStats.time =
13034         programStats.score = programStats.got_only_move = 0;
13035         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13036
13037         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13038         strcat(bookMove, bookHit);
13039         HandleMachineMove(bookMove, &first);
13040     }
13041 }
13042
13043
13044 void
13045 DisplayTwoMachinesTitle()
13046 {
13047     char buf[MSG_SIZ];
13048     if (appData.matchGames > 0) {
13049         if(appData.tourneyFile[0]) {
13050           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13051                    gameInfo.white, gameInfo.black,
13052                    nextGame+1, appData.matchGames+1,
13053                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13054         } else 
13055         if (first.twoMachinesColor[0] == 'w') {
13056           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13057                    gameInfo.white, gameInfo.black,
13058                    first.matchWins, second.matchWins,
13059                    matchGame - 1 - (first.matchWins + second.matchWins));
13060         } else {
13061           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13062                    gameInfo.white, gameInfo.black,
13063                    second.matchWins, first.matchWins,
13064                    matchGame - 1 - (first.matchWins + second.matchWins));
13065         }
13066     } else {
13067       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13068     }
13069     DisplayTitle(buf);
13070 }
13071
13072 void
13073 SettingsMenuIfReady()
13074 {
13075   if (second.lastPing != second.lastPong) {
13076     DisplayMessage("", _("Waiting for second chess program"));
13077     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13078     return;
13079   }
13080   ThawUI();
13081   DisplayMessage("", "");
13082   SettingsPopUp(&second);
13083 }
13084
13085 int
13086 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13087 {
13088     char buf[MSG_SIZ];
13089     if (cps->pr == NULL) {
13090         StartChessProgram(cps);
13091         if (cps->protocolVersion == 1) {
13092           retry();
13093         } else {
13094           /* kludge: allow timeout for initial "feature" command */
13095           FreezeUI();
13096           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13097           DisplayMessage("", buf);
13098           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13099         }
13100         return 1;
13101     }
13102     return 0;
13103 }
13104
13105 void
13106 TwoMachinesEvent P((void))
13107 {
13108     int i;
13109     char buf[MSG_SIZ];
13110     ChessProgramState *onmove;
13111     char *bookHit = NULL;
13112     static int stalling = 0;
13113     TimeMark now;
13114     long wait;
13115
13116     if (appData.noChessProgram) return;
13117
13118     switch (gameMode) {
13119       case TwoMachinesPlay:
13120         return;
13121       case MachinePlaysWhite:
13122       case MachinePlaysBlack:
13123         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13124             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13125             return;
13126         }
13127         /* fall through */
13128       case BeginningOfGame:
13129       case PlayFromGameFile:
13130       case EndOfGame:
13131         EditGameEvent();
13132         if (gameMode != EditGame) return;
13133         break;
13134       case EditPosition:
13135         EditPositionDone(TRUE);
13136         break;
13137       case AnalyzeMode:
13138       case AnalyzeFile:
13139         ExitAnalyzeMode();
13140         break;
13141       case EditGame:
13142       default:
13143         break;
13144     }
13145
13146 //    forwardMostMove = currentMove;
13147     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13148
13149     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13150
13151     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13152     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13153       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13154       return;
13155     }
13156     if(!stalling) {
13157       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13158       SendToProgram("force\n", &second);
13159       stalling = 1;
13160       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13161       return;
13162     }
13163     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13164     if(appData.matchPause>10000 || appData.matchPause<10)
13165                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13166     wait = SubtractTimeMarks(&now, &pauseStart);
13167     if(wait < appData.matchPause) {
13168         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13169         return;
13170     }
13171     stalling = 0;
13172     DisplayMessage("", "");
13173     if (startedFromSetupPosition) {
13174         SendBoard(&second, backwardMostMove);
13175     if (appData.debugMode) {
13176         fprintf(debugFP, "Two Machines\n");
13177     }
13178     }
13179     for (i = backwardMostMove; i < forwardMostMove; i++) {
13180         SendMoveToProgram(i, &second);
13181     }
13182
13183     gameMode = TwoMachinesPlay;
13184     pausing = FALSE;
13185     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13186     SetGameInfo();
13187     DisplayTwoMachinesTitle();
13188     firstMove = TRUE;
13189     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13190         onmove = &first;
13191     } else {
13192         onmove = &second;
13193     }
13194     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13195     SendToProgram(first.computerString, &first);
13196     if (first.sendName) {
13197       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13198       SendToProgram(buf, &first);
13199     }
13200     SendToProgram(second.computerString, &second);
13201     if (second.sendName) {
13202       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13203       SendToProgram(buf, &second);
13204     }
13205
13206     ResetClocks();
13207     if (!first.sendTime || !second.sendTime) {
13208         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13209         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13210     }
13211     if (onmove->sendTime) {
13212       if (onmove->useColors) {
13213         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13214       }
13215       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13216     }
13217     if (onmove->useColors) {
13218       SendToProgram(onmove->twoMachinesColor, onmove);
13219     }
13220     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13221 //    SendToProgram("go\n", onmove);
13222     onmove->maybeThinking = TRUE;
13223     SetMachineThinkingEnables();
13224
13225     StartClocks();
13226
13227     if(bookHit) { // [HGM] book: simulate book reply
13228         static char bookMove[MSG_SIZ]; // a bit generous?
13229
13230         programStats.nodes = programStats.depth = programStats.time =
13231         programStats.score = programStats.got_only_move = 0;
13232         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13233
13234         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13235         strcat(bookMove, bookHit);
13236         savedMessage = bookMove; // args for deferred call
13237         savedState = onmove;
13238         ScheduleDelayedEvent(DeferredBookMove, 1);
13239     }
13240 }
13241
13242 void
13243 TrainingEvent()
13244 {
13245     if (gameMode == Training) {
13246       SetTrainingModeOff();
13247       gameMode = PlayFromGameFile;
13248       DisplayMessage("", _("Training mode off"));
13249     } else {
13250       gameMode = Training;
13251       animateTraining = appData.animate;
13252
13253       /* make sure we are not already at the end of the game */
13254       if (currentMove < forwardMostMove) {
13255         SetTrainingModeOn();
13256         DisplayMessage("", _("Training mode on"));
13257       } else {
13258         gameMode = PlayFromGameFile;
13259         DisplayError(_("Already at end of game"), 0);
13260       }
13261     }
13262     ModeHighlight();
13263 }
13264
13265 void
13266 IcsClientEvent()
13267 {
13268     if (!appData.icsActive) return;
13269     switch (gameMode) {
13270       case IcsPlayingWhite:
13271       case IcsPlayingBlack:
13272       case IcsObserving:
13273       case IcsIdle:
13274       case BeginningOfGame:
13275       case IcsExamining:
13276         return;
13277
13278       case EditGame:
13279         break;
13280
13281       case EditPosition:
13282         EditPositionDone(TRUE);
13283         break;
13284
13285       case AnalyzeMode:
13286       case AnalyzeFile:
13287         ExitAnalyzeMode();
13288         break;
13289
13290       default:
13291         EditGameEvent();
13292         break;
13293     }
13294
13295     gameMode = IcsIdle;
13296     ModeHighlight();
13297     return;
13298 }
13299
13300
13301 void
13302 EditGameEvent()
13303 {
13304     int i;
13305
13306     switch (gameMode) {
13307       case Training:
13308         SetTrainingModeOff();
13309         break;
13310       case MachinePlaysWhite:
13311       case MachinePlaysBlack:
13312       case BeginningOfGame:
13313         SendToProgram("force\n", &first);
13314         SetUserThinkingEnables();
13315         break;
13316       case PlayFromGameFile:
13317         (void) StopLoadGameTimer();
13318         if (gameFileFP != NULL) {
13319             gameFileFP = NULL;
13320         }
13321         break;
13322       case EditPosition:
13323         EditPositionDone(TRUE);
13324         break;
13325       case AnalyzeMode:
13326       case AnalyzeFile:
13327         ExitAnalyzeMode();
13328         SendToProgram("force\n", &first);
13329         break;
13330       case TwoMachinesPlay:
13331         GameEnds(EndOfFile, NULL, GE_PLAYER);
13332         ResurrectChessProgram();
13333         SetUserThinkingEnables();
13334         break;
13335       case EndOfGame:
13336         ResurrectChessProgram();
13337         break;
13338       case IcsPlayingBlack:
13339       case IcsPlayingWhite:
13340         DisplayError(_("Warning: You are still playing a game"), 0);
13341         break;
13342       case IcsObserving:
13343         DisplayError(_("Warning: You are still observing a game"), 0);
13344         break;
13345       case IcsExamining:
13346         DisplayError(_("Warning: You are still examining a game"), 0);
13347         break;
13348       case IcsIdle:
13349         break;
13350       case EditGame:
13351       default:
13352         return;
13353     }
13354
13355     pausing = FALSE;
13356     StopClocks();
13357     first.offeredDraw = second.offeredDraw = 0;
13358
13359     if (gameMode == PlayFromGameFile) {
13360         whiteTimeRemaining = timeRemaining[0][currentMove];
13361         blackTimeRemaining = timeRemaining[1][currentMove];
13362         DisplayTitle("");
13363     }
13364
13365     if (gameMode == MachinePlaysWhite ||
13366         gameMode == MachinePlaysBlack ||
13367         gameMode == TwoMachinesPlay ||
13368         gameMode == EndOfGame) {
13369         i = forwardMostMove;
13370         while (i > currentMove) {
13371             SendToProgram("undo\n", &first);
13372             i--;
13373         }
13374         whiteTimeRemaining = timeRemaining[0][currentMove];
13375         blackTimeRemaining = timeRemaining[1][currentMove];
13376         DisplayBothClocks();
13377         if (whiteFlag || blackFlag) {
13378             whiteFlag = blackFlag = 0;
13379         }
13380         DisplayTitle("");
13381     }
13382
13383     gameMode = EditGame;
13384     ModeHighlight();
13385     SetGameInfo();
13386 }
13387
13388
13389 void
13390 EditPositionEvent()
13391 {
13392     if (gameMode == EditPosition) {
13393         EditGameEvent();
13394         return;
13395     }
13396
13397     EditGameEvent();
13398     if (gameMode != EditGame) return;
13399
13400     gameMode = EditPosition;
13401     ModeHighlight();
13402     SetGameInfo();
13403     if (currentMove > 0)
13404       CopyBoard(boards[0], boards[currentMove]);
13405
13406     blackPlaysFirst = !WhiteOnMove(currentMove);
13407     ResetClocks();
13408     currentMove = forwardMostMove = backwardMostMove = 0;
13409     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13410     DisplayMove(-1);
13411 }
13412
13413 void
13414 ExitAnalyzeMode()
13415 {
13416     /* [DM] icsEngineAnalyze - possible call from other functions */
13417     if (appData.icsEngineAnalyze) {
13418         appData.icsEngineAnalyze = FALSE;
13419
13420         DisplayMessage("",_("Close ICS engine analyze..."));
13421     }
13422     if (first.analysisSupport && first.analyzing) {
13423       SendToProgram("exit\n", &first);
13424       first.analyzing = FALSE;
13425     }
13426     thinkOutput[0] = NULLCHAR;
13427 }
13428
13429 void
13430 EditPositionDone(Boolean fakeRights)
13431 {
13432     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13433
13434     startedFromSetupPosition = TRUE;
13435     InitChessProgram(&first, FALSE);
13436     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13437       boards[0][EP_STATUS] = EP_NONE;
13438       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13439     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13440         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13441         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13442       } else boards[0][CASTLING][2] = NoRights;
13443     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13444         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13445         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13446       } else boards[0][CASTLING][5] = NoRights;
13447     }
13448     SendToProgram("force\n", &first);
13449     if (blackPlaysFirst) {
13450         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13451         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13452         currentMove = forwardMostMove = backwardMostMove = 1;
13453         CopyBoard(boards[1], boards[0]);
13454     } else {
13455         currentMove = forwardMostMove = backwardMostMove = 0;
13456     }
13457     SendBoard(&first, forwardMostMove);
13458     if (appData.debugMode) {
13459         fprintf(debugFP, "EditPosDone\n");
13460     }
13461     DisplayTitle("");
13462     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13463     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13464     gameMode = EditGame;
13465     ModeHighlight();
13466     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13467     ClearHighlights(); /* [AS] */
13468 }
13469
13470 /* Pause for `ms' milliseconds */
13471 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13472 void
13473 TimeDelay(ms)
13474      long ms;
13475 {
13476     TimeMark m1, m2;
13477
13478     GetTimeMark(&m1);
13479     do {
13480         GetTimeMark(&m2);
13481     } while (SubtractTimeMarks(&m2, &m1) < ms);
13482 }
13483
13484 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13485 void
13486 SendMultiLineToICS(buf)
13487      char *buf;
13488 {
13489     char temp[MSG_SIZ+1], *p;
13490     int len;
13491
13492     len = strlen(buf);
13493     if (len > MSG_SIZ)
13494       len = MSG_SIZ;
13495
13496     strncpy(temp, buf, len);
13497     temp[len] = 0;
13498
13499     p = temp;
13500     while (*p) {
13501         if (*p == '\n' || *p == '\r')
13502           *p = ' ';
13503         ++p;
13504     }
13505
13506     strcat(temp, "\n");
13507     SendToICS(temp);
13508     SendToPlayer(temp, strlen(temp));
13509 }
13510
13511 void
13512 SetWhiteToPlayEvent()
13513 {
13514     if (gameMode == EditPosition) {
13515         blackPlaysFirst = FALSE;
13516         DisplayBothClocks();    /* works because currentMove is 0 */
13517     } else if (gameMode == IcsExamining) {
13518         SendToICS(ics_prefix);
13519         SendToICS("tomove white\n");
13520     }
13521 }
13522
13523 void
13524 SetBlackToPlayEvent()
13525 {
13526     if (gameMode == EditPosition) {
13527         blackPlaysFirst = TRUE;
13528         currentMove = 1;        /* kludge */
13529         DisplayBothClocks();
13530         currentMove = 0;
13531     } else if (gameMode == IcsExamining) {
13532         SendToICS(ics_prefix);
13533         SendToICS("tomove black\n");
13534     }
13535 }
13536
13537 void
13538 EditPositionMenuEvent(selection, x, y)
13539      ChessSquare selection;
13540      int x, y;
13541 {
13542     char buf[MSG_SIZ];
13543     ChessSquare piece = boards[0][y][x];
13544
13545     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13546
13547     switch (selection) {
13548       case ClearBoard:
13549         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13550             SendToICS(ics_prefix);
13551             SendToICS("bsetup clear\n");
13552         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13553             SendToICS(ics_prefix);
13554             SendToICS("clearboard\n");
13555         } else {
13556             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13557                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13558                 for (y = 0; y < BOARD_HEIGHT; y++) {
13559                     if (gameMode == IcsExamining) {
13560                         if (boards[currentMove][y][x] != EmptySquare) {
13561                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13562                                     AAA + x, ONE + y);
13563                             SendToICS(buf);
13564                         }
13565                     } else {
13566                         boards[0][y][x] = p;
13567                     }
13568                 }
13569             }
13570         }
13571         if (gameMode == EditPosition) {
13572             DrawPosition(FALSE, boards[0]);
13573         }
13574         break;
13575
13576       case WhitePlay:
13577         SetWhiteToPlayEvent();
13578         break;
13579
13580       case BlackPlay:
13581         SetBlackToPlayEvent();
13582         break;
13583
13584       case EmptySquare:
13585         if (gameMode == IcsExamining) {
13586             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13587             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13588             SendToICS(buf);
13589         } else {
13590             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13591                 if(x == BOARD_LEFT-2) {
13592                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13593                     boards[0][y][1] = 0;
13594                 } else
13595                 if(x == BOARD_RGHT+1) {
13596                     if(y >= gameInfo.holdingsSize) break;
13597                     boards[0][y][BOARD_WIDTH-2] = 0;
13598                 } else break;
13599             }
13600             boards[0][y][x] = EmptySquare;
13601             DrawPosition(FALSE, boards[0]);
13602         }
13603         break;
13604
13605       case PromotePiece:
13606         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13607            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13608             selection = (ChessSquare) (PROMOTED piece);
13609         } else if(piece == EmptySquare) selection = WhiteSilver;
13610         else selection = (ChessSquare)((int)piece - 1);
13611         goto defaultlabel;
13612
13613       case DemotePiece:
13614         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13615            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13616             selection = (ChessSquare) (DEMOTED piece);
13617         } else if(piece == EmptySquare) selection = BlackSilver;
13618         else selection = (ChessSquare)((int)piece + 1);
13619         goto defaultlabel;
13620
13621       case WhiteQueen:
13622       case BlackQueen:
13623         if(gameInfo.variant == VariantShatranj ||
13624            gameInfo.variant == VariantXiangqi  ||
13625            gameInfo.variant == VariantCourier  ||
13626            gameInfo.variant == VariantMakruk     )
13627             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13628         goto defaultlabel;
13629
13630       case WhiteKing:
13631       case BlackKing:
13632         if(gameInfo.variant == VariantXiangqi)
13633             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13634         if(gameInfo.variant == VariantKnightmate)
13635             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13636       default:
13637         defaultlabel:
13638         if (gameMode == IcsExamining) {
13639             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13640             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13641                      PieceToChar(selection), AAA + x, ONE + y);
13642             SendToICS(buf);
13643         } else {
13644             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13645                 int n;
13646                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13647                     n = PieceToNumber(selection - BlackPawn);
13648                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13649                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13650                     boards[0][BOARD_HEIGHT-1-n][1]++;
13651                 } else
13652                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13653                     n = PieceToNumber(selection);
13654                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13655                     boards[0][n][BOARD_WIDTH-1] = selection;
13656                     boards[0][n][BOARD_WIDTH-2]++;
13657                 }
13658             } else
13659             boards[0][y][x] = selection;
13660             DrawPosition(TRUE, boards[0]);
13661         }
13662         break;
13663     }
13664 }
13665
13666
13667 void
13668 DropMenuEvent(selection, x, y)
13669      ChessSquare selection;
13670      int x, y;
13671 {
13672     ChessMove moveType;
13673
13674     switch (gameMode) {
13675       case IcsPlayingWhite:
13676       case MachinePlaysBlack:
13677         if (!WhiteOnMove(currentMove)) {
13678             DisplayMoveError(_("It is Black's turn"));
13679             return;
13680         }
13681         moveType = WhiteDrop;
13682         break;
13683       case IcsPlayingBlack:
13684       case MachinePlaysWhite:
13685         if (WhiteOnMove(currentMove)) {
13686             DisplayMoveError(_("It is White's turn"));
13687             return;
13688         }
13689         moveType = BlackDrop;
13690         break;
13691       case EditGame:
13692         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13693         break;
13694       default:
13695         return;
13696     }
13697
13698     if (moveType == BlackDrop && selection < BlackPawn) {
13699       selection = (ChessSquare) ((int) selection
13700                                  + (int) BlackPawn - (int) WhitePawn);
13701     }
13702     if (boards[currentMove][y][x] != EmptySquare) {
13703         DisplayMoveError(_("That square is occupied"));
13704         return;
13705     }
13706
13707     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13708 }
13709
13710 void
13711 AcceptEvent()
13712 {
13713     /* Accept a pending offer of any kind from opponent */
13714
13715     if (appData.icsActive) {
13716         SendToICS(ics_prefix);
13717         SendToICS("accept\n");
13718     } else if (cmailMsgLoaded) {
13719         if (currentMove == cmailOldMove &&
13720             commentList[cmailOldMove] != NULL &&
13721             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13722                    "Black offers a draw" : "White offers a draw")) {
13723             TruncateGame();
13724             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13725             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13726         } else {
13727             DisplayError(_("There is no pending offer on this move"), 0);
13728             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13729         }
13730     } else {
13731         /* Not used for offers from chess program */
13732     }
13733 }
13734
13735 void
13736 DeclineEvent()
13737 {
13738     /* Decline a pending offer of any kind from opponent */
13739
13740     if (appData.icsActive) {
13741         SendToICS(ics_prefix);
13742         SendToICS("decline\n");
13743     } else if (cmailMsgLoaded) {
13744         if (currentMove == cmailOldMove &&
13745             commentList[cmailOldMove] != NULL &&
13746             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13747                    "Black offers a draw" : "White offers a draw")) {
13748 #ifdef NOTDEF
13749             AppendComment(cmailOldMove, "Draw declined", TRUE);
13750             DisplayComment(cmailOldMove - 1, "Draw declined");
13751 #endif /*NOTDEF*/
13752         } else {
13753             DisplayError(_("There is no pending offer on this move"), 0);
13754         }
13755     } else {
13756         /* Not used for offers from chess program */
13757     }
13758 }
13759
13760 void
13761 RematchEvent()
13762 {
13763     /* Issue ICS rematch command */
13764     if (appData.icsActive) {
13765         SendToICS(ics_prefix);
13766         SendToICS("rematch\n");
13767     }
13768 }
13769
13770 void
13771 CallFlagEvent()
13772 {
13773     /* Call your opponent's flag (claim a win on time) */
13774     if (appData.icsActive) {
13775         SendToICS(ics_prefix);
13776         SendToICS("flag\n");
13777     } else {
13778         switch (gameMode) {
13779           default:
13780             return;
13781           case MachinePlaysWhite:
13782             if (whiteFlag) {
13783                 if (blackFlag)
13784                   GameEnds(GameIsDrawn, "Both players ran out of time",
13785                            GE_PLAYER);
13786                 else
13787                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13788             } else {
13789                 DisplayError(_("Your opponent is not out of time"), 0);
13790             }
13791             break;
13792           case MachinePlaysBlack:
13793             if (blackFlag) {
13794                 if (whiteFlag)
13795                   GameEnds(GameIsDrawn, "Both players ran out of time",
13796                            GE_PLAYER);
13797                 else
13798                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13799             } else {
13800                 DisplayError(_("Your opponent is not out of time"), 0);
13801             }
13802             break;
13803         }
13804     }
13805 }
13806
13807 void
13808 ClockClick(int which)
13809 {       // [HGM] code moved to back-end from winboard.c
13810         if(which) { // black clock
13811           if (gameMode == EditPosition || gameMode == IcsExamining) {
13812             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13813             SetBlackToPlayEvent();
13814           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13815           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13816           } else if (shiftKey) {
13817             AdjustClock(which, -1);
13818           } else if (gameMode == IcsPlayingWhite ||
13819                      gameMode == MachinePlaysBlack) {
13820             CallFlagEvent();
13821           }
13822         } else { // white clock
13823           if (gameMode == EditPosition || gameMode == IcsExamining) {
13824             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13825             SetWhiteToPlayEvent();
13826           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13827           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13828           } else if (shiftKey) {
13829             AdjustClock(which, -1);
13830           } else if (gameMode == IcsPlayingBlack ||
13831                    gameMode == MachinePlaysWhite) {
13832             CallFlagEvent();
13833           }
13834         }
13835 }
13836
13837 void
13838 DrawEvent()
13839 {
13840     /* Offer draw or accept pending draw offer from opponent */
13841
13842     if (appData.icsActive) {
13843         /* Note: tournament rules require draw offers to be
13844            made after you make your move but before you punch
13845            your clock.  Currently ICS doesn't let you do that;
13846            instead, you immediately punch your clock after making
13847            a move, but you can offer a draw at any time. */
13848
13849         SendToICS(ics_prefix);
13850         SendToICS("draw\n");
13851         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13852     } else if (cmailMsgLoaded) {
13853         if (currentMove == cmailOldMove &&
13854             commentList[cmailOldMove] != NULL &&
13855             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13856                    "Black offers a draw" : "White offers a draw")) {
13857             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13858             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13859         } else if (currentMove == cmailOldMove + 1) {
13860             char *offer = WhiteOnMove(cmailOldMove) ?
13861               "White offers a draw" : "Black offers a draw";
13862             AppendComment(currentMove, offer, TRUE);
13863             DisplayComment(currentMove - 1, offer);
13864             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13865         } else {
13866             DisplayError(_("You must make your move before offering a draw"), 0);
13867             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13868         }
13869     } else if (first.offeredDraw) {
13870         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13871     } else {
13872         if (first.sendDrawOffers) {
13873             SendToProgram("draw\n", &first);
13874             userOfferedDraw = TRUE;
13875         }
13876     }
13877 }
13878
13879 void
13880 AdjournEvent()
13881 {
13882     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13883
13884     if (appData.icsActive) {
13885         SendToICS(ics_prefix);
13886         SendToICS("adjourn\n");
13887     } else {
13888         /* Currently GNU Chess doesn't offer or accept Adjourns */
13889     }
13890 }
13891
13892
13893 void
13894 AbortEvent()
13895 {
13896     /* Offer Abort or accept pending Abort offer from opponent */
13897
13898     if (appData.icsActive) {
13899         SendToICS(ics_prefix);
13900         SendToICS("abort\n");
13901     } else {
13902         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13903     }
13904 }
13905
13906 void
13907 ResignEvent()
13908 {
13909     /* Resign.  You can do this even if it's not your turn. */
13910
13911     if (appData.icsActive) {
13912         SendToICS(ics_prefix);
13913         SendToICS("resign\n");
13914     } else {
13915         switch (gameMode) {
13916           case MachinePlaysWhite:
13917             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13918             break;
13919           case MachinePlaysBlack:
13920             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13921             break;
13922           case EditGame:
13923             if (cmailMsgLoaded) {
13924                 TruncateGame();
13925                 if (WhiteOnMove(cmailOldMove)) {
13926                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13927                 } else {
13928                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13929                 }
13930                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13931             }
13932             break;
13933           default:
13934             break;
13935         }
13936     }
13937 }
13938
13939
13940 void
13941 StopObservingEvent()
13942 {
13943     /* Stop observing current games */
13944     SendToICS(ics_prefix);
13945     SendToICS("unobserve\n");
13946 }
13947
13948 void
13949 StopExaminingEvent()
13950 {
13951     /* Stop observing current game */
13952     SendToICS(ics_prefix);
13953     SendToICS("unexamine\n");
13954 }
13955
13956 void
13957 ForwardInner(target)
13958      int target;
13959 {
13960     int limit;
13961
13962     if (appData.debugMode)
13963         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13964                 target, currentMove, forwardMostMove);
13965
13966     if (gameMode == EditPosition)
13967       return;
13968
13969     if (gameMode == PlayFromGameFile && !pausing)
13970       PauseEvent();
13971
13972     if (gameMode == IcsExamining && pausing)
13973       limit = pauseExamForwardMostMove;
13974     else
13975       limit = forwardMostMove;
13976
13977     if (target > limit) target = limit;
13978
13979     if (target > 0 && moveList[target - 1][0]) {
13980         int fromX, fromY, toX, toY;
13981         toX = moveList[target - 1][2] - AAA;
13982         toY = moveList[target - 1][3] - ONE;
13983         if (moveList[target - 1][1] == '@') {
13984             if (appData.highlightLastMove) {
13985                 SetHighlights(-1, -1, toX, toY);
13986             }
13987         } else {
13988             fromX = moveList[target - 1][0] - AAA;
13989             fromY = moveList[target - 1][1] - ONE;
13990             if (target == currentMove + 1) {
13991                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13992             }
13993             if (appData.highlightLastMove) {
13994                 SetHighlights(fromX, fromY, toX, toY);
13995             }
13996         }
13997     }
13998     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13999         gameMode == Training || gameMode == PlayFromGameFile ||
14000         gameMode == AnalyzeFile) {
14001         while (currentMove < target) {
14002             SendMoveToProgram(currentMove++, &first);
14003         }
14004     } else {
14005         currentMove = target;
14006     }
14007
14008     if (gameMode == EditGame || gameMode == EndOfGame) {
14009         whiteTimeRemaining = timeRemaining[0][currentMove];
14010         blackTimeRemaining = timeRemaining[1][currentMove];
14011     }
14012     DisplayBothClocks();
14013     DisplayMove(currentMove - 1);
14014     DrawPosition(FALSE, boards[currentMove]);
14015     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14016     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14017         DisplayComment(currentMove - 1, commentList[currentMove]);
14018     }
14019     DisplayBook(currentMove);
14020 }
14021
14022
14023 void
14024 ForwardEvent()
14025 {
14026     if (gameMode == IcsExamining && !pausing) {
14027         SendToICS(ics_prefix);
14028         SendToICS("forward\n");
14029     } else {
14030         ForwardInner(currentMove + 1);
14031     }
14032 }
14033
14034 void
14035 ToEndEvent()
14036 {
14037     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14038         /* to optimze, we temporarily turn off analysis mode while we feed
14039          * the remaining moves to the engine. Otherwise we get analysis output
14040          * after each move.
14041          */
14042         if (first.analysisSupport) {
14043           SendToProgram("exit\nforce\n", &first);
14044           first.analyzing = FALSE;
14045         }
14046     }
14047
14048     if (gameMode == IcsExamining && !pausing) {
14049         SendToICS(ics_prefix);
14050         SendToICS("forward 999999\n");
14051     } else {
14052         ForwardInner(forwardMostMove);
14053     }
14054
14055     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14056         /* we have fed all the moves, so reactivate analysis mode */
14057         SendToProgram("analyze\n", &first);
14058         first.analyzing = TRUE;
14059         /*first.maybeThinking = TRUE;*/
14060         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14061     }
14062 }
14063
14064 void
14065 BackwardInner(target)
14066      int target;
14067 {
14068     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14069
14070     if (appData.debugMode)
14071         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14072                 target, currentMove, forwardMostMove);
14073
14074     if (gameMode == EditPosition) return;
14075     if (currentMove <= backwardMostMove) {
14076         ClearHighlights();
14077         DrawPosition(full_redraw, boards[currentMove]);
14078         return;
14079     }
14080     if (gameMode == PlayFromGameFile && !pausing)
14081       PauseEvent();
14082
14083     if (moveList[target][0]) {
14084         int fromX, fromY, toX, toY;
14085         toX = moveList[target][2] - AAA;
14086         toY = moveList[target][3] - ONE;
14087         if (moveList[target][1] == '@') {
14088             if (appData.highlightLastMove) {
14089                 SetHighlights(-1, -1, toX, toY);
14090             }
14091         } else {
14092             fromX = moveList[target][0] - AAA;
14093             fromY = moveList[target][1] - ONE;
14094             if (target == currentMove - 1) {
14095                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14096             }
14097             if (appData.highlightLastMove) {
14098                 SetHighlights(fromX, fromY, toX, toY);
14099             }
14100         }
14101     }
14102     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14103         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14104         while (currentMove > target) {
14105             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14106                 // null move cannot be undone. Reload program with move history before it.
14107                 int i;
14108                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14109                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14110                 }
14111                 SendBoard(&first, i); 
14112                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14113                 break;
14114             }
14115             SendToProgram("undo\n", &first);
14116             currentMove--;
14117         }
14118     } else {
14119         currentMove = target;
14120     }
14121
14122     if (gameMode == EditGame || gameMode == EndOfGame) {
14123         whiteTimeRemaining = timeRemaining[0][currentMove];
14124         blackTimeRemaining = timeRemaining[1][currentMove];
14125     }
14126     DisplayBothClocks();
14127     DisplayMove(currentMove - 1);
14128     DrawPosition(full_redraw, boards[currentMove]);
14129     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14130     // [HGM] PV info: routine tests if comment empty
14131     DisplayComment(currentMove - 1, commentList[currentMove]);
14132     DisplayBook(currentMove);
14133 }
14134
14135 void
14136 BackwardEvent()
14137 {
14138     if (gameMode == IcsExamining && !pausing) {
14139         SendToICS(ics_prefix);
14140         SendToICS("backward\n");
14141     } else {
14142         BackwardInner(currentMove - 1);
14143     }
14144 }
14145
14146 void
14147 ToStartEvent()
14148 {
14149     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14150         /* to optimize, we temporarily turn off analysis mode while we undo
14151          * all the moves. Otherwise we get analysis output after each undo.
14152          */
14153         if (first.analysisSupport) {
14154           SendToProgram("exit\nforce\n", &first);
14155           first.analyzing = FALSE;
14156         }
14157     }
14158
14159     if (gameMode == IcsExamining && !pausing) {
14160         SendToICS(ics_prefix);
14161         SendToICS("backward 999999\n");
14162     } else {
14163         BackwardInner(backwardMostMove);
14164     }
14165
14166     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14167         /* we have fed all the moves, so reactivate analysis mode */
14168         SendToProgram("analyze\n", &first);
14169         first.analyzing = TRUE;
14170         /*first.maybeThinking = TRUE;*/
14171         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14172     }
14173 }
14174
14175 void
14176 ToNrEvent(int to)
14177 {
14178   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14179   if (to >= forwardMostMove) to = forwardMostMove;
14180   if (to <= backwardMostMove) to = backwardMostMove;
14181   if (to < currentMove) {
14182     BackwardInner(to);
14183   } else {
14184     ForwardInner(to);
14185   }
14186 }
14187
14188 void
14189 RevertEvent(Boolean annotate)
14190 {
14191     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14192         return;
14193     }
14194     if (gameMode != IcsExamining) {
14195         DisplayError(_("You are not examining a game"), 0);
14196         return;
14197     }
14198     if (pausing) {
14199         DisplayError(_("You can't revert while pausing"), 0);
14200         return;
14201     }
14202     SendToICS(ics_prefix);
14203     SendToICS("revert\n");
14204 }
14205
14206 void
14207 RetractMoveEvent()
14208 {
14209     switch (gameMode) {
14210       case MachinePlaysWhite:
14211       case MachinePlaysBlack:
14212         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14213             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14214             return;
14215         }
14216         if (forwardMostMove < 2) return;
14217         currentMove = forwardMostMove = forwardMostMove - 2;
14218         whiteTimeRemaining = timeRemaining[0][currentMove];
14219         blackTimeRemaining = timeRemaining[1][currentMove];
14220         DisplayBothClocks();
14221         DisplayMove(currentMove - 1);
14222         ClearHighlights();/*!! could figure this out*/
14223         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14224         SendToProgram("remove\n", &first);
14225         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14226         break;
14227
14228       case BeginningOfGame:
14229       default:
14230         break;
14231
14232       case IcsPlayingWhite:
14233       case IcsPlayingBlack:
14234         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14235             SendToICS(ics_prefix);
14236             SendToICS("takeback 2\n");
14237         } else {
14238             SendToICS(ics_prefix);
14239             SendToICS("takeback 1\n");
14240         }
14241         break;
14242     }
14243 }
14244
14245 void
14246 MoveNowEvent()
14247 {
14248     ChessProgramState *cps;
14249
14250     switch (gameMode) {
14251       case MachinePlaysWhite:
14252         if (!WhiteOnMove(forwardMostMove)) {
14253             DisplayError(_("It is your turn"), 0);
14254             return;
14255         }
14256         cps = &first;
14257         break;
14258       case MachinePlaysBlack:
14259         if (WhiteOnMove(forwardMostMove)) {
14260             DisplayError(_("It is your turn"), 0);
14261             return;
14262         }
14263         cps = &first;
14264         break;
14265       case TwoMachinesPlay:
14266         if (WhiteOnMove(forwardMostMove) ==
14267             (first.twoMachinesColor[0] == 'w')) {
14268             cps = &first;
14269         } else {
14270             cps = &second;
14271         }
14272         break;
14273       case BeginningOfGame:
14274       default:
14275         return;
14276     }
14277     SendToProgram("?\n", cps);
14278 }
14279
14280 void
14281 TruncateGameEvent()
14282 {
14283     EditGameEvent();
14284     if (gameMode != EditGame) return;
14285     TruncateGame();
14286 }
14287
14288 void
14289 TruncateGame()
14290 {
14291     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14292     if (forwardMostMove > currentMove) {
14293         if (gameInfo.resultDetails != NULL) {
14294             free(gameInfo.resultDetails);
14295             gameInfo.resultDetails = NULL;
14296             gameInfo.result = GameUnfinished;
14297         }
14298         forwardMostMove = currentMove;
14299         HistorySet(parseList, backwardMostMove, forwardMostMove,
14300                    currentMove-1);
14301     }
14302 }
14303
14304 void
14305 HintEvent()
14306 {
14307     if (appData.noChessProgram) return;
14308     switch (gameMode) {
14309       case MachinePlaysWhite:
14310         if (WhiteOnMove(forwardMostMove)) {
14311             DisplayError(_("Wait until your turn"), 0);
14312             return;
14313         }
14314         break;
14315       case BeginningOfGame:
14316       case MachinePlaysBlack:
14317         if (!WhiteOnMove(forwardMostMove)) {
14318             DisplayError(_("Wait until your turn"), 0);
14319             return;
14320         }
14321         break;
14322       default:
14323         DisplayError(_("No hint available"), 0);
14324         return;
14325     }
14326     SendToProgram("hint\n", &first);
14327     hintRequested = TRUE;
14328 }
14329
14330 void
14331 BookEvent()
14332 {
14333     if (appData.noChessProgram) return;
14334     switch (gameMode) {
14335       case MachinePlaysWhite:
14336         if (WhiteOnMove(forwardMostMove)) {
14337             DisplayError(_("Wait until your turn"), 0);
14338             return;
14339         }
14340         break;
14341       case BeginningOfGame:
14342       case MachinePlaysBlack:
14343         if (!WhiteOnMove(forwardMostMove)) {
14344             DisplayError(_("Wait until your turn"), 0);
14345             return;
14346         }
14347         break;
14348       case EditPosition:
14349         EditPositionDone(TRUE);
14350         break;
14351       case TwoMachinesPlay:
14352         return;
14353       default:
14354         break;
14355     }
14356     SendToProgram("bk\n", &first);
14357     bookOutput[0] = NULLCHAR;
14358     bookRequested = TRUE;
14359 }
14360
14361 void
14362 AboutGameEvent()
14363 {
14364     char *tags = PGNTags(&gameInfo);
14365     TagsPopUp(tags, CmailMsg());
14366     free(tags);
14367 }
14368
14369 /* end button procedures */
14370
14371 void
14372 PrintPosition(fp, move)
14373      FILE *fp;
14374      int move;
14375 {
14376     int i, j;
14377
14378     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14379         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14380             char c = PieceToChar(boards[move][i][j]);
14381             fputc(c == 'x' ? '.' : c, fp);
14382             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14383         }
14384     }
14385     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14386       fprintf(fp, "white to play\n");
14387     else
14388       fprintf(fp, "black to play\n");
14389 }
14390
14391 void
14392 PrintOpponents(fp)
14393      FILE *fp;
14394 {
14395     if (gameInfo.white != NULL) {
14396         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14397     } else {
14398         fprintf(fp, "\n");
14399     }
14400 }
14401
14402 /* Find last component of program's own name, using some heuristics */
14403 void
14404 TidyProgramName(prog, host, buf)
14405      char *prog, *host, buf[MSG_SIZ];
14406 {
14407     char *p, *q;
14408     int local = (strcmp(host, "localhost") == 0);
14409     while (!local && (p = strchr(prog, ';')) != NULL) {
14410         p++;
14411         while (*p == ' ') p++;
14412         prog = p;
14413     }
14414     if (*prog == '"' || *prog == '\'') {
14415         q = strchr(prog + 1, *prog);
14416     } else {
14417         q = strchr(prog, ' ');
14418     }
14419     if (q == NULL) q = prog + strlen(prog);
14420     p = q;
14421     while (p >= prog && *p != '/' && *p != '\\') p--;
14422     p++;
14423     if(p == prog && *p == '"') p++;
14424     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14425     memcpy(buf, p, q - p);
14426     buf[q - p] = NULLCHAR;
14427     if (!local) {
14428         strcat(buf, "@");
14429         strcat(buf, host);
14430     }
14431 }
14432
14433 char *
14434 TimeControlTagValue()
14435 {
14436     char buf[MSG_SIZ];
14437     if (!appData.clockMode) {
14438       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14439     } else if (movesPerSession > 0) {
14440       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14441     } else if (timeIncrement == 0) {
14442       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14443     } else {
14444       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14445     }
14446     return StrSave(buf);
14447 }
14448
14449 void
14450 SetGameInfo()
14451 {
14452     /* This routine is used only for certain modes */
14453     VariantClass v = gameInfo.variant;
14454     ChessMove r = GameUnfinished;
14455     char *p = NULL;
14456
14457     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14458         r = gameInfo.result;
14459         p = gameInfo.resultDetails;
14460         gameInfo.resultDetails = NULL;
14461     }
14462     ClearGameInfo(&gameInfo);
14463     gameInfo.variant = v;
14464
14465     switch (gameMode) {
14466       case MachinePlaysWhite:
14467         gameInfo.event = StrSave( appData.pgnEventHeader );
14468         gameInfo.site = StrSave(HostName());
14469         gameInfo.date = PGNDate();
14470         gameInfo.round = StrSave("-");
14471         gameInfo.white = StrSave(first.tidy);
14472         gameInfo.black = StrSave(UserName());
14473         gameInfo.timeControl = TimeControlTagValue();
14474         break;
14475
14476       case MachinePlaysBlack:
14477         gameInfo.event = StrSave( appData.pgnEventHeader );
14478         gameInfo.site = StrSave(HostName());
14479         gameInfo.date = PGNDate();
14480         gameInfo.round = StrSave("-");
14481         gameInfo.white = StrSave(UserName());
14482         gameInfo.black = StrSave(first.tidy);
14483         gameInfo.timeControl = TimeControlTagValue();
14484         break;
14485
14486       case TwoMachinesPlay:
14487         gameInfo.event = StrSave( appData.pgnEventHeader );
14488         gameInfo.site = StrSave(HostName());
14489         gameInfo.date = PGNDate();
14490         if (roundNr > 0) {
14491             char buf[MSG_SIZ];
14492             snprintf(buf, MSG_SIZ, "%d", roundNr);
14493             gameInfo.round = StrSave(buf);
14494         } else {
14495             gameInfo.round = StrSave("-");
14496         }
14497         if (first.twoMachinesColor[0] == 'w') {
14498             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14499             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14500         } else {
14501             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14502             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14503         }
14504         gameInfo.timeControl = TimeControlTagValue();
14505         break;
14506
14507       case EditGame:
14508         gameInfo.event = StrSave("Edited game");
14509         gameInfo.site = StrSave(HostName());
14510         gameInfo.date = PGNDate();
14511         gameInfo.round = StrSave("-");
14512         gameInfo.white = StrSave("-");
14513         gameInfo.black = StrSave("-");
14514         gameInfo.result = r;
14515         gameInfo.resultDetails = p;
14516         break;
14517
14518       case EditPosition:
14519         gameInfo.event = StrSave("Edited position");
14520         gameInfo.site = StrSave(HostName());
14521         gameInfo.date = PGNDate();
14522         gameInfo.round = StrSave("-");
14523         gameInfo.white = StrSave("-");
14524         gameInfo.black = StrSave("-");
14525         break;
14526
14527       case IcsPlayingWhite:
14528       case IcsPlayingBlack:
14529       case IcsObserving:
14530       case IcsExamining:
14531         break;
14532
14533       case PlayFromGameFile:
14534         gameInfo.event = StrSave("Game from non-PGN file");
14535         gameInfo.site = StrSave(HostName());
14536         gameInfo.date = PGNDate();
14537         gameInfo.round = StrSave("-");
14538         gameInfo.white = StrSave("?");
14539         gameInfo.black = StrSave("?");
14540         break;
14541
14542       default:
14543         break;
14544     }
14545 }
14546
14547 void
14548 ReplaceComment(index, text)
14549      int index;
14550      char *text;
14551 {
14552     int len;
14553     char *p;
14554     float score;
14555
14556     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14557        pvInfoList[index-1].depth == len &&
14558        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14559        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14560     while (*text == '\n') text++;
14561     len = strlen(text);
14562     while (len > 0 && text[len - 1] == '\n') len--;
14563
14564     if (commentList[index] != NULL)
14565       free(commentList[index]);
14566
14567     if (len == 0) {
14568         commentList[index] = NULL;
14569         return;
14570     }
14571   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14572       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14573       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14574     commentList[index] = (char *) malloc(len + 2);
14575     strncpy(commentList[index], text, len);
14576     commentList[index][len] = '\n';
14577     commentList[index][len + 1] = NULLCHAR;
14578   } else {
14579     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14580     char *p;
14581     commentList[index] = (char *) malloc(len + 7);
14582     safeStrCpy(commentList[index], "{\n", 3);
14583     safeStrCpy(commentList[index]+2, text, len+1);
14584     commentList[index][len+2] = NULLCHAR;
14585     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14586     strcat(commentList[index], "\n}\n");
14587   }
14588 }
14589
14590 void
14591 CrushCRs(text)
14592      char *text;
14593 {
14594   char *p = text;
14595   char *q = text;
14596   char ch;
14597
14598   do {
14599     ch = *p++;
14600     if (ch == '\r') continue;
14601     *q++ = ch;
14602   } while (ch != '\0');
14603 }
14604
14605 void
14606 AppendComment(index, text, addBraces)
14607      int index;
14608      char *text;
14609      Boolean addBraces; // [HGM] braces: tells if we should add {}
14610 {
14611     int oldlen, len;
14612     char *old;
14613
14614 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14615     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14616
14617     CrushCRs(text);
14618     while (*text == '\n') text++;
14619     len = strlen(text);
14620     while (len > 0 && text[len - 1] == '\n') len--;
14621
14622     if (len == 0) return;
14623
14624     if (commentList[index] != NULL) {
14625         old = commentList[index];
14626         oldlen = strlen(old);
14627         while(commentList[index][oldlen-1] ==  '\n')
14628           commentList[index][--oldlen] = NULLCHAR;
14629         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14630         safeStrCpy(commentList[index], old, oldlen + len + 6);
14631         free(old);
14632         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14633         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14634           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14635           while (*text == '\n') { text++; len--; }
14636           commentList[index][--oldlen] = NULLCHAR;
14637       }
14638         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14639         else          strcat(commentList[index], "\n");
14640         strcat(commentList[index], text);
14641         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14642         else          strcat(commentList[index], "\n");
14643     } else {
14644         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14645         if(addBraces)
14646           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14647         else commentList[index][0] = NULLCHAR;
14648         strcat(commentList[index], text);
14649         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14650         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14651     }
14652 }
14653
14654 static char * FindStr( char * text, char * sub_text )
14655 {
14656     char * result = strstr( text, sub_text );
14657
14658     if( result != NULL ) {
14659         result += strlen( sub_text );
14660     }
14661
14662     return result;
14663 }
14664
14665 /* [AS] Try to extract PV info from PGN comment */
14666 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14667 char *GetInfoFromComment( int index, char * text )
14668 {
14669     char * sep = text, *p;
14670
14671     if( text != NULL && index > 0 ) {
14672         int score = 0;
14673         int depth = 0;
14674         int time = -1, sec = 0, deci;
14675         char * s_eval = FindStr( text, "[%eval " );
14676         char * s_emt = FindStr( text, "[%emt " );
14677
14678         if( s_eval != NULL || s_emt != NULL ) {
14679             /* New style */
14680             char delim;
14681
14682             if( s_eval != NULL ) {
14683                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14684                     return text;
14685                 }
14686
14687                 if( delim != ']' ) {
14688                     return text;
14689                 }
14690             }
14691
14692             if( s_emt != NULL ) {
14693             }
14694                 return text;
14695         }
14696         else {
14697             /* We expect something like: [+|-]nnn.nn/dd */
14698             int score_lo = 0;
14699
14700             if(*text != '{') return text; // [HGM] braces: must be normal comment
14701
14702             sep = strchr( text, '/' );
14703             if( sep == NULL || sep < (text+4) ) {
14704                 return text;
14705             }
14706
14707             p = text;
14708             if(p[1] == '(') { // comment starts with PV
14709                p = strchr(p, ')'); // locate end of PV
14710                if(p == NULL || sep < p+5) return text;
14711                // at this point we have something like "{(.*) +0.23/6 ..."
14712                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14713                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14714                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14715             }
14716             time = -1; sec = -1; deci = -1;
14717             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14718                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14719                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14720                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14721                 return text;
14722             }
14723
14724             if( score_lo < 0 || score_lo >= 100 ) {
14725                 return text;
14726             }
14727
14728             if(sec >= 0) time = 600*time + 10*sec; else
14729             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14730
14731             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14732
14733             /* [HGM] PV time: now locate end of PV info */
14734             while( *++sep >= '0' && *sep <= '9'); // strip depth
14735             if(time >= 0)
14736             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14737             if(sec >= 0)
14738             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14739             if(deci >= 0)
14740             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14741             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14742         }
14743
14744         if( depth <= 0 ) {
14745             return text;
14746         }
14747
14748         if( time < 0 ) {
14749             time = -1;
14750         }
14751
14752         pvInfoList[index-1].depth = depth;
14753         pvInfoList[index-1].score = score;
14754         pvInfoList[index-1].time  = 10*time; // centi-sec
14755         if(*sep == '}') *sep = 0; else *--sep = '{';
14756         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14757     }
14758     return sep;
14759 }
14760
14761 void
14762 SendToProgram(message, cps)
14763      char *message;
14764      ChessProgramState *cps;
14765 {
14766     int count, outCount, error;
14767     char buf[MSG_SIZ];
14768
14769     if (cps->pr == NULL) return;
14770     Attention(cps);
14771
14772     if (appData.debugMode) {
14773         TimeMark now;
14774         GetTimeMark(&now);
14775         fprintf(debugFP, "%ld >%-6s: %s",
14776                 SubtractTimeMarks(&now, &programStartTime),
14777                 cps->which, message);
14778     }
14779
14780     count = strlen(message);
14781     outCount = OutputToProcess(cps->pr, message, count, &error);
14782     if (outCount < count && !exiting
14783                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14784       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14785       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14786         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14787             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14788                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14789                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14790                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14791             } else {
14792                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14793                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14794                 gameInfo.result = res;
14795             }
14796             gameInfo.resultDetails = StrSave(buf);
14797         }
14798         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14799         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14800     }
14801 }
14802
14803 void
14804 ReceiveFromProgram(isr, closure, message, count, error)
14805      InputSourceRef isr;
14806      VOIDSTAR closure;
14807      char *message;
14808      int count;
14809      int error;
14810 {
14811     char *end_str;
14812     char buf[MSG_SIZ];
14813     ChessProgramState *cps = (ChessProgramState *)closure;
14814
14815     if (isr != cps->isr) return; /* Killed intentionally */
14816     if (count <= 0) {
14817         if (count == 0) {
14818             RemoveInputSource(cps->isr);
14819             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14820             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14821                     _(cps->which), cps->program);
14822         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14823                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14824                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14825                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14826                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14827                 } else {
14828                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14829                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14830                     gameInfo.result = res;
14831                 }
14832                 gameInfo.resultDetails = StrSave(buf);
14833             }
14834             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14835             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14836         } else {
14837             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14838                     _(cps->which), cps->program);
14839             RemoveInputSource(cps->isr);
14840
14841             /* [AS] Program is misbehaving badly... kill it */
14842             if( count == -2 ) {
14843                 DestroyChildProcess( cps->pr, 9 );
14844                 cps->pr = NoProc;
14845             }
14846
14847             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14848         }
14849         return;
14850     }
14851
14852     if ((end_str = strchr(message, '\r')) != NULL)
14853       *end_str = NULLCHAR;
14854     if ((end_str = strchr(message, '\n')) != NULL)
14855       *end_str = NULLCHAR;
14856
14857     if (appData.debugMode) {
14858         TimeMark now; int print = 1;
14859         char *quote = ""; char c; int i;
14860
14861         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14862                 char start = message[0];
14863                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14864                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14865                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14866                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14867                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14868                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14869                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14870                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14871                    sscanf(message, "hint: %c", &c)!=1 && 
14872                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14873                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14874                     print = (appData.engineComments >= 2);
14875                 }
14876                 message[0] = start; // restore original message
14877         }
14878         if(print) {
14879                 GetTimeMark(&now);
14880                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14881                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14882                         quote,
14883                         message);
14884         }
14885     }
14886
14887     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14888     if (appData.icsEngineAnalyze) {
14889         if (strstr(message, "whisper") != NULL ||
14890              strstr(message, "kibitz") != NULL ||
14891             strstr(message, "tellics") != NULL) return;
14892     }
14893
14894     HandleMachineMove(message, cps);
14895 }
14896
14897
14898 void
14899 SendTimeControl(cps, mps, tc, inc, sd, st)
14900      ChessProgramState *cps;
14901      int mps, inc, sd, st;
14902      long tc;
14903 {
14904     char buf[MSG_SIZ];
14905     int seconds;
14906
14907     if( timeControl_2 > 0 ) {
14908         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14909             tc = timeControl_2;
14910         }
14911     }
14912     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14913     inc /= cps->timeOdds;
14914     st  /= cps->timeOdds;
14915
14916     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14917
14918     if (st > 0) {
14919       /* Set exact time per move, normally using st command */
14920       if (cps->stKludge) {
14921         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14922         seconds = st % 60;
14923         if (seconds == 0) {
14924           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14925         } else {
14926           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14927         }
14928       } else {
14929         snprintf(buf, MSG_SIZ, "st %d\n", st);
14930       }
14931     } else {
14932       /* Set conventional or incremental time control, using level command */
14933       if (seconds == 0) {
14934         /* Note old gnuchess bug -- minutes:seconds used to not work.
14935            Fixed in later versions, but still avoid :seconds
14936            when seconds is 0. */
14937         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14938       } else {
14939         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14940                  seconds, inc/1000.);
14941       }
14942     }
14943     SendToProgram(buf, cps);
14944
14945     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14946     /* Orthogonally, limit search to given depth */
14947     if (sd > 0) {
14948       if (cps->sdKludge) {
14949         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14950       } else {
14951         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14952       }
14953       SendToProgram(buf, cps);
14954     }
14955
14956     if(cps->nps >= 0) { /* [HGM] nps */
14957         if(cps->supportsNPS == FALSE)
14958           cps->nps = -1; // don't use if engine explicitly says not supported!
14959         else {
14960           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14961           SendToProgram(buf, cps);
14962         }
14963     }
14964 }
14965
14966 ChessProgramState *WhitePlayer()
14967 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14968 {
14969     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14970        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14971         return &second;
14972     return &first;
14973 }
14974
14975 void
14976 SendTimeRemaining(cps, machineWhite)
14977      ChessProgramState *cps;
14978      int /*boolean*/ machineWhite;
14979 {
14980     char message[MSG_SIZ];
14981     long time, otime;
14982
14983     /* Note: this routine must be called when the clocks are stopped
14984        or when they have *just* been set or switched; otherwise
14985        it will be off by the time since the current tick started.
14986     */
14987     if (machineWhite) {
14988         time = whiteTimeRemaining / 10;
14989         otime = blackTimeRemaining / 10;
14990     } else {
14991         time = blackTimeRemaining / 10;
14992         otime = whiteTimeRemaining / 10;
14993     }
14994     /* [HGM] translate opponent's time by time-odds factor */
14995     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14996     if (appData.debugMode) {
14997         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14998     }
14999
15000     if (time <= 0) time = 1;
15001     if (otime <= 0) otime = 1;
15002
15003     snprintf(message, MSG_SIZ, "time %ld\n", time);
15004     SendToProgram(message, cps);
15005
15006     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15007     SendToProgram(message, cps);
15008 }
15009
15010 int
15011 BoolFeature(p, name, loc, cps)
15012      char **p;
15013      char *name;
15014      int *loc;
15015      ChessProgramState *cps;
15016 {
15017   char buf[MSG_SIZ];
15018   int len = strlen(name);
15019   int val;
15020
15021   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15022     (*p) += len + 1;
15023     sscanf(*p, "%d", &val);
15024     *loc = (val != 0);
15025     while (**p && **p != ' ')
15026       (*p)++;
15027     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15028     SendToProgram(buf, cps);
15029     return TRUE;
15030   }
15031   return FALSE;
15032 }
15033
15034 int
15035 IntFeature(p, name, loc, cps)
15036      char **p;
15037      char *name;
15038      int *loc;
15039      ChessProgramState *cps;
15040 {
15041   char buf[MSG_SIZ];
15042   int len = strlen(name);
15043   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15044     (*p) += len + 1;
15045     sscanf(*p, "%d", loc);
15046     while (**p && **p != ' ') (*p)++;
15047     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15048     SendToProgram(buf, cps);
15049     return TRUE;
15050   }
15051   return FALSE;
15052 }
15053
15054 int
15055 StringFeature(p, name, loc, cps)
15056      char **p;
15057      char *name;
15058      char loc[];
15059      ChessProgramState *cps;
15060 {
15061   char buf[MSG_SIZ];
15062   int len = strlen(name);
15063   if (strncmp((*p), name, len) == 0
15064       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15065     (*p) += len + 2;
15066     sscanf(*p, "%[^\"]", loc);
15067     while (**p && **p != '\"') (*p)++;
15068     if (**p == '\"') (*p)++;
15069     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15070     SendToProgram(buf, cps);
15071     return TRUE;
15072   }
15073   return FALSE;
15074 }
15075
15076 int
15077 ParseOption(Option *opt, ChessProgramState *cps)
15078 // [HGM] options: process the string that defines an engine option, and determine
15079 // name, type, default value, and allowed value range
15080 {
15081         char *p, *q, buf[MSG_SIZ];
15082         int n, min = (-1)<<31, max = 1<<31, def;
15083
15084         if(p = strstr(opt->name, " -spin ")) {
15085             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15086             if(max < min) max = min; // enforce consistency
15087             if(def < min) def = min;
15088             if(def > max) def = max;
15089             opt->value = def;
15090             opt->min = min;
15091             opt->max = max;
15092             opt->type = Spin;
15093         } else if((p = strstr(opt->name, " -slider "))) {
15094             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15095             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15096             if(max < min) max = min; // enforce consistency
15097             if(def < min) def = min;
15098             if(def > max) def = max;
15099             opt->value = def;
15100             opt->min = min;
15101             opt->max = max;
15102             opt->type = Spin; // Slider;
15103         } else if((p = strstr(opt->name, " -string "))) {
15104             opt->textValue = p+9;
15105             opt->type = TextBox;
15106         } else if((p = strstr(opt->name, " -file "))) {
15107             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15108             opt->textValue = p+7;
15109             opt->type = FileName; // FileName;
15110         } else if((p = strstr(opt->name, " -path "))) {
15111             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15112             opt->textValue = p+7;
15113             opt->type = PathName; // PathName;
15114         } else if(p = strstr(opt->name, " -check ")) {
15115             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15116             opt->value = (def != 0);
15117             opt->type = CheckBox;
15118         } else if(p = strstr(opt->name, " -combo ")) {
15119             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15120             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15121             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15122             opt->value = n = 0;
15123             while(q = StrStr(q, " /// ")) {
15124                 n++; *q = 0;    // count choices, and null-terminate each of them
15125                 q += 5;
15126                 if(*q == '*') { // remember default, which is marked with * prefix
15127                     q++;
15128                     opt->value = n;
15129                 }
15130                 cps->comboList[cps->comboCnt++] = q;
15131             }
15132             cps->comboList[cps->comboCnt++] = NULL;
15133             opt->max = n + 1;
15134             opt->type = ComboBox;
15135         } else if(p = strstr(opt->name, " -button")) {
15136             opt->type = Button;
15137         } else if(p = strstr(opt->name, " -save")) {
15138             opt->type = SaveButton;
15139         } else return FALSE;
15140         *p = 0; // terminate option name
15141         // now look if the command-line options define a setting for this engine option.
15142         if(cps->optionSettings && cps->optionSettings[0])
15143             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15144         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15145           snprintf(buf, MSG_SIZ, "option %s", p);
15146                 if(p = strstr(buf, ",")) *p = 0;
15147                 if(q = strchr(buf, '=')) switch(opt->type) {
15148                     case ComboBox:
15149                         for(n=0; n<opt->max; n++)
15150                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15151                         break;
15152                     case TextBox:
15153                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15154                         break;
15155                     case Spin:
15156                     case CheckBox:
15157                         opt->value = atoi(q+1);
15158                     default:
15159                         break;
15160                 }
15161                 strcat(buf, "\n");
15162                 SendToProgram(buf, cps);
15163         }
15164         return TRUE;
15165 }
15166
15167 void
15168 FeatureDone(cps, val)
15169      ChessProgramState* cps;
15170      int val;
15171 {
15172   DelayedEventCallback cb = GetDelayedEvent();
15173   if ((cb == InitBackEnd3 && cps == &first) ||
15174       (cb == SettingsMenuIfReady && cps == &second) ||
15175       (cb == LoadEngine) ||
15176       (cb == TwoMachinesEventIfReady)) {
15177     CancelDelayedEvent();
15178     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15179   }
15180   cps->initDone = val;
15181 }
15182
15183 /* Parse feature command from engine */
15184 void
15185 ParseFeatures(args, cps)
15186      char* args;
15187      ChessProgramState *cps;
15188 {
15189   char *p = args;
15190   char *q;
15191   int val;
15192   char buf[MSG_SIZ];
15193
15194   for (;;) {
15195     while (*p == ' ') p++;
15196     if (*p == NULLCHAR) return;
15197
15198     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15199     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15200     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15201     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15202     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15203     if (BoolFeature(&p, "reuse", &val, cps)) {
15204       /* Engine can disable reuse, but can't enable it if user said no */
15205       if (!val) cps->reuse = FALSE;
15206       continue;
15207     }
15208     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15209     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15210       if (gameMode == TwoMachinesPlay) {
15211         DisplayTwoMachinesTitle();
15212       } else {
15213         DisplayTitle("");
15214       }
15215       continue;
15216     }
15217     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15218     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15219     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15220     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15221     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15222     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15223     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15224     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15225     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15226     if (IntFeature(&p, "done", &val, cps)) {
15227       FeatureDone(cps, val);
15228       continue;
15229     }
15230     /* Added by Tord: */
15231     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15232     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15233     /* End of additions by Tord */
15234
15235     /* [HGM] added features: */
15236     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15237     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15238     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15239     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15240     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15241     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15242     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15243         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15244           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15245             SendToProgram(buf, cps);
15246             continue;
15247         }
15248         if(cps->nrOptions >= MAX_OPTIONS) {
15249             cps->nrOptions--;
15250             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15251             DisplayError(buf, 0);
15252         }
15253         continue;
15254     }
15255     /* End of additions by HGM */
15256
15257     /* unknown feature: complain and skip */
15258     q = p;
15259     while (*q && *q != '=') q++;
15260     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15261     SendToProgram(buf, cps);
15262     p = q;
15263     if (*p == '=') {
15264       p++;
15265       if (*p == '\"') {
15266         p++;
15267         while (*p && *p != '\"') p++;
15268         if (*p == '\"') p++;
15269       } else {
15270         while (*p && *p != ' ') p++;
15271       }
15272     }
15273   }
15274
15275 }
15276
15277 void
15278 PeriodicUpdatesEvent(newState)
15279      int newState;
15280 {
15281     if (newState == appData.periodicUpdates)
15282       return;
15283
15284     appData.periodicUpdates=newState;
15285
15286     /* Display type changes, so update it now */
15287 //    DisplayAnalysis();
15288
15289     /* Get the ball rolling again... */
15290     if (newState) {
15291         AnalysisPeriodicEvent(1);
15292         StartAnalysisClock();
15293     }
15294 }
15295
15296 void
15297 PonderNextMoveEvent(newState)
15298      int newState;
15299 {
15300     if (newState == appData.ponderNextMove) return;
15301     if (gameMode == EditPosition) EditPositionDone(TRUE);
15302     if (newState) {
15303         SendToProgram("hard\n", &first);
15304         if (gameMode == TwoMachinesPlay) {
15305             SendToProgram("hard\n", &second);
15306         }
15307     } else {
15308         SendToProgram("easy\n", &first);
15309         thinkOutput[0] = NULLCHAR;
15310         if (gameMode == TwoMachinesPlay) {
15311             SendToProgram("easy\n", &second);
15312         }
15313     }
15314     appData.ponderNextMove = newState;
15315 }
15316
15317 void
15318 NewSettingEvent(option, feature, command, value)
15319      char *command;
15320      int option, value, *feature;
15321 {
15322     char buf[MSG_SIZ];
15323
15324     if (gameMode == EditPosition) EditPositionDone(TRUE);
15325     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15326     if(feature == NULL || *feature) SendToProgram(buf, &first);
15327     if (gameMode == TwoMachinesPlay) {
15328         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15329     }
15330 }
15331
15332 void
15333 ShowThinkingEvent()
15334 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15335 {
15336     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15337     int newState = appData.showThinking
15338         // [HGM] thinking: other features now need thinking output as well
15339         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15340
15341     if (oldState == newState) return;
15342     oldState = newState;
15343     if (gameMode == EditPosition) EditPositionDone(TRUE);
15344     if (oldState) {
15345         SendToProgram("post\n", &first);
15346         if (gameMode == TwoMachinesPlay) {
15347             SendToProgram("post\n", &second);
15348         }
15349     } else {
15350         SendToProgram("nopost\n", &first);
15351         thinkOutput[0] = NULLCHAR;
15352         if (gameMode == TwoMachinesPlay) {
15353             SendToProgram("nopost\n", &second);
15354         }
15355     }
15356 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15357 }
15358
15359 void
15360 AskQuestionEvent(title, question, replyPrefix, which)
15361      char *title; char *question; char *replyPrefix; char *which;
15362 {
15363   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15364   if (pr == NoProc) return;
15365   AskQuestion(title, question, replyPrefix, pr);
15366 }
15367
15368 void
15369 TypeInEvent(char firstChar)
15370 {
15371     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15372         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15373         gameMode == AnalyzeMode || gameMode == EditGame || 
15374         gameMode == EditPosition || gameMode == IcsExamining ||
15375         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15376         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15377                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15378                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15379         gameMode == Training) PopUpMoveDialog(firstChar);
15380 }
15381
15382 void
15383 TypeInDoneEvent(char *move)
15384 {
15385         Board board;
15386         int n, fromX, fromY, toX, toY;
15387         char promoChar;
15388         ChessMove moveType;
15389
15390         // [HGM] FENedit
15391         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15392                 EditPositionPasteFEN(move);
15393                 return;
15394         }
15395         // [HGM] movenum: allow move number to be typed in any mode
15396         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15397           ToNrEvent(2*n-1);
15398           return;
15399         }
15400
15401       if (gameMode != EditGame && currentMove != forwardMostMove && 
15402         gameMode != Training) {
15403         DisplayMoveError(_("Displayed move is not current"));
15404       } else {
15405         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15406           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15407         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15408         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15409           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15410           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15411         } else {
15412           DisplayMoveError(_("Could not parse move"));
15413         }
15414       }
15415 }
15416
15417 void
15418 DisplayMove(moveNumber)
15419      int moveNumber;
15420 {
15421     char message[MSG_SIZ];
15422     char res[MSG_SIZ];
15423     char cpThinkOutput[MSG_SIZ];
15424
15425     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15426
15427     if (moveNumber == forwardMostMove - 1 ||
15428         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15429
15430         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15431
15432         if (strchr(cpThinkOutput, '\n')) {
15433             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15434         }
15435     } else {
15436         *cpThinkOutput = NULLCHAR;
15437     }
15438
15439     /* [AS] Hide thinking from human user */
15440     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15441         *cpThinkOutput = NULLCHAR;
15442         if( thinkOutput[0] != NULLCHAR ) {
15443             int i;
15444
15445             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15446                 cpThinkOutput[i] = '.';
15447             }
15448             cpThinkOutput[i] = NULLCHAR;
15449             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15450         }
15451     }
15452
15453     if (moveNumber == forwardMostMove - 1 &&
15454         gameInfo.resultDetails != NULL) {
15455         if (gameInfo.resultDetails[0] == NULLCHAR) {
15456           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15457         } else {
15458           snprintf(res, MSG_SIZ, " {%s} %s",
15459                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15460         }
15461     } else {
15462         res[0] = NULLCHAR;
15463     }
15464
15465     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15466         DisplayMessage(res, cpThinkOutput);
15467     } else {
15468       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15469                 WhiteOnMove(moveNumber) ? " " : ".. ",
15470                 parseList[moveNumber], res);
15471         DisplayMessage(message, cpThinkOutput);
15472     }
15473 }
15474
15475 void
15476 DisplayComment(moveNumber, text)
15477      int moveNumber;
15478      char *text;
15479 {
15480     char title[MSG_SIZ];
15481
15482     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15483       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15484     } else {
15485       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15486               WhiteOnMove(moveNumber) ? " " : ".. ",
15487               parseList[moveNumber]);
15488     }
15489     if (text != NULL && (appData.autoDisplayComment || commentUp))
15490         CommentPopUp(title, text);
15491 }
15492
15493 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15494  * might be busy thinking or pondering.  It can be omitted if your
15495  * gnuchess is configured to stop thinking immediately on any user
15496  * input.  However, that gnuchess feature depends on the FIONREAD
15497  * ioctl, which does not work properly on some flavors of Unix.
15498  */
15499 void
15500 Attention(cps)
15501      ChessProgramState *cps;
15502 {
15503 #if ATTENTION
15504     if (!cps->useSigint) return;
15505     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15506     switch (gameMode) {
15507       case MachinePlaysWhite:
15508       case MachinePlaysBlack:
15509       case TwoMachinesPlay:
15510       case IcsPlayingWhite:
15511       case IcsPlayingBlack:
15512       case AnalyzeMode:
15513       case AnalyzeFile:
15514         /* Skip if we know it isn't thinking */
15515         if (!cps->maybeThinking) return;
15516         if (appData.debugMode)
15517           fprintf(debugFP, "Interrupting %s\n", cps->which);
15518         InterruptChildProcess(cps->pr);
15519         cps->maybeThinking = FALSE;
15520         break;
15521       default:
15522         break;
15523     }
15524 #endif /*ATTENTION*/
15525 }
15526
15527 int
15528 CheckFlags()
15529 {
15530     if (whiteTimeRemaining <= 0) {
15531         if (!whiteFlag) {
15532             whiteFlag = TRUE;
15533             if (appData.icsActive) {
15534                 if (appData.autoCallFlag &&
15535                     gameMode == IcsPlayingBlack && !blackFlag) {
15536                   SendToICS(ics_prefix);
15537                   SendToICS("flag\n");
15538                 }
15539             } else {
15540                 if (blackFlag) {
15541                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15542                 } else {
15543                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15544                     if (appData.autoCallFlag) {
15545                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15546                         return TRUE;
15547                     }
15548                 }
15549             }
15550         }
15551     }
15552     if (blackTimeRemaining <= 0) {
15553         if (!blackFlag) {
15554             blackFlag = TRUE;
15555             if (appData.icsActive) {
15556                 if (appData.autoCallFlag &&
15557                     gameMode == IcsPlayingWhite && !whiteFlag) {
15558                   SendToICS(ics_prefix);
15559                   SendToICS("flag\n");
15560                 }
15561             } else {
15562                 if (whiteFlag) {
15563                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15564                 } else {
15565                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15566                     if (appData.autoCallFlag) {
15567                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15568                         return TRUE;
15569                     }
15570                 }
15571             }
15572         }
15573     }
15574     return FALSE;
15575 }
15576
15577 void
15578 CheckTimeControl()
15579 {
15580     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15581         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15582
15583     /*
15584      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15585      */
15586     if ( !WhiteOnMove(forwardMostMove) ) {
15587         /* White made time control */
15588         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15589         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15590         /* [HGM] time odds: correct new time quota for time odds! */
15591                                             / WhitePlayer()->timeOdds;
15592         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15593     } else {
15594         lastBlack -= blackTimeRemaining;
15595         /* Black made time control */
15596         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15597                                             / WhitePlayer()->other->timeOdds;
15598         lastWhite = whiteTimeRemaining;
15599     }
15600 }
15601
15602 void
15603 DisplayBothClocks()
15604 {
15605     int wom = gameMode == EditPosition ?
15606       !blackPlaysFirst : WhiteOnMove(currentMove);
15607     DisplayWhiteClock(whiteTimeRemaining, wom);
15608     DisplayBlackClock(blackTimeRemaining, !wom);
15609 }
15610
15611
15612 /* Timekeeping seems to be a portability nightmare.  I think everyone
15613    has ftime(), but I'm really not sure, so I'm including some ifdefs
15614    to use other calls if you don't.  Clocks will be less accurate if
15615    you have neither ftime nor gettimeofday.
15616 */
15617
15618 /* VS 2008 requires the #include outside of the function */
15619 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15620 #include <sys/timeb.h>
15621 #endif
15622
15623 /* Get the current time as a TimeMark */
15624 void
15625 GetTimeMark(tm)
15626      TimeMark *tm;
15627 {
15628 #if HAVE_GETTIMEOFDAY
15629
15630     struct timeval timeVal;
15631     struct timezone timeZone;
15632
15633     gettimeofday(&timeVal, &timeZone);
15634     tm->sec = (long) timeVal.tv_sec;
15635     tm->ms = (int) (timeVal.tv_usec / 1000L);
15636
15637 #else /*!HAVE_GETTIMEOFDAY*/
15638 #if HAVE_FTIME
15639
15640 // include <sys/timeb.h> / moved to just above start of function
15641     struct timeb timeB;
15642
15643     ftime(&timeB);
15644     tm->sec = (long) timeB.time;
15645     tm->ms = (int) timeB.millitm;
15646
15647 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15648     tm->sec = (long) time(NULL);
15649     tm->ms = 0;
15650 #endif
15651 #endif
15652 }
15653
15654 /* Return the difference in milliseconds between two
15655    time marks.  We assume the difference will fit in a long!
15656 */
15657 long
15658 SubtractTimeMarks(tm2, tm1)
15659      TimeMark *tm2, *tm1;
15660 {
15661     return 1000L*(tm2->sec - tm1->sec) +
15662            (long) (tm2->ms - tm1->ms);
15663 }
15664
15665
15666 /*
15667  * Code to manage the game clocks.
15668  *
15669  * In tournament play, black starts the clock and then white makes a move.
15670  * We give the human user a slight advantage if he is playing white---the
15671  * clocks don't run until he makes his first move, so it takes zero time.
15672  * Also, we don't account for network lag, so we could get out of sync
15673  * with GNU Chess's clock -- but then, referees are always right.
15674  */
15675
15676 static TimeMark tickStartTM;
15677 static long intendedTickLength;
15678
15679 long
15680 NextTickLength(timeRemaining)
15681      long timeRemaining;
15682 {
15683     long nominalTickLength, nextTickLength;
15684
15685     if (timeRemaining > 0L && timeRemaining <= 10000L)
15686       nominalTickLength = 100L;
15687     else
15688       nominalTickLength = 1000L;
15689     nextTickLength = timeRemaining % nominalTickLength;
15690     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15691
15692     return nextTickLength;
15693 }
15694
15695 /* Adjust clock one minute up or down */
15696 void
15697 AdjustClock(Boolean which, int dir)
15698 {
15699     if(which) blackTimeRemaining += 60000*dir;
15700     else      whiteTimeRemaining += 60000*dir;
15701     DisplayBothClocks();
15702 }
15703
15704 /* Stop clocks and reset to a fresh time control */
15705 void
15706 ResetClocks()
15707 {
15708     (void) StopClockTimer();
15709     if (appData.icsActive) {
15710         whiteTimeRemaining = blackTimeRemaining = 0;
15711     } else if (searchTime) {
15712         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15713         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15714     } else { /* [HGM] correct new time quote for time odds */
15715         whiteTC = blackTC = fullTimeControlString;
15716         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15717         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15718     }
15719     if (whiteFlag || blackFlag) {
15720         DisplayTitle("");
15721         whiteFlag = blackFlag = FALSE;
15722     }
15723     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15724     DisplayBothClocks();
15725 }
15726
15727 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15728
15729 /* Decrement running clock by amount of time that has passed */
15730 void
15731 DecrementClocks()
15732 {
15733     long timeRemaining;
15734     long lastTickLength, fudge;
15735     TimeMark now;
15736
15737     if (!appData.clockMode) return;
15738     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15739
15740     GetTimeMark(&now);
15741
15742     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15743
15744     /* Fudge if we woke up a little too soon */
15745     fudge = intendedTickLength - lastTickLength;
15746     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15747
15748     if (WhiteOnMove(forwardMostMove)) {
15749         if(whiteNPS >= 0) lastTickLength = 0;
15750         timeRemaining = whiteTimeRemaining -= lastTickLength;
15751         if(timeRemaining < 0 && !appData.icsActive) {
15752             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15753             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15754                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15755                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15756             }
15757         }
15758         DisplayWhiteClock(whiteTimeRemaining - fudge,
15759                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15760     } else {
15761         if(blackNPS >= 0) lastTickLength = 0;
15762         timeRemaining = blackTimeRemaining -= lastTickLength;
15763         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15764             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15765             if(suddenDeath) {
15766                 blackStartMove = forwardMostMove;
15767                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15768             }
15769         }
15770         DisplayBlackClock(blackTimeRemaining - fudge,
15771                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15772     }
15773     if (CheckFlags()) return;
15774
15775     tickStartTM = now;
15776     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15777     StartClockTimer(intendedTickLength);
15778
15779     /* if the time remaining has fallen below the alarm threshold, sound the
15780      * alarm. if the alarm has sounded and (due to a takeback or time control
15781      * with increment) the time remaining has increased to a level above the
15782      * threshold, reset the alarm so it can sound again.
15783      */
15784
15785     if (appData.icsActive && appData.icsAlarm) {
15786
15787         /* make sure we are dealing with the user's clock */
15788         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15789                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15790            )) return;
15791
15792         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15793             alarmSounded = FALSE;
15794         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15795             PlayAlarmSound();
15796             alarmSounded = TRUE;
15797         }
15798     }
15799 }
15800
15801
15802 /* A player has just moved, so stop the previously running
15803    clock and (if in clock mode) start the other one.
15804    We redisplay both clocks in case we're in ICS mode, because
15805    ICS gives us an update to both clocks after every move.
15806    Note that this routine is called *after* forwardMostMove
15807    is updated, so the last fractional tick must be subtracted
15808    from the color that is *not* on move now.
15809 */
15810 void
15811 SwitchClocks(int newMoveNr)
15812 {
15813     long lastTickLength;
15814     TimeMark now;
15815     int flagged = FALSE;
15816
15817     GetTimeMark(&now);
15818
15819     if (StopClockTimer() && appData.clockMode) {
15820         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15821         if (!WhiteOnMove(forwardMostMove)) {
15822             if(blackNPS >= 0) lastTickLength = 0;
15823             blackTimeRemaining -= lastTickLength;
15824            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15825 //         if(pvInfoList[forwardMostMove].time == -1)
15826                  pvInfoList[forwardMostMove].time =               // use GUI time
15827                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15828         } else {
15829            if(whiteNPS >= 0) lastTickLength = 0;
15830            whiteTimeRemaining -= lastTickLength;
15831            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15832 //         if(pvInfoList[forwardMostMove].time == -1)
15833                  pvInfoList[forwardMostMove].time =
15834                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15835         }
15836         flagged = CheckFlags();
15837     }
15838     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15839     CheckTimeControl();
15840
15841     if (flagged || !appData.clockMode) return;
15842
15843     switch (gameMode) {
15844       case MachinePlaysBlack:
15845       case MachinePlaysWhite:
15846       case BeginningOfGame:
15847         if (pausing) return;
15848         break;
15849
15850       case EditGame:
15851       case PlayFromGameFile:
15852       case IcsExamining:
15853         return;
15854
15855       default:
15856         break;
15857     }
15858
15859     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15860         if(WhiteOnMove(forwardMostMove))
15861              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15862         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15863     }
15864
15865     tickStartTM = now;
15866     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15867       whiteTimeRemaining : blackTimeRemaining);
15868     StartClockTimer(intendedTickLength);
15869 }
15870
15871
15872 /* Stop both clocks */
15873 void
15874 StopClocks()
15875 {
15876     long lastTickLength;
15877     TimeMark now;
15878
15879     if (!StopClockTimer()) return;
15880     if (!appData.clockMode) return;
15881
15882     GetTimeMark(&now);
15883
15884     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15885     if (WhiteOnMove(forwardMostMove)) {
15886         if(whiteNPS >= 0) lastTickLength = 0;
15887         whiteTimeRemaining -= lastTickLength;
15888         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15889     } else {
15890         if(blackNPS >= 0) lastTickLength = 0;
15891         blackTimeRemaining -= lastTickLength;
15892         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15893     }
15894     CheckFlags();
15895 }
15896
15897 /* Start clock of player on move.  Time may have been reset, so
15898    if clock is already running, stop and restart it. */
15899 void
15900 StartClocks()
15901 {
15902     (void) StopClockTimer(); /* in case it was running already */
15903     DisplayBothClocks();
15904     if (CheckFlags()) return;
15905
15906     if (!appData.clockMode) return;
15907     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15908
15909     GetTimeMark(&tickStartTM);
15910     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15911       whiteTimeRemaining : blackTimeRemaining);
15912
15913    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15914     whiteNPS = blackNPS = -1;
15915     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15916        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15917         whiteNPS = first.nps;
15918     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15919        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15920         blackNPS = first.nps;
15921     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15922         whiteNPS = second.nps;
15923     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15924         blackNPS = second.nps;
15925     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15926
15927     StartClockTimer(intendedTickLength);
15928 }
15929
15930 char *
15931 TimeString(ms)
15932      long ms;
15933 {
15934     long second, minute, hour, day;
15935     char *sign = "";
15936     static char buf[32];
15937
15938     if (ms > 0 && ms <= 9900) {
15939       /* convert milliseconds to tenths, rounding up */
15940       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15941
15942       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15943       return buf;
15944     }
15945
15946     /* convert milliseconds to seconds, rounding up */
15947     /* use floating point to avoid strangeness of integer division
15948        with negative dividends on many machines */
15949     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15950
15951     if (second < 0) {
15952         sign = "-";
15953         second = -second;
15954     }
15955
15956     day = second / (60 * 60 * 24);
15957     second = second % (60 * 60 * 24);
15958     hour = second / (60 * 60);
15959     second = second % (60 * 60);
15960     minute = second / 60;
15961     second = second % 60;
15962
15963     if (day > 0)
15964       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15965               sign, day, hour, minute, second);
15966     else if (hour > 0)
15967       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15968     else
15969       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15970
15971     return buf;
15972 }
15973
15974
15975 /*
15976  * This is necessary because some C libraries aren't ANSI C compliant yet.
15977  */
15978 char *
15979 StrStr(string, match)
15980      char *string, *match;
15981 {
15982     int i, length;
15983
15984     length = strlen(match);
15985
15986     for (i = strlen(string) - length; i >= 0; i--, string++)
15987       if (!strncmp(match, string, length))
15988         return string;
15989
15990     return NULL;
15991 }
15992
15993 char *
15994 StrCaseStr(string, match)
15995      char *string, *match;
15996 {
15997     int i, j, length;
15998
15999     length = strlen(match);
16000
16001     for (i = strlen(string) - length; i >= 0; i--, string++) {
16002         for (j = 0; j < length; j++) {
16003             if (ToLower(match[j]) != ToLower(string[j]))
16004               break;
16005         }
16006         if (j == length) return string;
16007     }
16008
16009     return NULL;
16010 }
16011
16012 #ifndef _amigados
16013 int
16014 StrCaseCmp(s1, s2)
16015      char *s1, *s2;
16016 {
16017     char c1, c2;
16018
16019     for (;;) {
16020         c1 = ToLower(*s1++);
16021         c2 = ToLower(*s2++);
16022         if (c1 > c2) return 1;
16023         if (c1 < c2) return -1;
16024         if (c1 == NULLCHAR) return 0;
16025     }
16026 }
16027
16028
16029 int
16030 ToLower(c)
16031      int c;
16032 {
16033     return isupper(c) ? tolower(c) : c;
16034 }
16035
16036
16037 int
16038 ToUpper(c)
16039      int c;
16040 {
16041     return islower(c) ? toupper(c) : c;
16042 }
16043 #endif /* !_amigados    */
16044
16045 char *
16046 StrSave(s)
16047      char *s;
16048 {
16049   char *ret;
16050
16051   if ((ret = (char *) malloc(strlen(s) + 1)))
16052     {
16053       safeStrCpy(ret, s, strlen(s)+1);
16054     }
16055   return ret;
16056 }
16057
16058 char *
16059 StrSavePtr(s, savePtr)
16060      char *s, **savePtr;
16061 {
16062     if (*savePtr) {
16063         free(*savePtr);
16064     }
16065     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16066       safeStrCpy(*savePtr, s, strlen(s)+1);
16067     }
16068     return(*savePtr);
16069 }
16070
16071 char *
16072 PGNDate()
16073 {
16074     time_t clock;
16075     struct tm *tm;
16076     char buf[MSG_SIZ];
16077
16078     clock = time((time_t *)NULL);
16079     tm = localtime(&clock);
16080     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16081             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16082     return StrSave(buf);
16083 }
16084
16085
16086 char *
16087 PositionToFEN(move, overrideCastling)
16088      int move;
16089      char *overrideCastling;
16090 {
16091     int i, j, fromX, fromY, toX, toY;
16092     int whiteToPlay;
16093     char buf[MSG_SIZ];
16094     char *p, *q;
16095     int emptycount;
16096     ChessSquare piece;
16097
16098     whiteToPlay = (gameMode == EditPosition) ?
16099       !blackPlaysFirst : (move % 2 == 0);
16100     p = buf;
16101
16102     /* Piece placement data */
16103     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16104         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16105         emptycount = 0;
16106         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16107             if (boards[move][i][j] == EmptySquare) {
16108                 emptycount++;
16109             } else { ChessSquare piece = boards[move][i][j];
16110                 if (emptycount > 0) {
16111                     if(emptycount<10) /* [HGM] can be >= 10 */
16112                         *p++ = '0' + emptycount;
16113                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16114                     emptycount = 0;
16115                 }
16116                 if(PieceToChar(piece) == '+') {
16117                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16118                     *p++ = '+';
16119                     piece = (ChessSquare)(DEMOTED piece);
16120                 }
16121                 *p++ = PieceToChar(piece);
16122                 if(p[-1] == '~') {
16123                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16124                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16125                     *p++ = '~';
16126                 }
16127             }
16128         }
16129         if (emptycount > 0) {
16130             if(emptycount<10) /* [HGM] can be >= 10 */
16131                 *p++ = '0' + emptycount;
16132             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16133             emptycount = 0;
16134         }
16135         *p++ = '/';
16136     }
16137     *(p - 1) = ' ';
16138
16139     /* [HGM] print Crazyhouse or Shogi holdings */
16140     if( gameInfo.holdingsWidth ) {
16141         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16142         q = p;
16143         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16144             piece = boards[move][i][BOARD_WIDTH-1];
16145             if( piece != EmptySquare )
16146               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16147                   *p++ = PieceToChar(piece);
16148         }
16149         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16150             piece = boards[move][BOARD_HEIGHT-i-1][0];
16151             if( piece != EmptySquare )
16152               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16153                   *p++ = PieceToChar(piece);
16154         }
16155
16156         if( q == p ) *p++ = '-';
16157         *p++ = ']';
16158         *p++ = ' ';
16159     }
16160
16161     /* Active color */
16162     *p++ = whiteToPlay ? 'w' : 'b';
16163     *p++ = ' ';
16164
16165   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16166     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16167   } else {
16168   if(nrCastlingRights) {
16169      q = p;
16170      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16171        /* [HGM] write directly from rights */
16172            if(boards[move][CASTLING][2] != NoRights &&
16173               boards[move][CASTLING][0] != NoRights   )
16174                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16175            if(boards[move][CASTLING][2] != NoRights &&
16176               boards[move][CASTLING][1] != NoRights   )
16177                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16178            if(boards[move][CASTLING][5] != NoRights &&
16179               boards[move][CASTLING][3] != NoRights   )
16180                 *p++ = boards[move][CASTLING][3] + AAA;
16181            if(boards[move][CASTLING][5] != NoRights &&
16182               boards[move][CASTLING][4] != NoRights   )
16183                 *p++ = boards[move][CASTLING][4] + AAA;
16184      } else {
16185
16186         /* [HGM] write true castling rights */
16187         if( nrCastlingRights == 6 ) {
16188             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16189                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16190             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16191                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16192             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16193                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16194             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16195                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16196         }
16197      }
16198      if (q == p) *p++ = '-'; /* No castling rights */
16199      *p++ = ' ';
16200   }
16201
16202   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16203      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16204     /* En passant target square */
16205     if (move > backwardMostMove) {
16206         fromX = moveList[move - 1][0] - AAA;
16207         fromY = moveList[move - 1][1] - ONE;
16208         toX = moveList[move - 1][2] - AAA;
16209         toY = moveList[move - 1][3] - ONE;
16210         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16211             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16212             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16213             fromX == toX) {
16214             /* 2-square pawn move just happened */
16215             *p++ = toX + AAA;
16216             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16217         } else {
16218             *p++ = '-';
16219         }
16220     } else if(move == backwardMostMove) {
16221         // [HGM] perhaps we should always do it like this, and forget the above?
16222         if((signed char)boards[move][EP_STATUS] >= 0) {
16223             *p++ = boards[move][EP_STATUS] + AAA;
16224             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16225         } else {
16226             *p++ = '-';
16227         }
16228     } else {
16229         *p++ = '-';
16230     }
16231     *p++ = ' ';
16232   }
16233   }
16234
16235     /* [HGM] find reversible plies */
16236     {   int i = 0, j=move;
16237
16238         if (appData.debugMode) { int k;
16239             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16240             for(k=backwardMostMove; k<=forwardMostMove; k++)
16241                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16242
16243         }
16244
16245         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16246         if( j == backwardMostMove ) i += initialRulePlies;
16247         sprintf(p, "%d ", i);
16248         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16249     }
16250     /* Fullmove number */
16251     sprintf(p, "%d", (move / 2) + 1);
16252
16253     return StrSave(buf);
16254 }
16255
16256 Boolean
16257 ParseFEN(board, blackPlaysFirst, fen)
16258     Board board;
16259      int *blackPlaysFirst;
16260      char *fen;
16261 {
16262     int i, j;
16263     char *p, c;
16264     int emptycount;
16265     ChessSquare piece;
16266
16267     p = fen;
16268
16269     /* [HGM] by default clear Crazyhouse holdings, if present */
16270     if(gameInfo.holdingsWidth) {
16271        for(i=0; i<BOARD_HEIGHT; i++) {
16272            board[i][0]             = EmptySquare; /* black holdings */
16273            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16274            board[i][1]             = (ChessSquare) 0; /* black counts */
16275            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16276        }
16277     }
16278
16279     /* Piece placement data */
16280     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16281         j = 0;
16282         for (;;) {
16283             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16284                 if (*p == '/') p++;
16285                 emptycount = gameInfo.boardWidth - j;
16286                 while (emptycount--)
16287                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16288                 break;
16289 #if(BOARD_FILES >= 10)
16290             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16291                 p++; emptycount=10;
16292                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16293                 while (emptycount--)
16294                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16295 #endif
16296             } else if (isdigit(*p)) {
16297                 emptycount = *p++ - '0';
16298                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16299                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16300                 while (emptycount--)
16301                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16302             } else if (*p == '+' || isalpha(*p)) {
16303                 if (j >= gameInfo.boardWidth) return FALSE;
16304                 if(*p=='+') {
16305                     piece = CharToPiece(*++p);
16306                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16307                     piece = (ChessSquare) (PROMOTED piece ); p++;
16308                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16309                 } else piece = CharToPiece(*p++);
16310
16311                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16312                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16313                     piece = (ChessSquare) (PROMOTED piece);
16314                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16315                     p++;
16316                 }
16317                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16318             } else {
16319                 return FALSE;
16320             }
16321         }
16322     }
16323     while (*p == '/' || *p == ' ') p++;
16324
16325     /* [HGM] look for Crazyhouse holdings here */
16326     while(*p==' ') p++;
16327     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16328         if(*p == '[') p++;
16329         if(*p == '-' ) p++; /* empty holdings */ else {
16330             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16331             /* if we would allow FEN reading to set board size, we would   */
16332             /* have to add holdings and shift the board read so far here   */
16333             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16334                 p++;
16335                 if((int) piece >= (int) BlackPawn ) {
16336                     i = (int)piece - (int)BlackPawn;
16337                     i = PieceToNumber((ChessSquare)i);
16338                     if( i >= gameInfo.holdingsSize ) return FALSE;
16339                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16340                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16341                 } else {
16342                     i = (int)piece - (int)WhitePawn;
16343                     i = PieceToNumber((ChessSquare)i);
16344                     if( i >= gameInfo.holdingsSize ) return FALSE;
16345                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16346                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16347                 }
16348             }
16349         }
16350         if(*p == ']') p++;
16351     }
16352
16353     while(*p == ' ') p++;
16354
16355     /* Active color */
16356     c = *p++;
16357     if(appData.colorNickNames) {
16358       if( c == appData.colorNickNames[0] ) c = 'w'; else
16359       if( c == appData.colorNickNames[1] ) c = 'b';
16360     }
16361     switch (c) {
16362       case 'w':
16363         *blackPlaysFirst = FALSE;
16364         break;
16365       case 'b':
16366         *blackPlaysFirst = TRUE;
16367         break;
16368       default:
16369         return FALSE;
16370     }
16371
16372     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16373     /* return the extra info in global variiables             */
16374
16375     /* set defaults in case FEN is incomplete */
16376     board[EP_STATUS] = EP_UNKNOWN;
16377     for(i=0; i<nrCastlingRights; i++ ) {
16378         board[CASTLING][i] =
16379             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16380     }   /* assume possible unless obviously impossible */
16381     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16382     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16383     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16384                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16385     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16386     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16387     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16388                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16389     FENrulePlies = 0;
16390
16391     while(*p==' ') p++;
16392     if(nrCastlingRights) {
16393       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16394           /* castling indicator present, so default becomes no castlings */
16395           for(i=0; i<nrCastlingRights; i++ ) {
16396                  board[CASTLING][i] = NoRights;
16397           }
16398       }
16399       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16400              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16401              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16402              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16403         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16404
16405         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16406             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16407             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16408         }
16409         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16410             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16411         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16412                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16413         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16414                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16415         switch(c) {
16416           case'K':
16417               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16418               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16419               board[CASTLING][2] = whiteKingFile;
16420               break;
16421           case'Q':
16422               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16423               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16424               board[CASTLING][2] = whiteKingFile;
16425               break;
16426           case'k':
16427               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16428               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16429               board[CASTLING][5] = blackKingFile;
16430               break;
16431           case'q':
16432               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16433               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16434               board[CASTLING][5] = blackKingFile;
16435           case '-':
16436               break;
16437           default: /* FRC castlings */
16438               if(c >= 'a') { /* black rights */
16439                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16440                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16441                   if(i == BOARD_RGHT) break;
16442                   board[CASTLING][5] = i;
16443                   c -= AAA;
16444                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16445                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16446                   if(c > i)
16447                       board[CASTLING][3] = c;
16448                   else
16449                       board[CASTLING][4] = c;
16450               } else { /* white rights */
16451                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16452                     if(board[0][i] == WhiteKing) break;
16453                   if(i == BOARD_RGHT) break;
16454                   board[CASTLING][2] = i;
16455                   c -= AAA - 'a' + 'A';
16456                   if(board[0][c] >= WhiteKing) break;
16457                   if(c > i)
16458                       board[CASTLING][0] = c;
16459                   else
16460                       board[CASTLING][1] = c;
16461               }
16462         }
16463       }
16464       for(i=0; i<nrCastlingRights; i++)
16465         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16466     if (appData.debugMode) {
16467         fprintf(debugFP, "FEN castling rights:");
16468         for(i=0; i<nrCastlingRights; i++)
16469         fprintf(debugFP, " %d", board[CASTLING][i]);
16470         fprintf(debugFP, "\n");
16471     }
16472
16473       while(*p==' ') p++;
16474     }
16475
16476     /* read e.p. field in games that know e.p. capture */
16477     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16478        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16479       if(*p=='-') {
16480         p++; board[EP_STATUS] = EP_NONE;
16481       } else {
16482          char c = *p++ - AAA;
16483
16484          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16485          if(*p >= '0' && *p <='9') p++;
16486          board[EP_STATUS] = c;
16487       }
16488     }
16489
16490
16491     if(sscanf(p, "%d", &i) == 1) {
16492         FENrulePlies = i; /* 50-move ply counter */
16493         /* (The move number is still ignored)    */
16494     }
16495
16496     return TRUE;
16497 }
16498
16499 void
16500 EditPositionPasteFEN(char *fen)
16501 {
16502   if (fen != NULL) {
16503     Board initial_position;
16504
16505     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16506       DisplayError(_("Bad FEN position in clipboard"), 0);
16507       return ;
16508     } else {
16509       int savedBlackPlaysFirst = blackPlaysFirst;
16510       EditPositionEvent();
16511       blackPlaysFirst = savedBlackPlaysFirst;
16512       CopyBoard(boards[0], initial_position);
16513       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16514       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16515       DisplayBothClocks();
16516       DrawPosition(FALSE, boards[currentMove]);
16517     }
16518   }
16519 }
16520
16521 static char cseq[12] = "\\   ";
16522
16523 Boolean set_cont_sequence(char *new_seq)
16524 {
16525     int len;
16526     Boolean ret;
16527
16528     // handle bad attempts to set the sequence
16529         if (!new_seq)
16530                 return 0; // acceptable error - no debug
16531
16532     len = strlen(new_seq);
16533     ret = (len > 0) && (len < sizeof(cseq));
16534     if (ret)
16535       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16536     else if (appData.debugMode)
16537       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16538     return ret;
16539 }
16540
16541 /*
16542     reformat a source message so words don't cross the width boundary.  internal
16543     newlines are not removed.  returns the wrapped size (no null character unless
16544     included in source message).  If dest is NULL, only calculate the size required
16545     for the dest buffer.  lp argument indicats line position upon entry, and it's
16546     passed back upon exit.
16547 */
16548 int wrap(char *dest, char *src, int count, int width, int *lp)
16549 {
16550     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16551
16552     cseq_len = strlen(cseq);
16553     old_line = line = *lp;
16554     ansi = len = clen = 0;
16555
16556     for (i=0; i < count; i++)
16557     {
16558         if (src[i] == '\033')
16559             ansi = 1;
16560
16561         // if we hit the width, back up
16562         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16563         {
16564             // store i & len in case the word is too long
16565             old_i = i, old_len = len;
16566
16567             // find the end of the last word
16568             while (i && src[i] != ' ' && src[i] != '\n')
16569             {
16570                 i--;
16571                 len--;
16572             }
16573
16574             // word too long?  restore i & len before splitting it
16575             if ((old_i-i+clen) >= width)
16576             {
16577                 i = old_i;
16578                 len = old_len;
16579             }
16580
16581             // extra space?
16582             if (i && src[i-1] == ' ')
16583                 len--;
16584
16585             if (src[i] != ' ' && src[i] != '\n')
16586             {
16587                 i--;
16588                 if (len)
16589                     len--;
16590             }
16591
16592             // now append the newline and continuation sequence
16593             if (dest)
16594                 dest[len] = '\n';
16595             len++;
16596             if (dest)
16597                 strncpy(dest+len, cseq, cseq_len);
16598             len += cseq_len;
16599             line = cseq_len;
16600             clen = cseq_len;
16601             continue;
16602         }
16603
16604         if (dest)
16605             dest[len] = src[i];
16606         len++;
16607         if (!ansi)
16608             line++;
16609         if (src[i] == '\n')
16610             line = 0;
16611         if (src[i] == 'm')
16612             ansi = 0;
16613     }
16614     if (dest && appData.debugMode)
16615     {
16616         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16617             count, width, line, len, *lp);
16618         show_bytes(debugFP, src, count);
16619         fprintf(debugFP, "\ndest: ");
16620         show_bytes(debugFP, dest, len);
16621         fprintf(debugFP, "\n");
16622     }
16623     *lp = dest ? line : old_line;
16624
16625     return len;
16626 }
16627
16628 // [HGM] vari: routines for shelving variations
16629 Boolean modeRestore = FALSE;
16630
16631 void
16632 PushInner(int firstMove, int lastMove)
16633 {
16634         int i, j, nrMoves = lastMove - firstMove;
16635
16636         // push current tail of game on stack
16637         savedResult[storedGames] = gameInfo.result;
16638         savedDetails[storedGames] = gameInfo.resultDetails;
16639         gameInfo.resultDetails = NULL;
16640         savedFirst[storedGames] = firstMove;
16641         savedLast [storedGames] = lastMove;
16642         savedFramePtr[storedGames] = framePtr;
16643         framePtr -= nrMoves; // reserve space for the boards
16644         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16645             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16646             for(j=0; j<MOVE_LEN; j++)
16647                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16648             for(j=0; j<2*MOVE_LEN; j++)
16649                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16650             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16651             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16652             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16653             pvInfoList[firstMove+i-1].depth = 0;
16654             commentList[framePtr+i] = commentList[firstMove+i];
16655             commentList[firstMove+i] = NULL;
16656         }
16657
16658         storedGames++;
16659         forwardMostMove = firstMove; // truncate game so we can start variation
16660 }
16661
16662 void
16663 PushTail(int firstMove, int lastMove)
16664 {
16665         if(appData.icsActive) { // only in local mode
16666                 forwardMostMove = currentMove; // mimic old ICS behavior
16667                 return;
16668         }
16669         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16670
16671         PushInner(firstMove, lastMove);
16672         if(storedGames == 1) GreyRevert(FALSE);
16673         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16674 }
16675
16676 void
16677 PopInner(Boolean annotate)
16678 {
16679         int i, j, nrMoves;
16680         char buf[8000], moveBuf[20];
16681
16682         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16683         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16684         nrMoves = savedLast[storedGames] - currentMove;
16685         if(annotate) {
16686                 int cnt = 10;
16687                 if(!WhiteOnMove(currentMove))
16688                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16689                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16690                 for(i=currentMove; i<forwardMostMove; i++) {
16691                         if(WhiteOnMove(i))
16692                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16693                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16694                         strcat(buf, moveBuf);
16695                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16696                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16697                 }
16698                 strcat(buf, ")");
16699         }
16700         for(i=1; i<=nrMoves; i++) { // copy last variation back
16701             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16702             for(j=0; j<MOVE_LEN; j++)
16703                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16704             for(j=0; j<2*MOVE_LEN; j++)
16705                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16706             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16707             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16708             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16709             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16710             commentList[currentMove+i] = commentList[framePtr+i];
16711             commentList[framePtr+i] = NULL;
16712         }
16713         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16714         framePtr = savedFramePtr[storedGames];
16715         gameInfo.result = savedResult[storedGames];
16716         if(gameInfo.resultDetails != NULL) {
16717             free(gameInfo.resultDetails);
16718       }
16719         gameInfo.resultDetails = savedDetails[storedGames];
16720         forwardMostMove = currentMove + nrMoves;
16721 }
16722
16723 Boolean
16724 PopTail(Boolean annotate)
16725 {
16726         if(appData.icsActive) return FALSE; // only in local mode
16727         if(!storedGames) return FALSE; // sanity
16728         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16729
16730         PopInner(annotate);
16731         if(currentMove < forwardMostMove) ForwardEvent(); else
16732         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16733
16734         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16735         return TRUE;
16736 }
16737
16738 void
16739 CleanupTail()
16740 {       // remove all shelved variations
16741         int i;
16742         for(i=0; i<storedGames; i++) {
16743             if(savedDetails[i])
16744                 free(savedDetails[i]);
16745             savedDetails[i] = NULL;
16746         }
16747         for(i=framePtr; i<MAX_MOVES; i++) {
16748                 if(commentList[i]) free(commentList[i]);
16749                 commentList[i] = NULL;
16750         }
16751         framePtr = MAX_MOVES-1;
16752         storedGames = 0;
16753 }
16754
16755 void
16756 LoadVariation(int index, char *text)
16757 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16758         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16759         int level = 0, move;
16760
16761         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16762         // first find outermost bracketing variation
16763         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16764             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16765                 if(*p == '{') wait = '}'; else
16766                 if(*p == '[') wait = ']'; else
16767                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16768                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16769             }
16770             if(*p == wait) wait = NULLCHAR; // closing ]} found
16771             p++;
16772         }
16773         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16774         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16775         end[1] = NULLCHAR; // clip off comment beyond variation
16776         ToNrEvent(currentMove-1);
16777         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16778         // kludge: use ParsePV() to append variation to game
16779         move = currentMove;
16780         ParsePV(start, TRUE, TRUE);
16781         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16782         ClearPremoveHighlights();
16783         CommentPopDown();
16784         ToNrEvent(currentMove+1);
16785 }
16786