Fix mate test
[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 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 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270
271 /* States for ics_getting_history */
272 #define H_FALSE 0
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
278
279 /* whosays values for GameEnds */
280 #define GE_ICS 0
281 #define GE_ENGINE 1
282 #define GE_PLAYER 2
283 #define GE_FILE 3
284 #define GE_XBOARD 4
285 #define GE_ENGINE1 5
286 #define GE_ENGINE2 6
287
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
290
291 /* Different types of move when calling RegisterMove */
292 #define CMAIL_MOVE   0
293 #define CMAIL_RESIGN 1
294 #define CMAIL_DRAW   2
295 #define CMAIL_ACCEPT 3
296
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
301
302 /* Telnet protocol constants */
303 #define TN_WILL 0373
304 #define TN_WONT 0374
305 #define TN_DO   0375
306 #define TN_DONT 0376
307 #define TN_IAC  0377
308 #define TN_ECHO 0001
309 #define TN_SGA  0003
310 #define TN_PORT 23
311
312 char*
313 safeStrCpy( char *dst, const char *src, size_t count )
314 { // [HGM] made safe
315   int i;
316   assert( dst != NULL );
317   assert( src != NULL );
318   assert( count > 0 );
319
320   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
321   if(  i == count && dst[count-1] != NULLCHAR)
322     {
323       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
324       if(appData.debugMode)
325       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
326     }
327
328   return dst;
329 }
330
331 /* Some compiler can't cast u64 to double
332  * This function do the job for us:
333
334  * We use the highest bit for cast, this only
335  * works if the highest bit is not
336  * in use (This should not happen)
337  *
338  * We used this for all compiler
339  */
340 double
341 u64ToDouble(u64 value)
342 {
343   double r;
344   u64 tmp = value & u64Const(0x7fffffffffffffff);
345   r = (double)(s64)tmp;
346   if (value & u64Const(0x8000000000000000))
347        r +=  9.2233720368547758080e18; /* 2^63 */
348  return r;
349 }
350
351 /* Fake up flags for now, as we aren't keeping track of castling
352    availability yet. [HGM] Change of logic: the flag now only
353    indicates the type of castlings allowed by the rule of the game.
354    The actual rights themselves are maintained in the array
355    castlingRights, as part of the game history, and are not probed
356    by this function.
357  */
358 int
359 PosFlags(index)
360 {
361   int flags = F_ALL_CASTLE_OK;
362   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
363   switch (gameInfo.variant) {
364   case VariantSuicide:
365     flags &= ~F_ALL_CASTLE_OK;
366   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
367     flags |= F_IGNORE_CHECK;
368   case VariantLosers:
369     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
370     break;
371   case VariantAtomic:
372     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
373     break;
374   case VariantKriegspiel:
375     flags |= F_KRIEGSPIEL_CAPTURE;
376     break;
377   case VariantCapaRandom:
378   case VariantFischeRandom:
379     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
380   case VariantNoCastle:
381   case VariantShatranj:
382   case VariantCourier:
383   case VariantMakruk:
384     flags &= ~F_ALL_CASTLE_OK;
385     break;
386   default:
387     break;
388   }
389   return flags;
390 }
391
392 FILE *gameFileFP, *debugFP;
393
394 /*
395     [AS] Note: sometimes, the sscanf() function is used to parse the input
396     into a fixed-size buffer. Because of this, we must be prepared to
397     receive strings as long as the size of the input buffer, which is currently
398     set to 4K for Windows and 8K for the rest.
399     So, we must either allocate sufficiently large buffers here, or
400     reduce the size of the input buffer in the input reading part.
401 */
402
403 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
404 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
405 char thinkOutput1[MSG_SIZ*10];
406
407 ChessProgramState first, second;
408
409 /* premove variables */
410 int premoveToX = 0;
411 int premoveToY = 0;
412 int premoveFromX = 0;
413 int premoveFromY = 0;
414 int premovePromoChar = 0;
415 int gotPremove = 0;
416 Boolean alarmSounded;
417 /* end premove variables */
418
419 char *ics_prefix = "$";
420 int ics_type = ICS_GENERIC;
421
422 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
423 int pauseExamForwardMostMove = 0;
424 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
425 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
426 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
427 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
428 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
429 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
430 int whiteFlag = FALSE, blackFlag = FALSE;
431 int userOfferedDraw = FALSE;
432 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
433 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
434 int cmailMoveType[CMAIL_MAX_GAMES];
435 long ics_clock_paused = 0;
436 ProcRef icsPR = NoProc, cmailPR = NoProc;
437 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
438 GameMode gameMode = BeginningOfGame;
439 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
440 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
441 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
442 int hiddenThinkOutputState = 0; /* [AS] */
443 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
444 int adjudicateLossPlies = 6;
445 char white_holding[64], black_holding[64];
446 TimeMark lastNodeCountTime;
447 long lastNodeCount=0;
448 int shiftKey; // [HGM] set by mouse handler
449
450 int have_sent_ICS_logon = 0;
451 int movesPerSession;
452 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
453 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
454 long timeControl_2; /* [AS] Allow separate time controls */
455 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
456 long timeRemaining[2][MAX_MOVES];
457 int matchGame = 0;
458 TimeMark programStartTime;
459 char ics_handle[MSG_SIZ];
460 int have_set_title = 0;
461
462 /* animateTraining preserves the state of appData.animate
463  * when Training mode is activated. This allows the
464  * response to be animated when appData.animate == TRUE and
465  * appData.animateDragging == TRUE.
466  */
467 Boolean animateTraining;
468
469 GameInfo gameInfo;
470
471 AppData appData;
472
473 Board boards[MAX_MOVES];
474 /* [HGM] Following 7 needed for accurate legality tests: */
475 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
476 signed char  initialRights[BOARD_FILES];
477 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
478 int   initialRulePlies, FENrulePlies;
479 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
480 int loadFlag = 0;
481 int shuffleOpenings;
482 int mute; // mute all sounds
483
484 // [HGM] vari: next 12 to save and restore variations
485 #define MAX_VARIATIONS 10
486 int framePtr = MAX_MOVES-1; // points to free stack entry
487 int storedGames = 0;
488 int savedFirst[MAX_VARIATIONS];
489 int savedLast[MAX_VARIATIONS];
490 int savedFramePtr[MAX_VARIATIONS];
491 char *savedDetails[MAX_VARIATIONS];
492 ChessMove savedResult[MAX_VARIATIONS];
493
494 void PushTail P((int firstMove, int lastMove));
495 Boolean PopTail P((Boolean annotate));
496 void CleanupTail P((void));
497
498 ChessSquare  FIDEArray[2][BOARD_FILES] = {
499     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
500         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
502         BlackKing, BlackBishop, BlackKnight, BlackRook }
503 };
504
505 ChessSquare twoKingsArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackKing, BlackKnight, BlackRook }
510 };
511
512 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
514         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
515     { BlackRook, BlackMan, BlackBishop, BlackQueen,
516         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
517 };
518
519 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
520     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
521         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
522     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
523         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
524 };
525
526 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
527     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
528         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
529     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
530         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
531 };
532
533 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
534     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
535         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
536     { BlackRook, BlackKnight, BlackMan, BlackFerz,
537         BlackKing, BlackMan, BlackKnight, BlackRook }
538 };
539
540
541 #if (BOARD_FILES>=10)
542 ChessSquare ShogiArray[2][BOARD_FILES] = {
543     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
544         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
545     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
546         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
547 };
548
549 ChessSquare XiangqiArray[2][BOARD_FILES] = {
550     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
551         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
553         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
554 };
555
556 ChessSquare CapablancaArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
558         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
560         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
561 };
562
563 ChessSquare GreatArray[2][BOARD_FILES] = {
564     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
565         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
566     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
567         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
568 };
569
570 ChessSquare JanusArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
572         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
573     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
574         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
575 };
576
577 #ifdef GOTHIC
578 ChessSquare GothicArray[2][BOARD_FILES] = {
579     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
580         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
582         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
583 };
584 #else // !GOTHIC
585 #define GothicArray CapablancaArray
586 #endif // !GOTHIC
587
588 #ifdef FALCON
589 ChessSquare FalconArray[2][BOARD_FILES] = {
590     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
591         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
592     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
593         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
594 };
595 #else // !FALCON
596 #define FalconArray CapablancaArray
597 #endif // !FALCON
598
599 #else // !(BOARD_FILES>=10)
600 #define XiangqiPosition FIDEArray
601 #define CapablancaArray FIDEArray
602 #define GothicArray FIDEArray
603 #define GreatArray FIDEArray
604 #endif // !(BOARD_FILES>=10)
605
606 #if (BOARD_FILES>=12)
607 ChessSquare CourierArray[2][BOARD_FILES] = {
608     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
609         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
610     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
611         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
612 };
613 #else // !(BOARD_FILES>=12)
614 #define CourierArray CapablancaArray
615 #endif // !(BOARD_FILES>=12)
616
617
618 Board initialPosition;
619
620
621 /* Convert str to a rating. Checks for special cases of "----",
622
623    "++++", etc. Also strips ()'s */
624 int
625 string_to_rating(str)
626   char *str;
627 {
628   while(*str && !isdigit(*str)) ++str;
629   if (!*str)
630     return 0;   /* One of the special "no rating" cases */
631   else
632     return atoi(str);
633 }
634
635 void
636 ClearProgramStats()
637 {
638     /* Init programStats */
639     programStats.movelist[0] = 0;
640     programStats.depth = 0;
641     programStats.nr_moves = 0;
642     programStats.moves_left = 0;
643     programStats.nodes = 0;
644     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
645     programStats.score = 0;
646     programStats.got_only_move = 0;
647     programStats.got_fail = 0;
648     programStats.line_is_book = 0;
649 }
650
651 void
652 InitBackEnd1()
653 {
654     int matched, min, sec;
655
656     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
657     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
658
659     GetTimeMark(&programStartTime);
660     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
661
662     ClearProgramStats();
663     programStats.ok_to_send = 1;
664     programStats.seen_stat = 0;
665
666     /*
667      * Initialize game list
668      */
669     ListNew(&gameList);
670
671
672     /*
673      * Internet chess server status
674      */
675     if (appData.icsActive) {
676         appData.matchMode = FALSE;
677         appData.matchGames = 0;
678 #if ZIPPY
679         appData.noChessProgram = !appData.zippyPlay;
680 #else
681         appData.zippyPlay = FALSE;
682         appData.zippyTalk = FALSE;
683         appData.noChessProgram = TRUE;
684 #endif
685         if (*appData.icsHelper != NULLCHAR) {
686             appData.useTelnet = TRUE;
687             appData.telnetProgram = appData.icsHelper;
688         }
689     } else {
690         appData.zippyTalk = appData.zippyPlay = FALSE;
691     }
692
693     /* [AS] Initialize pv info list [HGM] and game state */
694     {
695         int i, j;
696
697         for( i=0; i<=framePtr; i++ ) {
698             pvInfoList[i].depth = -1;
699             boards[i][EP_STATUS] = EP_NONE;
700             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
701         }
702     }
703
704     /*
705      * Parse timeControl resource
706      */
707     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
708                           appData.movesPerSession)) {
709         char buf[MSG_SIZ];
710         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
711         DisplayFatalError(buf, 0, 2);
712     }
713
714     /*
715      * Parse searchTime resource
716      */
717     if (*appData.searchTime != NULLCHAR) {
718         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
719         if (matched == 1) {
720             searchTime = min * 60;
721         } else if (matched == 2) {
722             searchTime = min * 60 + sec;
723         } else {
724             char buf[MSG_SIZ];
725             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
726             DisplayFatalError(buf, 0, 2);
727         }
728     }
729
730     /* [AS] Adjudication threshold */
731     adjudicateLossThreshold = appData.adjudicateLossThreshold;
732
733     first.which = _("first");
734     second.which = _("second");
735     first.maybeThinking = second.maybeThinking = FALSE;
736     first.pr = second.pr = NoProc;
737     first.isr = second.isr = NULL;
738     first.sendTime = second.sendTime = 2;
739     first.sendDrawOffers = 1;
740     if (appData.firstPlaysBlack) {
741         first.twoMachinesColor = "black\n";
742         second.twoMachinesColor = "white\n";
743     } else {
744         first.twoMachinesColor = "white\n";
745         second.twoMachinesColor = "black\n";
746     }
747     first.program = appData.firstChessProgram;
748     second.program = appData.secondChessProgram;
749     first.host = appData.firstHost;
750     second.host = appData.secondHost;
751     first.dir = appData.firstDirectory;
752     second.dir = appData.secondDirectory;
753     first.other = &second;
754     second.other = &first;
755     first.initString = appData.initString;
756     second.initString = appData.secondInitString;
757     first.computerString = appData.firstComputerString;
758     second.computerString = appData.secondComputerString;
759     first.useSigint = second.useSigint = TRUE;
760     first.useSigterm = second.useSigterm = TRUE;
761     first.reuse = appData.reuseFirst;
762     second.reuse = appData.reuseSecond;
763     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
764     second.nps = appData.secondNPS;
765     first.useSetboard = second.useSetboard = FALSE;
766     first.useSAN = second.useSAN = FALSE;
767     first.usePing = second.usePing = FALSE;
768     first.lastPing = second.lastPing = 0;
769     first.lastPong = second.lastPong = 0;
770     first.usePlayother = second.usePlayother = FALSE;
771     first.useColors = second.useColors = TRUE;
772     first.useUsermove = second.useUsermove = FALSE;
773     first.sendICS = second.sendICS = FALSE;
774     first.sendName = second.sendName = appData.icsActive;
775     first.sdKludge = second.sdKludge = FALSE;
776     first.stKludge = second.stKludge = FALSE;
777     TidyProgramName(first.program, first.host, first.tidy);
778     TidyProgramName(second.program, second.host, second.tidy);
779     first.matchWins = second.matchWins = 0;
780     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
781     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
782     first.analysisSupport = second.analysisSupport = 2; /* detect */
783     first.analyzing = second.analyzing = FALSE;
784     first.initDone = second.initDone = FALSE;
785
786     /* New features added by Tord: */
787     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
788     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
789     /* End of new features added by Tord. */
790     first.fenOverride  = appData.fenOverride1;
791     second.fenOverride = appData.fenOverride2;
792
793     /* [HGM] time odds: set factor for each machine */
794     first.timeOdds  = appData.firstTimeOdds;
795     second.timeOdds = appData.secondTimeOdds;
796     { float norm = 1;
797         if(appData.timeOddsMode) {
798             norm = first.timeOdds;
799             if(norm > second.timeOdds) norm = second.timeOdds;
800         }
801         first.timeOdds /= norm;
802         second.timeOdds /= norm;
803     }
804
805     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
806     first.accumulateTC = appData.firstAccumulateTC;
807     second.accumulateTC = appData.secondAccumulateTC;
808     first.maxNrOfSessions = second.maxNrOfSessions = 1;
809
810     /* [HGM] debug */
811     first.debug = second.debug = FALSE;
812     first.supportsNPS = second.supportsNPS = UNKNOWN;
813
814     /* [HGM] options */
815     first.optionSettings  = appData.firstOptions;
816     second.optionSettings = appData.secondOptions;
817
818     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
819     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
820     first.isUCI = appData.firstIsUCI; /* [AS] */
821     second.isUCI = appData.secondIsUCI; /* [AS] */
822     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
823     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
824
825     if (appData.firstProtocolVersion > PROTOVER
826         || appData.firstProtocolVersion < 1)
827       {
828         char buf[MSG_SIZ];
829         int len;
830
831         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
832                        appData.firstProtocolVersion);
833         if( (len > MSG_SIZ) && appData.debugMode )
834           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
835
836         DisplayFatalError(buf, 0, 2);
837       }
838     else
839       {
840         first.protocolVersion = appData.firstProtocolVersion;
841       }
842
843     if (appData.secondProtocolVersion > PROTOVER
844         || appData.secondProtocolVersion < 1)
845       {
846         char buf[MSG_SIZ];
847         int len;
848
849         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
850                        appData.secondProtocolVersion);
851         if( (len > MSG_SIZ) && appData.debugMode )
852           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
853
854         DisplayFatalError(buf, 0, 2);
855       }
856     else
857       {
858         second.protocolVersion = appData.secondProtocolVersion;
859       }
860
861     if (appData.icsActive) {
862         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
863 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
864     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
865         appData.clockMode = FALSE;
866         first.sendTime = second.sendTime = 0;
867     }
868
869 #if ZIPPY
870     /* Override some settings from environment variables, for backward
871        compatibility.  Unfortunately it's not feasible to have the env
872        vars just set defaults, at least in xboard.  Ugh.
873     */
874     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
875       ZippyInit();
876     }
877 #endif
878
879     if (appData.noChessProgram) {
880         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
881         sprintf(programVersion, "%s", PACKAGE_STRING);
882     } else {
883       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
884       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
885       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
886     }
887
888     if (!appData.icsActive) {
889       char buf[MSG_SIZ];
890       int len;
891
892       /* Check for variants that are supported only in ICS mode,
893          or not at all.  Some that are accepted here nevertheless
894          have bugs; see comments below.
895       */
896       VariantClass variant = StringToVariant(appData.variant);
897       switch (variant) {
898       case VariantBughouse:     /* need four players and two boards */
899       case VariantKriegspiel:   /* need to hide pieces and move details */
900         /* case VariantFischeRandom: (Fabien: moved below) */
901         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
902         if( (len > MSG_SIZ) && appData.debugMode )
903           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
904
905         DisplayFatalError(buf, 0, 2);
906         return;
907
908       case VariantUnknown:
909       case VariantLoadable:
910       case Variant29:
911       case Variant30:
912       case Variant31:
913       case Variant32:
914       case Variant33:
915       case Variant34:
916       case Variant35:
917       case Variant36:
918       default:
919         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
920         if( (len > MSG_SIZ) && appData.debugMode )
921           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
922
923         DisplayFatalError(buf, 0, 2);
924         return;
925
926       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
927       case VariantFairy:      /* [HGM] TestLegality definitely off! */
928       case VariantGothic:     /* [HGM] should work */
929       case VariantCapablanca: /* [HGM] should work */
930       case VariantCourier:    /* [HGM] initial forced moves not implemented */
931       case VariantShogi:      /* [HGM] could still mate with pawn drop */
932       case VariantKnightmate: /* [HGM] should work */
933       case VariantCylinder:   /* [HGM] untested */
934       case VariantFalcon:     /* [HGM] untested */
935       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
936                                  offboard interposition not understood */
937       case VariantNormal:     /* definitely works! */
938       case VariantWildCastle: /* pieces not automatically shuffled */
939       case VariantNoCastle:   /* pieces not automatically shuffled */
940       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
941       case VariantLosers:     /* should work except for win condition,
942                                  and doesn't know captures are mandatory */
943       case VariantSuicide:    /* should work except for win condition,
944                                  and doesn't know captures are mandatory */
945       case VariantGiveaway:   /* should work except for win condition,
946                                  and doesn't know captures are mandatory */
947       case VariantTwoKings:   /* should work */
948       case VariantAtomic:     /* should work except for win condition */
949       case Variant3Check:     /* should work except for win condition */
950       case VariantShatranj:   /* should work except for all win conditions */
951       case VariantMakruk:     /* should work except for daw countdown */
952       case VariantBerolina:   /* might work if TestLegality is off */
953       case VariantCapaRandom: /* should work */
954       case VariantJanus:      /* should work */
955       case VariantSuper:      /* experimental */
956       case VariantGreat:      /* experimental, requires legality testing to be off */
957       case VariantSChess:     /* S-Chess, should work */
958         break;
959       }
960     }
961
962     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
963     InitEngineUCI( installDir, &second );
964 }
965
966 int NextIntegerFromString( char ** str, long * value )
967 {
968     int result = -1;
969     char * s = *str;
970
971     while( *s == ' ' || *s == '\t' ) {
972         s++;
973     }
974
975     *value = 0;
976
977     if( *s >= '0' && *s <= '9' ) {
978         while( *s >= '0' && *s <= '9' ) {
979             *value = *value * 10 + (*s - '0');
980             s++;
981         }
982
983         result = 0;
984     }
985
986     *str = s;
987
988     return result;
989 }
990
991 int NextTimeControlFromString( char ** str, long * value )
992 {
993     long temp;
994     int result = NextIntegerFromString( str, &temp );
995
996     if( result == 0 ) {
997         *value = temp * 60; /* Minutes */
998         if( **str == ':' ) {
999             (*str)++;
1000             result = NextIntegerFromString( str, &temp );
1001             *value += temp; /* Seconds */
1002         }
1003     }
1004
1005     return result;
1006 }
1007
1008 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1009 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1010     int result = -1, type = 0; long temp, temp2;
1011
1012     if(**str != ':') return -1; // old params remain in force!
1013     (*str)++;
1014     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1015     if( NextIntegerFromString( str, &temp ) ) return -1;
1016     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1017
1018     if(**str != '/') {
1019         /* time only: incremental or sudden-death time control */
1020         if(**str == '+') { /* increment follows; read it */
1021             (*str)++;
1022             if(**str == '!') type = *(*str)++; // Bronstein TC
1023             if(result = NextIntegerFromString( str, &temp2)) return -1;
1024             *inc = temp2 * 1000;
1025             if(**str == '.') { // read fraction of increment
1026                 char *start = ++(*str);
1027                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1028                 temp2 *= 1000;
1029                 while(start++ < *str) temp2 /= 10;
1030                 *inc += temp2;
1031             }
1032         } else *inc = 0;
1033         *moves = 0; *tc = temp * 1000; *incType = type;
1034         return 0;
1035     }
1036
1037     (*str)++; /* classical time control */
1038     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1039
1040     if(result == 0) {
1041         *moves = temp;
1042         *tc    = temp2 * 1000;
1043         *inc   = 0;
1044         *incType = type;
1045     }
1046     return result;
1047 }
1048
1049 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1050 {   /* [HGM] get time to add from the multi-session time-control string */
1051     int incType, moves=1; /* kludge to force reading of first session */
1052     long time, increment;
1053     char *s = tcString;
1054
1055     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1056     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1057     do {
1058         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1059         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1060         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1061         if(movenr == -1) return time;    /* last move before new session     */
1062         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1063         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1064         if(!moves) return increment;     /* current session is incremental   */
1065         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1066     } while(movenr >= -1);               /* try again for next session       */
1067
1068     return 0; // no new time quota on this move
1069 }
1070
1071 int
1072 ParseTimeControl(tc, ti, mps)
1073      char *tc;
1074      float ti;
1075      int mps;
1076 {
1077   long tc1;
1078   long tc2;
1079   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1080   int min, sec=0;
1081
1082   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1083   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1084       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1085   if(ti > 0) {
1086
1087     if(mps)
1088       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1089     else 
1090       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1091   } else {
1092     if(mps)
1093       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1094     else 
1095       snprintf(buf, MSG_SIZ, ":%s", mytc);
1096   }
1097   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1098   
1099   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1100     return FALSE;
1101   }
1102
1103   if( *tc == '/' ) {
1104     /* Parse second time control */
1105     tc++;
1106
1107     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1108       return FALSE;
1109     }
1110
1111     if( tc2 == 0 ) {
1112       return FALSE;
1113     }
1114
1115     timeControl_2 = tc2 * 1000;
1116   }
1117   else {
1118     timeControl_2 = 0;
1119   }
1120
1121   if( tc1 == 0 ) {
1122     return FALSE;
1123   }
1124
1125   timeControl = tc1 * 1000;
1126
1127   if (ti >= 0) {
1128     timeIncrement = ti * 1000;  /* convert to ms */
1129     movesPerSession = 0;
1130   } else {
1131     timeIncrement = 0;
1132     movesPerSession = mps;
1133   }
1134   return TRUE;
1135 }
1136
1137 void
1138 InitBackEnd2()
1139 {
1140     if (appData.debugMode) {
1141         fprintf(debugFP, "%s\n", programVersion);
1142     }
1143
1144     set_cont_sequence(appData.wrapContSeq);
1145     if (appData.matchGames > 0) {
1146         appData.matchMode = TRUE;
1147     } else if (appData.matchMode) {
1148         appData.matchGames = 1;
1149     }
1150     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1151         appData.matchGames = appData.sameColorGames;
1152     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1153         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1154         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1155     }
1156     Reset(TRUE, FALSE);
1157     if (appData.noChessProgram || first.protocolVersion == 1) {
1158       InitBackEnd3();
1159     } else {
1160       /* kludge: allow timeout for initial "feature" commands */
1161       FreezeUI();
1162       DisplayMessage("", _("Starting chess program"));
1163       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1164     }
1165 }
1166
1167 void
1168 InitBackEnd3 P((void))
1169 {
1170     GameMode initialMode;
1171     char buf[MSG_SIZ];
1172     int err, len;
1173
1174     InitChessProgram(&first, startedFromSetupPosition);
1175
1176     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1177         free(programVersion);
1178         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1179         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1180     }
1181
1182     if (appData.icsActive) {
1183 #ifdef WIN32
1184         /* [DM] Make a console window if needed [HGM] merged ifs */
1185         ConsoleCreate();
1186 #endif
1187         err = establish();
1188         if (err != 0)
1189           {
1190             if (*appData.icsCommPort != NULLCHAR)
1191               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1192                              appData.icsCommPort);
1193             else
1194               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1195                         appData.icsHost, appData.icsPort);
1196
1197             if( (len > MSG_SIZ) && appData.debugMode )
1198               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1199
1200             DisplayFatalError(buf, err, 1);
1201             return;
1202         }
1203         SetICSMode();
1204         telnetISR =
1205           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1206         fromUserISR =
1207           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1208         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1209             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1210     } else if (appData.noChessProgram) {
1211         SetNCPMode();
1212     } else {
1213         SetGNUMode();
1214     }
1215
1216     if (*appData.cmailGameName != NULLCHAR) {
1217         SetCmailMode();
1218         OpenLoopback(&cmailPR);
1219         cmailISR =
1220           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1221     }
1222
1223     ThawUI();
1224     DisplayMessage("", "");
1225     if (StrCaseCmp(appData.initialMode, "") == 0) {
1226       initialMode = BeginningOfGame;
1227     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1228       initialMode = TwoMachinesPlay;
1229     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1230       initialMode = AnalyzeFile;
1231     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1232       initialMode = AnalyzeMode;
1233     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1234       initialMode = MachinePlaysWhite;
1235     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1236       initialMode = MachinePlaysBlack;
1237     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1238       initialMode = EditGame;
1239     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1240       initialMode = EditPosition;
1241     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1242       initialMode = Training;
1243     } else {
1244       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1245       if( (len > MSG_SIZ) && appData.debugMode )
1246         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1247
1248       DisplayFatalError(buf, 0, 2);
1249       return;
1250     }
1251
1252     if (appData.matchMode) {
1253         /* Set up machine vs. machine match */
1254         if (appData.noChessProgram) {
1255             DisplayFatalError(_("Can't have a match with no chess programs"),
1256                               0, 2);
1257             return;
1258         }
1259         matchMode = TRUE;
1260         matchGame = 1;
1261         if (*appData.loadGameFile != NULLCHAR) {
1262             int index = appData.loadGameIndex; // [HGM] autoinc
1263             if(index<0) lastIndex = index = 1;
1264             if (!LoadGameFromFile(appData.loadGameFile,
1265                                   index,
1266                                   appData.loadGameFile, FALSE)) {
1267                 DisplayFatalError(_("Bad game file"), 0, 1);
1268                 return;
1269             }
1270         } else if (*appData.loadPositionFile != NULLCHAR) {
1271             int index = appData.loadPositionIndex; // [HGM] autoinc
1272             if(index<0) lastIndex = index = 1;
1273             if (!LoadPositionFromFile(appData.loadPositionFile,
1274                                       index,
1275                                       appData.loadPositionFile)) {
1276                 DisplayFatalError(_("Bad position file"), 0, 1);
1277                 return;
1278             }
1279         }
1280         TwoMachinesEvent();
1281     } else if (*appData.cmailGameName != NULLCHAR) {
1282         /* Set up cmail mode */
1283         ReloadCmailMsgEvent(TRUE);
1284     } else {
1285         /* Set up other modes */
1286         if (initialMode == AnalyzeFile) {
1287           if (*appData.loadGameFile == NULLCHAR) {
1288             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1289             return;
1290           }
1291         }
1292         if (*appData.loadGameFile != NULLCHAR) {
1293             (void) LoadGameFromFile(appData.loadGameFile,
1294                                     appData.loadGameIndex,
1295                                     appData.loadGameFile, TRUE);
1296         } else if (*appData.loadPositionFile != NULLCHAR) {
1297             (void) LoadPositionFromFile(appData.loadPositionFile,
1298                                         appData.loadPositionIndex,
1299                                         appData.loadPositionFile);
1300             /* [HGM] try to make self-starting even after FEN load */
1301             /* to allow automatic setup of fairy variants with wtm */
1302             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1303                 gameMode = BeginningOfGame;
1304                 setboardSpoiledMachineBlack = 1;
1305             }
1306             /* [HGM] loadPos: make that every new game uses the setup */
1307             /* from file as long as we do not switch variant          */
1308             if(!blackPlaysFirst) {
1309                 startedFromPositionFile = TRUE;
1310                 CopyBoard(filePosition, boards[0]);
1311             }
1312         }
1313         if (initialMode == AnalyzeMode) {
1314           if (appData.noChessProgram) {
1315             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1316             return;
1317           }
1318           if (appData.icsActive) {
1319             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1320             return;
1321           }
1322           AnalyzeModeEvent();
1323         } else if (initialMode == AnalyzeFile) {
1324           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1325           ShowThinkingEvent();
1326           AnalyzeFileEvent();
1327           AnalysisPeriodicEvent(1);
1328         } else if (initialMode == MachinePlaysWhite) {
1329           if (appData.noChessProgram) {
1330             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1331                               0, 2);
1332             return;
1333           }
1334           if (appData.icsActive) {
1335             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1336                               0, 2);
1337             return;
1338           }
1339           MachineWhiteEvent();
1340         } else if (initialMode == MachinePlaysBlack) {
1341           if (appData.noChessProgram) {
1342             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1343                               0, 2);
1344             return;
1345           }
1346           if (appData.icsActive) {
1347             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1348                               0, 2);
1349             return;
1350           }
1351           MachineBlackEvent();
1352         } else if (initialMode == TwoMachinesPlay) {
1353           if (appData.noChessProgram) {
1354             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1355                               0, 2);
1356             return;
1357           }
1358           if (appData.icsActive) {
1359             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1360                               0, 2);
1361             return;
1362           }
1363           TwoMachinesEvent();
1364         } else if (initialMode == EditGame) {
1365           EditGameEvent();
1366         } else if (initialMode == EditPosition) {
1367           EditPositionEvent();
1368         } else if (initialMode == Training) {
1369           if (*appData.loadGameFile == NULLCHAR) {
1370             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1371             return;
1372           }
1373           TrainingEvent();
1374         }
1375     }
1376 }
1377
1378 /*
1379  * Establish will establish a contact to a remote host.port.
1380  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1381  *  used to talk to the host.
1382  * Returns 0 if okay, error code if not.
1383  */
1384 int
1385 establish()
1386 {
1387     char buf[MSG_SIZ];
1388
1389     if (*appData.icsCommPort != NULLCHAR) {
1390         /* Talk to the host through a serial comm port */
1391         return OpenCommPort(appData.icsCommPort, &icsPR);
1392
1393     } else if (*appData.gateway != NULLCHAR) {
1394         if (*appData.remoteShell == NULLCHAR) {
1395             /* Use the rcmd protocol to run telnet program on a gateway host */
1396             snprintf(buf, sizeof(buf), "%s %s %s",
1397                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1398             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1399
1400         } else {
1401             /* Use the rsh program to run telnet program on a gateway host */
1402             if (*appData.remoteUser == NULLCHAR) {
1403                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1404                         appData.gateway, appData.telnetProgram,
1405                         appData.icsHost, appData.icsPort);
1406             } else {
1407                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1408                         appData.remoteShell, appData.gateway,
1409                         appData.remoteUser, appData.telnetProgram,
1410                         appData.icsHost, appData.icsPort);
1411             }
1412             return StartChildProcess(buf, "", &icsPR);
1413
1414         }
1415     } else if (appData.useTelnet) {
1416         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1417
1418     } else {
1419         /* TCP socket interface differs somewhat between
1420            Unix and NT; handle details in the front end.
1421            */
1422         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1423     }
1424 }
1425
1426 void EscapeExpand(char *p, char *q)
1427 {       // [HGM] initstring: routine to shape up string arguments
1428         while(*p++ = *q++) if(p[-1] == '\\')
1429             switch(*q++) {
1430                 case 'n': p[-1] = '\n'; break;
1431                 case 'r': p[-1] = '\r'; break;
1432                 case 't': p[-1] = '\t'; break;
1433                 case '\\': p[-1] = '\\'; break;
1434                 case 0: *p = 0; return;
1435                 default: p[-1] = q[-1]; break;
1436             }
1437 }
1438
1439 void
1440 show_bytes(fp, buf, count)
1441      FILE *fp;
1442      char *buf;
1443      int count;
1444 {
1445     while (count--) {
1446         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1447             fprintf(fp, "\\%03o", *buf & 0xff);
1448         } else {
1449             putc(*buf, fp);
1450         }
1451         buf++;
1452     }
1453     fflush(fp);
1454 }
1455
1456 /* Returns an errno value */
1457 int
1458 OutputMaybeTelnet(pr, message, count, outError)
1459      ProcRef pr;
1460      char *message;
1461      int count;
1462      int *outError;
1463 {
1464     char buf[8192], *p, *q, *buflim;
1465     int left, newcount, outcount;
1466
1467     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1468         *appData.gateway != NULLCHAR) {
1469         if (appData.debugMode) {
1470             fprintf(debugFP, ">ICS: ");
1471             show_bytes(debugFP, message, count);
1472             fprintf(debugFP, "\n");
1473         }
1474         return OutputToProcess(pr, message, count, outError);
1475     }
1476
1477     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1478     p = message;
1479     q = buf;
1480     left = count;
1481     newcount = 0;
1482     while (left) {
1483         if (q >= buflim) {
1484             if (appData.debugMode) {
1485                 fprintf(debugFP, ">ICS: ");
1486                 show_bytes(debugFP, buf, newcount);
1487                 fprintf(debugFP, "\n");
1488             }
1489             outcount = OutputToProcess(pr, buf, newcount, outError);
1490             if (outcount < newcount) return -1; /* to be sure */
1491             q = buf;
1492             newcount = 0;
1493         }
1494         if (*p == '\n') {
1495             *q++ = '\r';
1496             newcount++;
1497         } else if (((unsigned char) *p) == TN_IAC) {
1498             *q++ = (char) TN_IAC;
1499             newcount ++;
1500         }
1501         *q++ = *p++;
1502         newcount++;
1503         left--;
1504     }
1505     if (appData.debugMode) {
1506         fprintf(debugFP, ">ICS: ");
1507         show_bytes(debugFP, buf, newcount);
1508         fprintf(debugFP, "\n");
1509     }
1510     outcount = OutputToProcess(pr, buf, newcount, outError);
1511     if (outcount < newcount) return -1; /* to be sure */
1512     return count;
1513 }
1514
1515 void
1516 read_from_player(isr, closure, message, count, error)
1517      InputSourceRef isr;
1518      VOIDSTAR closure;
1519      char *message;
1520      int count;
1521      int error;
1522 {
1523     int outError, outCount;
1524     static int gotEof = 0;
1525
1526     /* Pass data read from player on to ICS */
1527     if (count > 0) {
1528         gotEof = 0;
1529         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1530         if (outCount < count) {
1531             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1532         }
1533     } else if (count < 0) {
1534         RemoveInputSource(isr);
1535         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1536     } else if (gotEof++ > 0) {
1537         RemoveInputSource(isr);
1538         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1539     }
1540 }
1541
1542 void
1543 KeepAlive()
1544 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1545     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1546     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1547     SendToICS("date\n");
1548     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1549 }
1550
1551 /* added routine for printf style output to ics */
1552 void ics_printf(char *format, ...)
1553 {
1554     char buffer[MSG_SIZ];
1555     va_list args;
1556
1557     va_start(args, format);
1558     vsnprintf(buffer, sizeof(buffer), format, args);
1559     buffer[sizeof(buffer)-1] = '\0';
1560     SendToICS(buffer);
1561     va_end(args);
1562 }
1563
1564 void
1565 SendToICS(s)
1566      char *s;
1567 {
1568     int count, outCount, outError;
1569
1570     if (icsPR == NULL) return;
1571
1572     count = strlen(s);
1573     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1574     if (outCount < count) {
1575         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1576     }
1577 }
1578
1579 /* This is used for sending logon scripts to the ICS. Sending
1580    without a delay causes problems when using timestamp on ICC
1581    (at least on my machine). */
1582 void
1583 SendToICSDelayed(s,msdelay)
1584      char *s;
1585      long msdelay;
1586 {
1587     int count, outCount, outError;
1588
1589     if (icsPR == NULL) return;
1590
1591     count = strlen(s);
1592     if (appData.debugMode) {
1593         fprintf(debugFP, ">ICS: ");
1594         show_bytes(debugFP, s, count);
1595         fprintf(debugFP, "\n");
1596     }
1597     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1598                                       msdelay);
1599     if (outCount < count) {
1600         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1601     }
1602 }
1603
1604
1605 /* Remove all highlighting escape sequences in s
1606    Also deletes any suffix starting with '('
1607    */
1608 char *
1609 StripHighlightAndTitle(s)
1610      char *s;
1611 {
1612     static char retbuf[MSG_SIZ];
1613     char *p = retbuf;
1614
1615     while (*s != NULLCHAR) {
1616         while (*s == '\033') {
1617             while (*s != NULLCHAR && !isalpha(*s)) s++;
1618             if (*s != NULLCHAR) s++;
1619         }
1620         while (*s != NULLCHAR && *s != '\033') {
1621             if (*s == '(' || *s == '[') {
1622                 *p = NULLCHAR;
1623                 return retbuf;
1624             }
1625             *p++ = *s++;
1626         }
1627     }
1628     *p = NULLCHAR;
1629     return retbuf;
1630 }
1631
1632 /* Remove all highlighting escape sequences in s */
1633 char *
1634 StripHighlight(s)
1635      char *s;
1636 {
1637     static char retbuf[MSG_SIZ];
1638     char *p = retbuf;
1639
1640     while (*s != NULLCHAR) {
1641         while (*s == '\033') {
1642             while (*s != NULLCHAR && !isalpha(*s)) s++;
1643             if (*s != NULLCHAR) s++;
1644         }
1645         while (*s != NULLCHAR && *s != '\033') {
1646             *p++ = *s++;
1647         }
1648     }
1649     *p = NULLCHAR;
1650     return retbuf;
1651 }
1652
1653 char *variantNames[] = VARIANT_NAMES;
1654 char *
1655 VariantName(v)
1656      VariantClass v;
1657 {
1658     return variantNames[v];
1659 }
1660
1661
1662 /* Identify a variant from the strings the chess servers use or the
1663    PGN Variant tag names we use. */
1664 VariantClass
1665 StringToVariant(e)
1666      char *e;
1667 {
1668     char *p;
1669     int wnum = -1;
1670     VariantClass v = VariantNormal;
1671     int i, found = FALSE;
1672     char buf[MSG_SIZ];
1673     int len;
1674
1675     if (!e) return v;
1676
1677     /* [HGM] skip over optional board-size prefixes */
1678     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1679         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1680         while( *e++ != '_');
1681     }
1682
1683     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1684         v = VariantNormal;
1685         found = TRUE;
1686     } else
1687     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1688       if (StrCaseStr(e, variantNames[i])) {
1689         v = (VariantClass) i;
1690         found = TRUE;
1691         break;
1692       }
1693     }
1694
1695     if (!found) {
1696       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1697           || StrCaseStr(e, "wild/fr")
1698           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1699         v = VariantFischeRandom;
1700       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1701                  (i = 1, p = StrCaseStr(e, "w"))) {
1702         p += i;
1703         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1704         if (isdigit(*p)) {
1705           wnum = atoi(p);
1706         } else {
1707           wnum = -1;
1708         }
1709         switch (wnum) {
1710         case 0: /* FICS only, actually */
1711         case 1:
1712           /* Castling legal even if K starts on d-file */
1713           v = VariantWildCastle;
1714           break;
1715         case 2:
1716         case 3:
1717         case 4:
1718           /* Castling illegal even if K & R happen to start in
1719              normal positions. */
1720           v = VariantNoCastle;
1721           break;
1722         case 5:
1723         case 7:
1724         case 8:
1725         case 10:
1726         case 11:
1727         case 12:
1728         case 13:
1729         case 14:
1730         case 15:
1731         case 18:
1732         case 19:
1733           /* Castling legal iff K & R start in normal positions */
1734           v = VariantNormal;
1735           break;
1736         case 6:
1737         case 20:
1738         case 21:
1739           /* Special wilds for position setup; unclear what to do here */
1740           v = VariantLoadable;
1741           break;
1742         case 9:
1743           /* Bizarre ICC game */
1744           v = VariantTwoKings;
1745           break;
1746         case 16:
1747           v = VariantKriegspiel;
1748           break;
1749         case 17:
1750           v = VariantLosers;
1751           break;
1752         case 22:
1753           v = VariantFischeRandom;
1754           break;
1755         case 23:
1756           v = VariantCrazyhouse;
1757           break;
1758         case 24:
1759           v = VariantBughouse;
1760           break;
1761         case 25:
1762           v = Variant3Check;
1763           break;
1764         case 26:
1765           /* Not quite the same as FICS suicide! */
1766           v = VariantGiveaway;
1767           break;
1768         case 27:
1769           v = VariantAtomic;
1770           break;
1771         case 28:
1772           v = VariantShatranj;
1773           break;
1774
1775         /* Temporary names for future ICC types.  The name *will* change in
1776            the next xboard/WinBoard release after ICC defines it. */
1777         case 29:
1778           v = Variant29;
1779           break;
1780         case 30:
1781           v = Variant30;
1782           break;
1783         case 31:
1784           v = Variant31;
1785           break;
1786         case 32:
1787           v = Variant32;
1788           break;
1789         case 33:
1790           v = Variant33;
1791           break;
1792         case 34:
1793           v = Variant34;
1794           break;
1795         case 35:
1796           v = Variant35;
1797           break;
1798         case 36:
1799           v = Variant36;
1800           break;
1801         case 37:
1802           v = VariantShogi;
1803           break;
1804         case 38:
1805           v = VariantXiangqi;
1806           break;
1807         case 39:
1808           v = VariantCourier;
1809           break;
1810         case 40:
1811           v = VariantGothic;
1812           break;
1813         case 41:
1814           v = VariantCapablanca;
1815           break;
1816         case 42:
1817           v = VariantKnightmate;
1818           break;
1819         case 43:
1820           v = VariantFairy;
1821           break;
1822         case 44:
1823           v = VariantCylinder;
1824           break;
1825         case 45:
1826           v = VariantFalcon;
1827           break;
1828         case 46:
1829           v = VariantCapaRandom;
1830           break;
1831         case 47:
1832           v = VariantBerolina;
1833           break;
1834         case 48:
1835           v = VariantJanus;
1836           break;
1837         case 49:
1838           v = VariantSuper;
1839           break;
1840         case 50:
1841           v = VariantGreat;
1842           break;
1843         case -1:
1844           /* Found "wild" or "w" in the string but no number;
1845              must assume it's normal chess. */
1846           v = VariantNormal;
1847           break;
1848         default:
1849           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1850           if( (len > MSG_SIZ) && appData.debugMode )
1851             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1852
1853           DisplayError(buf, 0);
1854           v = VariantUnknown;
1855           break;
1856         }
1857       }
1858     }
1859     if (appData.debugMode) {
1860       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1861               e, wnum, VariantName(v));
1862     }
1863     return v;
1864 }
1865
1866 static int leftover_start = 0, leftover_len = 0;
1867 char star_match[STAR_MATCH_N][MSG_SIZ];
1868
1869 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1870    advance *index beyond it, and set leftover_start to the new value of
1871    *index; else return FALSE.  If pattern contains the character '*', it
1872    matches any sequence of characters not containing '\r', '\n', or the
1873    character following the '*' (if any), and the matched sequence(s) are
1874    copied into star_match.
1875    */
1876 int
1877 looking_at(buf, index, pattern)
1878      char *buf;
1879      int *index;
1880      char *pattern;
1881 {
1882     char *bufp = &buf[*index], *patternp = pattern;
1883     int star_count = 0;
1884     char *matchp = star_match[0];
1885
1886     for (;;) {
1887         if (*patternp == NULLCHAR) {
1888             *index = leftover_start = bufp - buf;
1889             *matchp = NULLCHAR;
1890             return TRUE;
1891         }
1892         if (*bufp == NULLCHAR) return FALSE;
1893         if (*patternp == '*') {
1894             if (*bufp == *(patternp + 1)) {
1895                 *matchp = NULLCHAR;
1896                 matchp = star_match[++star_count];
1897                 patternp += 2;
1898                 bufp++;
1899                 continue;
1900             } else if (*bufp == '\n' || *bufp == '\r') {
1901                 patternp++;
1902                 if (*patternp == NULLCHAR)
1903                   continue;
1904                 else
1905                   return FALSE;
1906             } else {
1907                 *matchp++ = *bufp++;
1908                 continue;
1909             }
1910         }
1911         if (*patternp != *bufp) return FALSE;
1912         patternp++;
1913         bufp++;
1914     }
1915 }
1916
1917 void
1918 SendToPlayer(data, length)
1919      char *data;
1920      int length;
1921 {
1922     int error, outCount;
1923     outCount = OutputToProcess(NoProc, data, length, &error);
1924     if (outCount < length) {
1925         DisplayFatalError(_("Error writing to display"), error, 1);
1926     }
1927 }
1928
1929 void
1930 PackHolding(packed, holding)
1931      char packed[];
1932      char *holding;
1933 {
1934     char *p = holding;
1935     char *q = packed;
1936     int runlength = 0;
1937     int curr = 9999;
1938     do {
1939         if (*p == curr) {
1940             runlength++;
1941         } else {
1942             switch (runlength) {
1943               case 0:
1944                 break;
1945               case 1:
1946                 *q++ = curr;
1947                 break;
1948               case 2:
1949                 *q++ = curr;
1950                 *q++ = curr;
1951                 break;
1952               default:
1953                 sprintf(q, "%d", runlength);
1954                 while (*q) q++;
1955                 *q++ = curr;
1956                 break;
1957             }
1958             runlength = 1;
1959             curr = *p;
1960         }
1961     } while (*p++);
1962     *q = NULLCHAR;
1963 }
1964
1965 /* Telnet protocol requests from the front end */
1966 void
1967 TelnetRequest(ddww, option)
1968      unsigned char ddww, option;
1969 {
1970     unsigned char msg[3];
1971     int outCount, outError;
1972
1973     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1974
1975     if (appData.debugMode) {
1976         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1977         switch (ddww) {
1978           case TN_DO:
1979             ddwwStr = "DO";
1980             break;
1981           case TN_DONT:
1982             ddwwStr = "DONT";
1983             break;
1984           case TN_WILL:
1985             ddwwStr = "WILL";
1986             break;
1987           case TN_WONT:
1988             ddwwStr = "WONT";
1989             break;
1990           default:
1991             ddwwStr = buf1;
1992             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
1993             break;
1994         }
1995         switch (option) {
1996           case TN_ECHO:
1997             optionStr = "ECHO";
1998             break;
1999           default:
2000             optionStr = buf2;
2001             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2002             break;
2003         }
2004         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2005     }
2006     msg[0] = TN_IAC;
2007     msg[1] = ddww;
2008     msg[2] = option;
2009     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2010     if (outCount < 3) {
2011         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2012     }
2013 }
2014
2015 void
2016 DoEcho()
2017 {
2018     if (!appData.icsActive) return;
2019     TelnetRequest(TN_DO, TN_ECHO);
2020 }
2021
2022 void
2023 DontEcho()
2024 {
2025     if (!appData.icsActive) return;
2026     TelnetRequest(TN_DONT, TN_ECHO);
2027 }
2028
2029 void
2030 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2031 {
2032     /* put the holdings sent to us by the server on the board holdings area */
2033     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2034     char p;
2035     ChessSquare piece;
2036
2037     if(gameInfo.holdingsWidth < 2)  return;
2038     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2039         return; // prevent overwriting by pre-board holdings
2040
2041     if( (int)lowestPiece >= BlackPawn ) {
2042         holdingsColumn = 0;
2043         countsColumn = 1;
2044         holdingsStartRow = BOARD_HEIGHT-1;
2045         direction = -1;
2046     } else {
2047         holdingsColumn = BOARD_WIDTH-1;
2048         countsColumn = BOARD_WIDTH-2;
2049         holdingsStartRow = 0;
2050         direction = 1;
2051     }
2052
2053     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2054         board[i][holdingsColumn] = EmptySquare;
2055         board[i][countsColumn]   = (ChessSquare) 0;
2056     }
2057     while( (p=*holdings++) != NULLCHAR ) {
2058         piece = CharToPiece( ToUpper(p) );
2059         if(piece == EmptySquare) continue;
2060         /*j = (int) piece - (int) WhitePawn;*/
2061         j = PieceToNumber(piece);
2062         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2063         if(j < 0) continue;               /* should not happen */
2064         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2065         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2066         board[holdingsStartRow+j*direction][countsColumn]++;
2067     }
2068 }
2069
2070
2071 void
2072 VariantSwitch(Board board, VariantClass newVariant)
2073 {
2074    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2075    static Board oldBoard;
2076
2077    startedFromPositionFile = FALSE;
2078    if(gameInfo.variant == newVariant) return;
2079
2080    /* [HGM] This routine is called each time an assignment is made to
2081     * gameInfo.variant during a game, to make sure the board sizes
2082     * are set to match the new variant. If that means adding or deleting
2083     * holdings, we shift the playing board accordingly
2084     * This kludge is needed because in ICS observe mode, we get boards
2085     * of an ongoing game without knowing the variant, and learn about the
2086     * latter only later. This can be because of the move list we requested,
2087     * in which case the game history is refilled from the beginning anyway,
2088     * but also when receiving holdings of a crazyhouse game. In the latter
2089     * case we want to add those holdings to the already received position.
2090     */
2091
2092
2093    if (appData.debugMode) {
2094      fprintf(debugFP, "Switch board from %s to %s\n",
2095              VariantName(gameInfo.variant), VariantName(newVariant));
2096      setbuf(debugFP, NULL);
2097    }
2098    shuffleOpenings = 0;       /* [HGM] shuffle */
2099    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2100    switch(newVariant)
2101      {
2102      case VariantShogi:
2103        newWidth = 9;  newHeight = 9;
2104        gameInfo.holdingsSize = 7;
2105      case VariantBughouse:
2106      case VariantCrazyhouse:
2107        newHoldingsWidth = 2; break;
2108      case VariantGreat:
2109        newWidth = 10;
2110      case VariantSuper:
2111        newHoldingsWidth = 2;
2112        gameInfo.holdingsSize = 8;
2113        break;
2114      case VariantGothic:
2115      case VariantCapablanca:
2116      case VariantCapaRandom:
2117        newWidth = 10;
2118      default:
2119        newHoldingsWidth = gameInfo.holdingsSize = 0;
2120      };
2121
2122    if(newWidth  != gameInfo.boardWidth  ||
2123       newHeight != gameInfo.boardHeight ||
2124       newHoldingsWidth != gameInfo.holdingsWidth ) {
2125
2126      /* shift position to new playing area, if needed */
2127      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2128        for(i=0; i<BOARD_HEIGHT; i++)
2129          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2130            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2131              board[i][j];
2132        for(i=0; i<newHeight; i++) {
2133          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2134          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2135        }
2136      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2137        for(i=0; i<BOARD_HEIGHT; i++)
2138          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2139            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2140              board[i][j];
2141      }
2142      gameInfo.boardWidth  = newWidth;
2143      gameInfo.boardHeight = newHeight;
2144      gameInfo.holdingsWidth = newHoldingsWidth;
2145      gameInfo.variant = newVariant;
2146      InitDrawingSizes(-2, 0);
2147    } else gameInfo.variant = newVariant;
2148    CopyBoard(oldBoard, board);   // remember correctly formatted board
2149      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2150    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2151 }
2152
2153 static int loggedOn = FALSE;
2154
2155 /*-- Game start info cache: --*/
2156 int gs_gamenum;
2157 char gs_kind[MSG_SIZ];
2158 static char player1Name[128] = "";
2159 static char player2Name[128] = "";
2160 static char cont_seq[] = "\n\\   ";
2161 static int player1Rating = -1;
2162 static int player2Rating = -1;
2163 /*----------------------------*/
2164
2165 ColorClass curColor = ColorNormal;
2166 int suppressKibitz = 0;
2167
2168 // [HGM] seekgraph
2169 Boolean soughtPending = FALSE;
2170 Boolean seekGraphUp;
2171 #define MAX_SEEK_ADS 200
2172 #define SQUARE 0x80
2173 char *seekAdList[MAX_SEEK_ADS];
2174 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2175 float tcList[MAX_SEEK_ADS];
2176 char colorList[MAX_SEEK_ADS];
2177 int nrOfSeekAds = 0;
2178 int minRating = 1010, maxRating = 2800;
2179 int hMargin = 10, vMargin = 20, h, w;
2180 extern int squareSize, lineGap;
2181
2182 void
2183 PlotSeekAd(int i)
2184 {
2185         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2186         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2187         if(r < minRating+100 && r >=0 ) r = minRating+100;
2188         if(r > maxRating) r = maxRating;
2189         if(tc < 1.) tc = 1.;
2190         if(tc > 95.) tc = 95.;
2191         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2192         y = ((double)r - minRating)/(maxRating - minRating)
2193             * (h-vMargin-squareSize/8-1) + vMargin;
2194         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2195         if(strstr(seekAdList[i], " u ")) color = 1;
2196         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2197            !strstr(seekAdList[i], "bullet") &&
2198            !strstr(seekAdList[i], "blitz") &&
2199            !strstr(seekAdList[i], "standard") ) color = 2;
2200         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2201         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2202 }
2203
2204 void
2205 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2206 {
2207         char buf[MSG_SIZ], *ext = "";
2208         VariantClass v = StringToVariant(type);
2209         if(strstr(type, "wild")) {
2210             ext = type + 4; // append wild number
2211             if(v == VariantFischeRandom) type = "chess960"; else
2212             if(v == VariantLoadable) type = "setup"; else
2213             type = VariantName(v);
2214         }
2215         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2216         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2217             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2218             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2219             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2220             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2221             seekNrList[nrOfSeekAds] = nr;
2222             zList[nrOfSeekAds] = 0;
2223             seekAdList[nrOfSeekAds++] = StrSave(buf);
2224             if(plot) PlotSeekAd(nrOfSeekAds-1);
2225         }
2226 }
2227
2228 void
2229 EraseSeekDot(int i)
2230 {
2231     int x = xList[i], y = yList[i], d=squareSize/4, k;
2232     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2233     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2234     // now replot every dot that overlapped
2235     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2236         int xx = xList[k], yy = yList[k];
2237         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2238             DrawSeekDot(xx, yy, colorList[k]);
2239     }
2240 }
2241
2242 void
2243 RemoveSeekAd(int nr)
2244 {
2245         int i;
2246         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2247             EraseSeekDot(i);
2248             if(seekAdList[i]) free(seekAdList[i]);
2249             seekAdList[i] = seekAdList[--nrOfSeekAds];
2250             seekNrList[i] = seekNrList[nrOfSeekAds];
2251             ratingList[i] = ratingList[nrOfSeekAds];
2252             colorList[i]  = colorList[nrOfSeekAds];
2253             tcList[i] = tcList[nrOfSeekAds];
2254             xList[i]  = xList[nrOfSeekAds];
2255             yList[i]  = yList[nrOfSeekAds];
2256             zList[i]  = zList[nrOfSeekAds];
2257             seekAdList[nrOfSeekAds] = NULL;
2258             break;
2259         }
2260 }
2261
2262 Boolean
2263 MatchSoughtLine(char *line)
2264 {
2265     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2266     int nr, base, inc, u=0; char dummy;
2267
2268     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2269        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2270        (u=1) &&
2271        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2272         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2273         // match: compact and save the line
2274         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2275         return TRUE;
2276     }
2277     return FALSE;
2278 }
2279
2280 int
2281 DrawSeekGraph()
2282 {
2283     int i;
2284     if(!seekGraphUp) return FALSE;
2285     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2286     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2287
2288     DrawSeekBackground(0, 0, w, h);
2289     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2290     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2291     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2292         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2293         yy = h-1-yy;
2294         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2295         if(i%500 == 0) {
2296             char buf[MSG_SIZ];
2297             snprintf(buf, MSG_SIZ, "%d", i);
2298             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2299         }
2300     }
2301     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2302     for(i=1; i<100; i+=(i<10?1:5)) {
2303         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2304         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2305         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2306             char buf[MSG_SIZ];
2307             snprintf(buf, MSG_SIZ, "%d", i);
2308             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2309         }
2310     }
2311     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2312     return TRUE;
2313 }
2314
2315 int SeekGraphClick(ClickType click, int x, int y, int moving)
2316 {
2317     static int lastDown = 0, displayed = 0, lastSecond;
2318     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2319         if(click == Release || moving) return FALSE;
2320         nrOfSeekAds = 0;
2321         soughtPending = TRUE;
2322         SendToICS(ics_prefix);
2323         SendToICS("sought\n"); // should this be "sought all"?
2324     } else { // issue challenge based on clicked ad
2325         int dist = 10000; int i, closest = 0, second = 0;
2326         for(i=0; i<nrOfSeekAds; i++) {
2327             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2328             if(d < dist) { dist = d; closest = i; }
2329             second += (d - zList[i] < 120); // count in-range ads
2330             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2331         }
2332         if(dist < 120) {
2333             char buf[MSG_SIZ];
2334             second = (second > 1);
2335             if(displayed != closest || second != lastSecond) {
2336                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2337                 lastSecond = second; displayed = closest;
2338             }
2339             if(click == Press) {
2340                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2341                 lastDown = closest;
2342                 return TRUE;
2343             } // on press 'hit', only show info
2344             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2345             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2346             SendToICS(ics_prefix);
2347             SendToICS(buf);
2348             return TRUE; // let incoming board of started game pop down the graph
2349         } else if(click == Release) { // release 'miss' is ignored
2350             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2351             if(moving == 2) { // right up-click
2352                 nrOfSeekAds = 0; // refresh graph
2353                 soughtPending = TRUE;
2354                 SendToICS(ics_prefix);
2355                 SendToICS("sought\n"); // should this be "sought all"?
2356             }
2357             return TRUE;
2358         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2359         // press miss or release hit 'pop down' seek graph
2360         seekGraphUp = FALSE;
2361         DrawPosition(TRUE, NULL);
2362     }
2363     return TRUE;
2364 }
2365
2366 void
2367 read_from_ics(isr, closure, data, count, error)
2368      InputSourceRef isr;
2369      VOIDSTAR closure;
2370      char *data;
2371      int count;
2372      int error;
2373 {
2374 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2375 #define STARTED_NONE 0
2376 #define STARTED_MOVES 1
2377 #define STARTED_BOARD 2
2378 #define STARTED_OBSERVE 3
2379 #define STARTED_HOLDINGS 4
2380 #define STARTED_CHATTER 5
2381 #define STARTED_COMMENT 6
2382 #define STARTED_MOVES_NOHIDE 7
2383
2384     static int started = STARTED_NONE;
2385     static char parse[20000];
2386     static int parse_pos = 0;
2387     static char buf[BUF_SIZE + 1];
2388     static int firstTime = TRUE, intfSet = FALSE;
2389     static ColorClass prevColor = ColorNormal;
2390     static int savingComment = FALSE;
2391     static int cmatch = 0; // continuation sequence match
2392     char *bp;
2393     char str[MSG_SIZ];
2394     int i, oldi;
2395     int buf_len;
2396     int next_out;
2397     int tkind;
2398     int backup;    /* [DM] For zippy color lines */
2399     char *p;
2400     char talker[MSG_SIZ]; // [HGM] chat
2401     int channel;
2402
2403     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2404
2405     if (appData.debugMode) {
2406       if (!error) {
2407         fprintf(debugFP, "<ICS: ");
2408         show_bytes(debugFP, data, count);
2409         fprintf(debugFP, "\n");
2410       }
2411     }
2412
2413     if (appData.debugMode) { int f = forwardMostMove;
2414         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2415                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2416                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2417     }
2418     if (count > 0) {
2419         /* If last read ended with a partial line that we couldn't parse,
2420            prepend it to the new read and try again. */
2421         if (leftover_len > 0) {
2422             for (i=0; i<leftover_len; i++)
2423               buf[i] = buf[leftover_start + i];
2424         }
2425
2426     /* copy new characters into the buffer */
2427     bp = buf + leftover_len;
2428     buf_len=leftover_len;
2429     for (i=0; i<count; i++)
2430     {
2431         // ignore these
2432         if (data[i] == '\r')
2433             continue;
2434
2435         // join lines split by ICS?
2436         if (!appData.noJoin)
2437         {
2438             /*
2439                 Joining just consists of finding matches against the
2440                 continuation sequence, and discarding that sequence
2441                 if found instead of copying it.  So, until a match
2442                 fails, there's nothing to do since it might be the
2443                 complete sequence, and thus, something we don't want
2444                 copied.
2445             */
2446             if (data[i] == cont_seq[cmatch])
2447             {
2448                 cmatch++;
2449                 if (cmatch == strlen(cont_seq))
2450                 {
2451                     cmatch = 0; // complete match.  just reset the counter
2452
2453                     /*
2454                         it's possible for the ICS to not include the space
2455                         at the end of the last word, making our [correct]
2456                         join operation fuse two separate words.  the server
2457                         does this when the space occurs at the width setting.
2458                     */
2459                     if (!buf_len || buf[buf_len-1] != ' ')
2460                     {
2461                         *bp++ = ' ';
2462                         buf_len++;
2463                     }
2464                 }
2465                 continue;
2466             }
2467             else if (cmatch)
2468             {
2469                 /*
2470                     match failed, so we have to copy what matched before
2471                     falling through and copying this character.  In reality,
2472                     this will only ever be just the newline character, but
2473                     it doesn't hurt to be precise.
2474                 */
2475                 strncpy(bp, cont_seq, cmatch);
2476                 bp += cmatch;
2477                 buf_len += cmatch;
2478                 cmatch = 0;
2479             }
2480         }
2481
2482         // copy this char
2483         *bp++ = data[i];
2484         buf_len++;
2485     }
2486
2487         buf[buf_len] = NULLCHAR;
2488 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2489         next_out = 0;
2490         leftover_start = 0;
2491
2492         i = 0;
2493         while (i < buf_len) {
2494             /* Deal with part of the TELNET option negotiation
2495                protocol.  We refuse to do anything beyond the
2496                defaults, except that we allow the WILL ECHO option,
2497                which ICS uses to turn off password echoing when we are
2498                directly connected to it.  We reject this option
2499                if localLineEditing mode is on (always on in xboard)
2500                and we are talking to port 23, which might be a real
2501                telnet server that will try to keep WILL ECHO on permanently.
2502              */
2503             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2504                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2505                 unsigned char option;
2506                 oldi = i;
2507                 switch ((unsigned char) buf[++i]) {
2508                   case TN_WILL:
2509                     if (appData.debugMode)
2510                       fprintf(debugFP, "\n<WILL ");
2511                     switch (option = (unsigned char) buf[++i]) {
2512                       case TN_ECHO:
2513                         if (appData.debugMode)
2514                           fprintf(debugFP, "ECHO ");
2515                         /* Reply only if this is a change, according
2516                            to the protocol rules. */
2517                         if (remoteEchoOption) break;
2518                         if (appData.localLineEditing &&
2519                             atoi(appData.icsPort) == TN_PORT) {
2520                             TelnetRequest(TN_DONT, TN_ECHO);
2521                         } else {
2522                             EchoOff();
2523                             TelnetRequest(TN_DO, TN_ECHO);
2524                             remoteEchoOption = TRUE;
2525                         }
2526                         break;
2527                       default:
2528                         if (appData.debugMode)
2529                           fprintf(debugFP, "%d ", option);
2530                         /* Whatever this is, we don't want it. */
2531                         TelnetRequest(TN_DONT, option);
2532                         break;
2533                     }
2534                     break;
2535                   case TN_WONT:
2536                     if (appData.debugMode)
2537                       fprintf(debugFP, "\n<WONT ");
2538                     switch (option = (unsigned char) buf[++i]) {
2539                       case TN_ECHO:
2540                         if (appData.debugMode)
2541                           fprintf(debugFP, "ECHO ");
2542                         /* Reply only if this is a change, according
2543                            to the protocol rules. */
2544                         if (!remoteEchoOption) break;
2545                         EchoOn();
2546                         TelnetRequest(TN_DONT, TN_ECHO);
2547                         remoteEchoOption = FALSE;
2548                         break;
2549                       default:
2550                         if (appData.debugMode)
2551                           fprintf(debugFP, "%d ", (unsigned char) option);
2552                         /* Whatever this is, it must already be turned
2553                            off, because we never agree to turn on
2554                            anything non-default, so according to the
2555                            protocol rules, we don't reply. */
2556                         break;
2557                     }
2558                     break;
2559                   case TN_DO:
2560                     if (appData.debugMode)
2561                       fprintf(debugFP, "\n<DO ");
2562                     switch (option = (unsigned char) buf[++i]) {
2563                       default:
2564                         /* Whatever this is, we refuse to do it. */
2565                         if (appData.debugMode)
2566                           fprintf(debugFP, "%d ", option);
2567                         TelnetRequest(TN_WONT, option);
2568                         break;
2569                     }
2570                     break;
2571                   case TN_DONT:
2572                     if (appData.debugMode)
2573                       fprintf(debugFP, "\n<DONT ");
2574                     switch (option = (unsigned char) buf[++i]) {
2575                       default:
2576                         if (appData.debugMode)
2577                           fprintf(debugFP, "%d ", option);
2578                         /* Whatever this is, we are already not doing
2579                            it, because we never agree to do anything
2580                            non-default, so according to the protocol
2581                            rules, we don't reply. */
2582                         break;
2583                     }
2584                     break;
2585                   case TN_IAC:
2586                     if (appData.debugMode)
2587                       fprintf(debugFP, "\n<IAC ");
2588                     /* Doubled IAC; pass it through */
2589                     i--;
2590                     break;
2591                   default:
2592                     if (appData.debugMode)
2593                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2594                     /* Drop all other telnet commands on the floor */
2595                     break;
2596                 }
2597                 if (oldi > next_out)
2598                   SendToPlayer(&buf[next_out], oldi - next_out);
2599                 if (++i > next_out)
2600                   next_out = i;
2601                 continue;
2602             }
2603
2604             /* OK, this at least will *usually* work */
2605             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2606                 loggedOn = TRUE;
2607             }
2608
2609             if (loggedOn && !intfSet) {
2610                 if (ics_type == ICS_ICC) {
2611                   snprintf(str, MSG_SIZ,
2612                           "/set-quietly interface %s\n/set-quietly style 12\n",
2613                           programVersion);
2614                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2615                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2616                 } else if (ics_type == ICS_CHESSNET) {
2617                   snprintf(str, MSG_SIZ, "/style 12\n");
2618                 } else {
2619                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2620                   strcat(str, programVersion);
2621                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2622                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2623                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2624 #ifdef WIN32
2625                   strcat(str, "$iset nohighlight 1\n");
2626 #endif
2627                   strcat(str, "$iset lock 1\n$style 12\n");
2628                 }
2629                 SendToICS(str);
2630                 NotifyFrontendLogin();
2631                 intfSet = TRUE;
2632             }
2633
2634             if (started == STARTED_COMMENT) {
2635                 /* Accumulate characters in comment */
2636                 parse[parse_pos++] = buf[i];
2637                 if (buf[i] == '\n') {
2638                     parse[parse_pos] = NULLCHAR;
2639                     if(chattingPartner>=0) {
2640                         char mess[MSG_SIZ];
2641                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2642                         OutputChatMessage(chattingPartner, mess);
2643                         chattingPartner = -1;
2644                         next_out = i+1; // [HGM] suppress printing in ICS window
2645                     } else
2646                     if(!suppressKibitz) // [HGM] kibitz
2647                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2648                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2649                         int nrDigit = 0, nrAlph = 0, j;
2650                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2651                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2652                         parse[parse_pos] = NULLCHAR;
2653                         // try to be smart: if it does not look like search info, it should go to
2654                         // ICS interaction window after all, not to engine-output window.
2655                         for(j=0; j<parse_pos; j++) { // count letters and digits
2656                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2657                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2658                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2659                         }
2660                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2661                             int depth=0; float score;
2662                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2663                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2664                                 pvInfoList[forwardMostMove-1].depth = depth;
2665                                 pvInfoList[forwardMostMove-1].score = 100*score;
2666                             }
2667                             OutputKibitz(suppressKibitz, parse);
2668                         } else {
2669                             char tmp[MSG_SIZ];
2670                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2671                             SendToPlayer(tmp, strlen(tmp));
2672                         }
2673                         next_out = i+1; // [HGM] suppress printing in ICS window
2674                     }
2675                     started = STARTED_NONE;
2676                 } else {
2677                     /* Don't match patterns against characters in comment */
2678                     i++;
2679                     continue;
2680                 }
2681             }
2682             if (started == STARTED_CHATTER) {
2683                 if (buf[i] != '\n') {
2684                     /* Don't match patterns against characters in chatter */
2685                     i++;
2686                     continue;
2687                 }
2688                 started = STARTED_NONE;
2689                 if(suppressKibitz) next_out = i+1;
2690             }
2691
2692             /* Kludge to deal with rcmd protocol */
2693             if (firstTime && looking_at(buf, &i, "\001*")) {
2694                 DisplayFatalError(&buf[1], 0, 1);
2695                 continue;
2696             } else {
2697                 firstTime = FALSE;
2698             }
2699
2700             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2701                 ics_type = ICS_ICC;
2702                 ics_prefix = "/";
2703                 if (appData.debugMode)
2704                   fprintf(debugFP, "ics_type %d\n", ics_type);
2705                 continue;
2706             }
2707             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2708                 ics_type = ICS_FICS;
2709                 ics_prefix = "$";
2710                 if (appData.debugMode)
2711                   fprintf(debugFP, "ics_type %d\n", ics_type);
2712                 continue;
2713             }
2714             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2715                 ics_type = ICS_CHESSNET;
2716                 ics_prefix = "/";
2717                 if (appData.debugMode)
2718                   fprintf(debugFP, "ics_type %d\n", ics_type);
2719                 continue;
2720             }
2721
2722             if (!loggedOn &&
2723                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2724                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2725                  looking_at(buf, &i, "will be \"*\""))) {
2726               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2727               continue;
2728             }
2729
2730             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2731               char buf[MSG_SIZ];
2732               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2733               DisplayIcsInteractionTitle(buf);
2734               have_set_title = TRUE;
2735             }
2736
2737             /* skip finger notes */
2738             if (started == STARTED_NONE &&
2739                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2740                  (buf[i] == '1' && buf[i+1] == '0')) &&
2741                 buf[i+2] == ':' && buf[i+3] == ' ') {
2742               started = STARTED_CHATTER;
2743               i += 3;
2744               continue;
2745             }
2746
2747             oldi = i;
2748             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2749             if(appData.seekGraph) {
2750                 if(soughtPending && MatchSoughtLine(buf+i)) {
2751                     i = strstr(buf+i, "rated") - buf;
2752                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2753                     next_out = leftover_start = i;
2754                     started = STARTED_CHATTER;
2755                     suppressKibitz = TRUE;
2756                     continue;
2757                 }
2758                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2759                         && looking_at(buf, &i, "* ads displayed")) {
2760                     soughtPending = FALSE;
2761                     seekGraphUp = TRUE;
2762                     DrawSeekGraph();
2763                     continue;
2764                 }
2765                 if(appData.autoRefresh) {
2766                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2767                         int s = (ics_type == ICS_ICC); // ICC format differs
2768                         if(seekGraphUp)
2769                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2770                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2771                         looking_at(buf, &i, "*% "); // eat prompt
2772                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2773                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2774                         next_out = i; // suppress
2775                         continue;
2776                     }
2777                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2778                         char *p = star_match[0];
2779                         while(*p) {
2780                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2781                             while(*p && *p++ != ' '); // next
2782                         }
2783                         looking_at(buf, &i, "*% "); // eat prompt
2784                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2785                         next_out = i;
2786                         continue;
2787                     }
2788                 }
2789             }
2790
2791             /* skip formula vars */
2792             if (started == STARTED_NONE &&
2793                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2794               started = STARTED_CHATTER;
2795               i += 3;
2796               continue;
2797             }
2798
2799             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2800             if (appData.autoKibitz && started == STARTED_NONE &&
2801                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2802                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2803                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2804                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2805                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2806                         suppressKibitz = TRUE;
2807                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2808                         next_out = i;
2809                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2810                                 && (gameMode == IcsPlayingWhite)) ||
2811                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2812                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2813                             started = STARTED_CHATTER; // own kibitz we simply discard
2814                         else {
2815                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2816                             parse_pos = 0; parse[0] = NULLCHAR;
2817                             savingComment = TRUE;
2818                             suppressKibitz = gameMode != IcsObserving ? 2 :
2819                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2820                         }
2821                         continue;
2822                 } else
2823                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2824                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2825                          && atoi(star_match[0])) {
2826                     // suppress the acknowledgements of our own autoKibitz
2827                     char *p;
2828                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2829                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2830                     SendToPlayer(star_match[0], strlen(star_match[0]));
2831                     if(looking_at(buf, &i, "*% ")) // eat prompt
2832                         suppressKibitz = FALSE;
2833                     next_out = i;
2834                     continue;
2835                 }
2836             } // [HGM] kibitz: end of patch
2837
2838             // [HGM] chat: intercept tells by users for which we have an open chat window
2839             channel = -1;
2840             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2841                                            looking_at(buf, &i, "* whispers:") ||
2842                                            looking_at(buf, &i, "* kibitzes:") ||
2843                                            looking_at(buf, &i, "* shouts:") ||
2844                                            looking_at(buf, &i, "* c-shouts:") ||
2845                                            looking_at(buf, &i, "--> * ") ||
2846                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2847                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2848                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2849                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2850                 int p;
2851                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2852                 chattingPartner = -1;
2853
2854                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2855                 for(p=0; p<MAX_CHAT; p++) {
2856                     if(channel == atoi(chatPartner[p])) {
2857                     talker[0] = '['; strcat(talker, "] ");
2858                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2859                     chattingPartner = p; break;
2860                     }
2861                 } else
2862                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2863                 for(p=0; p<MAX_CHAT; p++) {
2864                     if(!strcmp("kibitzes", chatPartner[p])) {
2865                         talker[0] = '['; strcat(talker, "] ");
2866                         chattingPartner = p; break;
2867                     }
2868                 } else
2869                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2870                 for(p=0; p<MAX_CHAT; p++) {
2871                     if(!strcmp("whispers", chatPartner[p])) {
2872                         talker[0] = '['; strcat(talker, "] ");
2873                         chattingPartner = p; break;
2874                     }
2875                 } else
2876                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2877                   if(buf[i-8] == '-' && buf[i-3] == 't')
2878                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2879                     if(!strcmp("c-shouts", chatPartner[p])) {
2880                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2881                         chattingPartner = p; break;
2882                     }
2883                   }
2884                   if(chattingPartner < 0)
2885                   for(p=0; p<MAX_CHAT; p++) {
2886                     if(!strcmp("shouts", chatPartner[p])) {
2887                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2888                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2889                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2890                         chattingPartner = p; break;
2891                     }
2892                   }
2893                 }
2894                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2895                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2896                     talker[0] = 0; Colorize(ColorTell, FALSE);
2897                     chattingPartner = p; break;
2898                 }
2899                 if(chattingPartner<0) i = oldi; else {
2900                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2901                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2902                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2903                     started = STARTED_COMMENT;
2904                     parse_pos = 0; parse[0] = NULLCHAR;
2905                     savingComment = 3 + chattingPartner; // counts as TRUE
2906                     suppressKibitz = TRUE;
2907                     continue;
2908                 }
2909             } // [HGM] chat: end of patch
2910
2911             if (appData.zippyTalk || appData.zippyPlay) {
2912                 /* [DM] Backup address for color zippy lines */
2913                 backup = i;
2914 #if ZIPPY
2915                if (loggedOn == TRUE)
2916                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2917                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2918 #endif
2919             } // [DM] 'else { ' deleted
2920                 if (
2921                     /* Regular tells and says */
2922                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2923                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2924                     looking_at(buf, &i, "* says: ") ||
2925                     /* Don't color "message" or "messages" output */
2926                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2927                     looking_at(buf, &i, "*. * at *:*: ") ||
2928                     looking_at(buf, &i, "--* (*:*): ") ||
2929                     /* Message notifications (same color as tells) */
2930                     looking_at(buf, &i, "* has left a message ") ||
2931                     looking_at(buf, &i, "* just sent you a message:\n") ||
2932                     /* Whispers and kibitzes */
2933                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2934                     looking_at(buf, &i, "* kibitzes: ") ||
2935                     /* Channel tells */
2936                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2937
2938                   if (tkind == 1 && strchr(star_match[0], ':')) {
2939                       /* Avoid "tells you:" spoofs in channels */
2940                      tkind = 3;
2941                   }
2942                   if (star_match[0][0] == NULLCHAR ||
2943                       strchr(star_match[0], ' ') ||
2944                       (tkind == 3 && strchr(star_match[1], ' '))) {
2945                     /* Reject bogus matches */
2946                     i = oldi;
2947                   } else {
2948                     if (appData.colorize) {
2949                       if (oldi > next_out) {
2950                         SendToPlayer(&buf[next_out], oldi - next_out);
2951                         next_out = oldi;
2952                       }
2953                       switch (tkind) {
2954                       case 1:
2955                         Colorize(ColorTell, FALSE);
2956                         curColor = ColorTell;
2957                         break;
2958                       case 2:
2959                         Colorize(ColorKibitz, FALSE);
2960                         curColor = ColorKibitz;
2961                         break;
2962                       case 3:
2963                         p = strrchr(star_match[1], '(');
2964                         if (p == NULL) {
2965                           p = star_match[1];
2966                         } else {
2967                           p++;
2968                         }
2969                         if (atoi(p) == 1) {
2970                           Colorize(ColorChannel1, FALSE);
2971                           curColor = ColorChannel1;
2972                         } else {
2973                           Colorize(ColorChannel, FALSE);
2974                           curColor = ColorChannel;
2975                         }
2976                         break;
2977                       case 5:
2978                         curColor = ColorNormal;
2979                         break;
2980                       }
2981                     }
2982                     if (started == STARTED_NONE && appData.autoComment &&
2983                         (gameMode == IcsObserving ||
2984                          gameMode == IcsPlayingWhite ||
2985                          gameMode == IcsPlayingBlack)) {
2986                       parse_pos = i - oldi;
2987                       memcpy(parse, &buf[oldi], parse_pos);
2988                       parse[parse_pos] = NULLCHAR;
2989                       started = STARTED_COMMENT;
2990                       savingComment = TRUE;
2991                     } else {
2992                       started = STARTED_CHATTER;
2993                       savingComment = FALSE;
2994                     }
2995                     loggedOn = TRUE;
2996                     continue;
2997                   }
2998                 }
2999
3000                 if (looking_at(buf, &i, "* s-shouts: ") ||
3001                     looking_at(buf, &i, "* c-shouts: ")) {
3002                     if (appData.colorize) {
3003                         if (oldi > next_out) {
3004                             SendToPlayer(&buf[next_out], oldi - next_out);
3005                             next_out = oldi;
3006                         }
3007                         Colorize(ColorSShout, FALSE);
3008                         curColor = ColorSShout;
3009                     }
3010                     loggedOn = TRUE;
3011                     started = STARTED_CHATTER;
3012                     continue;
3013                 }
3014
3015                 if (looking_at(buf, &i, "--->")) {
3016                     loggedOn = TRUE;
3017                     continue;
3018                 }
3019
3020                 if (looking_at(buf, &i, "* shouts: ") ||
3021                     looking_at(buf, &i, "--> ")) {
3022                     if (appData.colorize) {
3023                         if (oldi > next_out) {
3024                             SendToPlayer(&buf[next_out], oldi - next_out);
3025                             next_out = oldi;
3026                         }
3027                         Colorize(ColorShout, FALSE);
3028                         curColor = ColorShout;
3029                     }
3030                     loggedOn = TRUE;
3031                     started = STARTED_CHATTER;
3032                     continue;
3033                 }
3034
3035                 if (looking_at( buf, &i, "Challenge:")) {
3036                     if (appData.colorize) {
3037                         if (oldi > next_out) {
3038                             SendToPlayer(&buf[next_out], oldi - next_out);
3039                             next_out = oldi;
3040                         }
3041                         Colorize(ColorChallenge, FALSE);
3042                         curColor = ColorChallenge;
3043                     }
3044                     loggedOn = TRUE;
3045                     continue;
3046                 }
3047
3048                 if (looking_at(buf, &i, "* offers you") ||
3049                     looking_at(buf, &i, "* offers to be") ||
3050                     looking_at(buf, &i, "* would like to") ||
3051                     looking_at(buf, &i, "* requests to") ||
3052                     looking_at(buf, &i, "Your opponent offers") ||
3053                     looking_at(buf, &i, "Your opponent requests")) {
3054
3055                     if (appData.colorize) {
3056                         if (oldi > next_out) {
3057                             SendToPlayer(&buf[next_out], oldi - next_out);
3058                             next_out = oldi;
3059                         }
3060                         Colorize(ColorRequest, FALSE);
3061                         curColor = ColorRequest;
3062                     }
3063                     continue;
3064                 }
3065
3066                 if (looking_at(buf, &i, "* (*) seeking")) {
3067                     if (appData.colorize) {
3068                         if (oldi > next_out) {
3069                             SendToPlayer(&buf[next_out], oldi - next_out);
3070                             next_out = oldi;
3071                         }
3072                         Colorize(ColorSeek, FALSE);
3073                         curColor = ColorSeek;
3074                     }
3075                     continue;
3076             }
3077
3078             if (looking_at(buf, &i, "\\   ")) {
3079                 if (prevColor != ColorNormal) {
3080                     if (oldi > next_out) {
3081                         SendToPlayer(&buf[next_out], oldi - next_out);
3082                         next_out = oldi;
3083                     }
3084                     Colorize(prevColor, TRUE);
3085                     curColor = prevColor;
3086                 }
3087                 if (savingComment) {
3088                     parse_pos = i - oldi;
3089                     memcpy(parse, &buf[oldi], parse_pos);
3090                     parse[parse_pos] = NULLCHAR;
3091                     started = STARTED_COMMENT;
3092                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3093                         chattingPartner = savingComment - 3; // kludge to remember the box
3094                 } else {
3095                     started = STARTED_CHATTER;
3096                 }
3097                 continue;
3098             }
3099
3100             if (looking_at(buf, &i, "Black Strength :") ||
3101                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3102                 looking_at(buf, &i, "<10>") ||
3103                 looking_at(buf, &i, "#@#")) {
3104                 /* Wrong board style */
3105                 loggedOn = TRUE;
3106                 SendToICS(ics_prefix);
3107                 SendToICS("set style 12\n");
3108                 SendToICS(ics_prefix);
3109                 SendToICS("refresh\n");
3110                 continue;
3111             }
3112
3113             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3114                 ICSInitScript();
3115                 have_sent_ICS_logon = 1;
3116                 continue;
3117             }
3118
3119             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3120                 (looking_at(buf, &i, "\n<12> ") ||
3121                  looking_at(buf, &i, "<12> "))) {
3122                 loggedOn = TRUE;
3123                 if (oldi > next_out) {
3124                     SendToPlayer(&buf[next_out], oldi - next_out);
3125                 }
3126                 next_out = i;
3127                 started = STARTED_BOARD;
3128                 parse_pos = 0;
3129                 continue;
3130             }
3131
3132             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3133                 looking_at(buf, &i, "<b1> ")) {
3134                 if (oldi > next_out) {
3135                     SendToPlayer(&buf[next_out], oldi - next_out);
3136                 }
3137                 next_out = i;
3138                 started = STARTED_HOLDINGS;
3139                 parse_pos = 0;
3140                 continue;
3141             }
3142
3143             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3144                 loggedOn = TRUE;
3145                 /* Header for a move list -- first line */
3146
3147                 switch (ics_getting_history) {
3148                   case H_FALSE:
3149                     switch (gameMode) {
3150                       case IcsIdle:
3151                       case BeginningOfGame:
3152                         /* User typed "moves" or "oldmoves" while we
3153                            were idle.  Pretend we asked for these
3154                            moves and soak them up so user can step
3155                            through them and/or save them.
3156                            */
3157                         Reset(FALSE, TRUE);
3158                         gameMode = IcsObserving;
3159                         ModeHighlight();
3160                         ics_gamenum = -1;
3161                         ics_getting_history = H_GOT_UNREQ_HEADER;
3162                         break;
3163                       case EditGame: /*?*/
3164                       case EditPosition: /*?*/
3165                         /* Should above feature work in these modes too? */
3166                         /* For now it doesn't */
3167                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3168                         break;
3169                       default:
3170                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3171                         break;
3172                     }
3173                     break;
3174                   case H_REQUESTED:
3175                     /* Is this the right one? */
3176                     if (gameInfo.white && gameInfo.black &&
3177                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3178                         strcmp(gameInfo.black, star_match[2]) == 0) {
3179                         /* All is well */
3180                         ics_getting_history = H_GOT_REQ_HEADER;
3181                     }
3182                     break;
3183                   case H_GOT_REQ_HEADER:
3184                   case H_GOT_UNREQ_HEADER:
3185                   case H_GOT_UNWANTED_HEADER:
3186                   case H_GETTING_MOVES:
3187                     /* Should not happen */
3188                     DisplayError(_("Error gathering move list: two headers"), 0);
3189                     ics_getting_history = H_FALSE;
3190                     break;
3191                 }
3192
3193                 /* Save player ratings into gameInfo if needed */
3194                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3195                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3196                     (gameInfo.whiteRating == -1 ||
3197                      gameInfo.blackRating == -1)) {
3198
3199                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3200                     gameInfo.blackRating = string_to_rating(star_match[3]);
3201                     if (appData.debugMode)
3202                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3203                               gameInfo.whiteRating, gameInfo.blackRating);
3204                 }
3205                 continue;
3206             }
3207
3208             if (looking_at(buf, &i,
3209               "* * match, initial time: * minute*, increment: * second")) {
3210                 /* Header for a move list -- second line */
3211                 /* Initial board will follow if this is a wild game */
3212                 if (gameInfo.event != NULL) free(gameInfo.event);
3213                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3214                 gameInfo.event = StrSave(str);
3215                 /* [HGM] we switched variant. Translate boards if needed. */
3216                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3217                 continue;
3218             }
3219
3220             if (looking_at(buf, &i, "Move  ")) {
3221                 /* Beginning of a move list */
3222                 switch (ics_getting_history) {
3223                   case H_FALSE:
3224                     /* Normally should not happen */
3225                     /* Maybe user hit reset while we were parsing */
3226                     break;
3227                   case H_REQUESTED:
3228                     /* Happens if we are ignoring a move list that is not
3229                      * the one we just requested.  Common if the user
3230                      * tries to observe two games without turning off
3231                      * getMoveList */
3232                     break;
3233                   case H_GETTING_MOVES:
3234                     /* Should not happen */
3235                     DisplayError(_("Error gathering move list: nested"), 0);
3236                     ics_getting_history = H_FALSE;
3237                     break;
3238                   case H_GOT_REQ_HEADER:
3239                     ics_getting_history = H_GETTING_MOVES;
3240                     started = STARTED_MOVES;
3241                     parse_pos = 0;
3242                     if (oldi > next_out) {
3243                         SendToPlayer(&buf[next_out], oldi - next_out);
3244                     }
3245                     break;
3246                   case H_GOT_UNREQ_HEADER:
3247                     ics_getting_history = H_GETTING_MOVES;
3248                     started = STARTED_MOVES_NOHIDE;
3249                     parse_pos = 0;
3250                     break;
3251                   case H_GOT_UNWANTED_HEADER:
3252                     ics_getting_history = H_FALSE;
3253                     break;
3254                 }
3255                 continue;
3256             }
3257
3258             if (looking_at(buf, &i, "% ") ||
3259                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3260                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3261                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3262                     soughtPending = FALSE;
3263                     seekGraphUp = TRUE;
3264                     DrawSeekGraph();
3265                 }
3266                 if(suppressKibitz) next_out = i;
3267                 savingComment = FALSE;
3268                 suppressKibitz = 0;
3269                 switch (started) {
3270                   case STARTED_MOVES:
3271                   case STARTED_MOVES_NOHIDE:
3272                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3273                     parse[parse_pos + i - oldi] = NULLCHAR;
3274                     ParseGameHistory(parse);
3275 #if ZIPPY
3276                     if (appData.zippyPlay && first.initDone) {
3277                         FeedMovesToProgram(&first, forwardMostMove);
3278                         if (gameMode == IcsPlayingWhite) {
3279                             if (WhiteOnMove(forwardMostMove)) {
3280                                 if (first.sendTime) {
3281                                   if (first.useColors) {
3282                                     SendToProgram("black\n", &first);
3283                                   }
3284                                   SendTimeRemaining(&first, TRUE);
3285                                 }
3286                                 if (first.useColors) {
3287                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3288                                 }
3289                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3290                                 first.maybeThinking = TRUE;
3291                             } else {
3292                                 if (first.usePlayother) {
3293                                   if (first.sendTime) {
3294                                     SendTimeRemaining(&first, TRUE);
3295                                   }
3296                                   SendToProgram("playother\n", &first);
3297                                   firstMove = FALSE;
3298                                 } else {
3299                                   firstMove = TRUE;
3300                                 }
3301                             }
3302                         } else if (gameMode == IcsPlayingBlack) {
3303                             if (!WhiteOnMove(forwardMostMove)) {
3304                                 if (first.sendTime) {
3305                                   if (first.useColors) {
3306                                     SendToProgram("white\n", &first);
3307                                   }
3308                                   SendTimeRemaining(&first, FALSE);
3309                                 }
3310                                 if (first.useColors) {
3311                                   SendToProgram("black\n", &first);
3312                                 }
3313                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3314                                 first.maybeThinking = TRUE;
3315                             } else {
3316                                 if (first.usePlayother) {
3317                                   if (first.sendTime) {
3318                                     SendTimeRemaining(&first, FALSE);
3319                                   }
3320                                   SendToProgram("playother\n", &first);
3321                                   firstMove = FALSE;
3322                                 } else {
3323                                   firstMove = TRUE;
3324                                 }
3325                             }
3326                         }
3327                     }
3328 #endif
3329                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3330                         /* Moves came from oldmoves or moves command
3331                            while we weren't doing anything else.
3332                            */
3333                         currentMove = forwardMostMove;
3334                         ClearHighlights();/*!!could figure this out*/
3335                         flipView = appData.flipView;
3336                         DrawPosition(TRUE, boards[currentMove]);
3337                         DisplayBothClocks();
3338                         snprintf(str, MSG_SIZ, "%s vs. %s",
3339                                 gameInfo.white, gameInfo.black);
3340                         DisplayTitle(str);
3341                         gameMode = IcsIdle;
3342                     } else {
3343                         /* Moves were history of an active game */
3344                         if (gameInfo.resultDetails != NULL) {
3345                             free(gameInfo.resultDetails);
3346                             gameInfo.resultDetails = NULL;
3347                         }
3348                     }
3349                     HistorySet(parseList, backwardMostMove,
3350                                forwardMostMove, currentMove-1);
3351                     DisplayMove(currentMove - 1);
3352                     if (started == STARTED_MOVES) next_out = i;
3353                     started = STARTED_NONE;
3354                     ics_getting_history = H_FALSE;
3355                     break;
3356
3357                   case STARTED_OBSERVE:
3358                     started = STARTED_NONE;
3359                     SendToICS(ics_prefix);
3360                     SendToICS("refresh\n");
3361                     break;
3362
3363                   default:
3364                     break;
3365                 }
3366                 if(bookHit) { // [HGM] book: simulate book reply
3367                     static char bookMove[MSG_SIZ]; // a bit generous?
3368
3369                     programStats.nodes = programStats.depth = programStats.time =
3370                     programStats.score = programStats.got_only_move = 0;
3371                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3372
3373                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3374                     strcat(bookMove, bookHit);
3375                     HandleMachineMove(bookMove, &first);
3376                 }
3377                 continue;
3378             }
3379
3380             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3381                  started == STARTED_HOLDINGS ||
3382                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3383                 /* Accumulate characters in move list or board */
3384                 parse[parse_pos++] = buf[i];
3385             }
3386
3387             /* Start of game messages.  Mostly we detect start of game
3388                when the first board image arrives.  On some versions
3389                of the ICS, though, we need to do a "refresh" after starting
3390                to observe in order to get the current board right away. */
3391             if (looking_at(buf, &i, "Adding game * to observation list")) {
3392                 started = STARTED_OBSERVE;
3393                 continue;
3394             }
3395
3396             /* Handle auto-observe */
3397             if (appData.autoObserve &&
3398                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3399                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3400                 char *player;
3401                 /* Choose the player that was highlighted, if any. */
3402                 if (star_match[0][0] == '\033' ||
3403                     star_match[1][0] != '\033') {
3404                     player = star_match[0];
3405                 } else {
3406                     player = star_match[2];
3407                 }
3408                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3409                         ics_prefix, StripHighlightAndTitle(player));
3410                 SendToICS(str);
3411
3412                 /* Save ratings from notify string */
3413                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3414                 player1Rating = string_to_rating(star_match[1]);
3415                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3416                 player2Rating = string_to_rating(star_match[3]);
3417
3418                 if (appData.debugMode)
3419                   fprintf(debugFP,
3420                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3421                           player1Name, player1Rating,
3422                           player2Name, player2Rating);
3423
3424                 continue;
3425             }
3426
3427             /* Deal with automatic examine mode after a game,
3428                and with IcsObserving -> IcsExamining transition */
3429             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3430                 looking_at(buf, &i, "has made you an examiner of game *")) {
3431
3432                 int gamenum = atoi(star_match[0]);
3433                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3434                     gamenum == ics_gamenum) {
3435                     /* We were already playing or observing this game;
3436                        no need to refetch history */
3437                     gameMode = IcsExamining;
3438                     if (pausing) {
3439                         pauseExamForwardMostMove = forwardMostMove;
3440                     } else if (currentMove < forwardMostMove) {
3441                         ForwardInner(forwardMostMove);
3442                     }
3443                 } else {
3444                     /* I don't think this case really can happen */
3445                     SendToICS(ics_prefix);
3446                     SendToICS("refresh\n");
3447                 }
3448                 continue;
3449             }
3450
3451             /* Error messages */
3452 //          if (ics_user_moved) {
3453             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3454                 if (looking_at(buf, &i, "Illegal move") ||
3455                     looking_at(buf, &i, "Not a legal move") ||
3456                     looking_at(buf, &i, "Your king is in check") ||
3457                     looking_at(buf, &i, "It isn't your turn") ||
3458                     looking_at(buf, &i, "It is not your move")) {
3459                     /* Illegal move */
3460                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3461                         currentMove = forwardMostMove-1;
3462                         DisplayMove(currentMove - 1); /* before DMError */
3463                         DrawPosition(FALSE, boards[currentMove]);
3464                         SwitchClocks(forwardMostMove-1); // [HGM] race
3465                         DisplayBothClocks();
3466                     }
3467                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3468                     ics_user_moved = 0;
3469                     continue;
3470                 }
3471             }
3472
3473             if (looking_at(buf, &i, "still have time") ||
3474                 looking_at(buf, &i, "not out of time") ||
3475                 looking_at(buf, &i, "either player is out of time") ||
3476                 looking_at(buf, &i, "has timeseal; checking")) {
3477                 /* We must have called his flag a little too soon */
3478                 whiteFlag = blackFlag = FALSE;
3479                 continue;
3480             }
3481
3482             if (looking_at(buf, &i, "added * seconds to") ||
3483                 looking_at(buf, &i, "seconds were added to")) {
3484                 /* Update the clocks */
3485                 SendToICS(ics_prefix);
3486                 SendToICS("refresh\n");
3487                 continue;
3488             }
3489
3490             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3491                 ics_clock_paused = TRUE;
3492                 StopClocks();
3493                 continue;
3494             }
3495
3496             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3497                 ics_clock_paused = FALSE;
3498                 StartClocks();
3499                 continue;
3500             }
3501
3502             /* Grab player ratings from the Creating: message.
3503                Note we have to check for the special case when
3504                the ICS inserts things like [white] or [black]. */
3505             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3506                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3507                 /* star_matches:
3508                    0    player 1 name (not necessarily white)
3509                    1    player 1 rating
3510                    2    empty, white, or black (IGNORED)
3511                    3    player 2 name (not necessarily black)
3512                    4    player 2 rating
3513
3514                    The names/ratings are sorted out when the game
3515                    actually starts (below).
3516                 */
3517                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3518                 player1Rating = string_to_rating(star_match[1]);
3519                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3520                 player2Rating = string_to_rating(star_match[4]);
3521
3522                 if (appData.debugMode)
3523                   fprintf(debugFP,
3524                           "Ratings from 'Creating:' %s %d, %s %d\n",
3525                           player1Name, player1Rating,
3526                           player2Name, player2Rating);
3527
3528                 continue;
3529             }
3530
3531             /* Improved generic start/end-of-game messages */
3532             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3533                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3534                 /* If tkind == 0: */
3535                 /* star_match[0] is the game number */
3536                 /*           [1] is the white player's name */
3537                 /*           [2] is the black player's name */
3538                 /* For end-of-game: */
3539                 /*           [3] is the reason for the game end */
3540                 /*           [4] is a PGN end game-token, preceded by " " */
3541                 /* For start-of-game: */
3542                 /*           [3] begins with "Creating" or "Continuing" */
3543                 /*           [4] is " *" or empty (don't care). */
3544                 int gamenum = atoi(star_match[0]);
3545                 char *whitename, *blackname, *why, *endtoken;
3546                 ChessMove endtype = EndOfFile;
3547
3548                 if (tkind == 0) {
3549                   whitename = star_match[1];
3550                   blackname = star_match[2];
3551                   why = star_match[3];
3552                   endtoken = star_match[4];
3553                 } else {
3554                   whitename = star_match[1];
3555                   blackname = star_match[3];
3556                   why = star_match[5];
3557                   endtoken = star_match[6];
3558                 }
3559
3560                 /* Game start messages */
3561                 if (strncmp(why, "Creating ", 9) == 0 ||
3562                     strncmp(why, "Continuing ", 11) == 0) {
3563                     gs_gamenum = gamenum;
3564                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3565                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3566 #if ZIPPY
3567                     if (appData.zippyPlay) {
3568                         ZippyGameStart(whitename, blackname);
3569                     }
3570 #endif /*ZIPPY*/
3571                     partnerBoardValid = FALSE; // [HGM] bughouse
3572                     continue;
3573                 }
3574
3575                 /* Game end messages */
3576                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3577                     ics_gamenum != gamenum) {
3578                     continue;
3579                 }
3580                 while (endtoken[0] == ' ') endtoken++;
3581                 switch (endtoken[0]) {
3582                   case '*':
3583                   default:
3584                     endtype = GameUnfinished;
3585                     break;
3586                   case '0':
3587                     endtype = BlackWins;
3588                     break;
3589                   case '1':
3590                     if (endtoken[1] == '/')
3591                       endtype = GameIsDrawn;
3592                     else
3593                       endtype = WhiteWins;
3594                     break;
3595                 }
3596                 GameEnds(endtype, why, GE_ICS);
3597 #if ZIPPY
3598                 if (appData.zippyPlay && first.initDone) {
3599                     ZippyGameEnd(endtype, why);
3600                     if (first.pr == NULL) {
3601                       /* Start the next process early so that we'll
3602                          be ready for the next challenge */
3603                       StartChessProgram(&first);
3604                     }
3605                     /* Send "new" early, in case this command takes
3606                        a long time to finish, so that we'll be ready
3607                        for the next challenge. */
3608                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3609                     Reset(TRUE, TRUE);
3610                 }
3611 #endif /*ZIPPY*/
3612                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3613                 continue;
3614             }
3615
3616             if (looking_at(buf, &i, "Removing game * from observation") ||
3617                 looking_at(buf, &i, "no longer observing game *") ||
3618                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3619                 if (gameMode == IcsObserving &&
3620                     atoi(star_match[0]) == ics_gamenum)
3621                   {
3622                       /* icsEngineAnalyze */
3623                       if (appData.icsEngineAnalyze) {
3624                             ExitAnalyzeMode();
3625                             ModeHighlight();
3626                       }
3627                       StopClocks();
3628                       gameMode = IcsIdle;
3629                       ics_gamenum = -1;
3630                       ics_user_moved = FALSE;
3631                   }
3632                 continue;
3633             }
3634
3635             if (looking_at(buf, &i, "no longer examining game *")) {
3636                 if (gameMode == IcsExamining &&
3637                     atoi(star_match[0]) == ics_gamenum)
3638                   {
3639                       gameMode = IcsIdle;
3640                       ics_gamenum = -1;
3641                       ics_user_moved = FALSE;
3642                   }
3643                 continue;
3644             }
3645
3646             /* Advance leftover_start past any newlines we find,
3647                so only partial lines can get reparsed */
3648             if (looking_at(buf, &i, "\n")) {
3649                 prevColor = curColor;
3650                 if (curColor != ColorNormal) {
3651                     if (oldi > next_out) {
3652                         SendToPlayer(&buf[next_out], oldi - next_out);
3653                         next_out = oldi;
3654                     }
3655                     Colorize(ColorNormal, FALSE);
3656                     curColor = ColorNormal;
3657                 }
3658                 if (started == STARTED_BOARD) {
3659                     started = STARTED_NONE;
3660                     parse[parse_pos] = NULLCHAR;
3661                     ParseBoard12(parse);
3662                     ics_user_moved = 0;
3663
3664                     /* Send premove here */
3665                     if (appData.premove) {
3666                       char str[MSG_SIZ];
3667                       if (currentMove == 0 &&
3668                           gameMode == IcsPlayingWhite &&
3669                           appData.premoveWhite) {
3670                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3671                         if (appData.debugMode)
3672                           fprintf(debugFP, "Sending premove:\n");
3673                         SendToICS(str);
3674                       } else if (currentMove == 1 &&
3675                                  gameMode == IcsPlayingBlack &&
3676                                  appData.premoveBlack) {
3677                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3678                         if (appData.debugMode)
3679                           fprintf(debugFP, "Sending premove:\n");
3680                         SendToICS(str);
3681                       } else if (gotPremove) {
3682                         gotPremove = 0;
3683                         ClearPremoveHighlights();
3684                         if (appData.debugMode)
3685                           fprintf(debugFP, "Sending premove:\n");
3686                           UserMoveEvent(premoveFromX, premoveFromY,
3687                                         premoveToX, premoveToY,
3688                                         premovePromoChar);
3689                       }
3690                     }
3691
3692                     /* Usually suppress following prompt */
3693                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3694                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3695                         if (looking_at(buf, &i, "*% ")) {
3696                             savingComment = FALSE;
3697                             suppressKibitz = 0;
3698                         }
3699                     }
3700                     next_out = i;
3701                 } else if (started == STARTED_HOLDINGS) {
3702                     int gamenum;
3703                     char new_piece[MSG_SIZ];
3704                     started = STARTED_NONE;
3705                     parse[parse_pos] = NULLCHAR;
3706                     if (appData.debugMode)
3707                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3708                                                         parse, currentMove);
3709                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3710                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3711                         if (gameInfo.variant == VariantNormal) {
3712                           /* [HGM] We seem to switch variant during a game!
3713                            * Presumably no holdings were displayed, so we have
3714                            * to move the position two files to the right to
3715                            * create room for them!
3716                            */
3717                           VariantClass newVariant;
3718                           switch(gameInfo.boardWidth) { // base guess on board width
3719                                 case 9:  newVariant = VariantShogi; break;
3720                                 case 10: newVariant = VariantGreat; break;
3721                                 default: newVariant = VariantCrazyhouse; break;
3722                           }
3723                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3724                           /* Get a move list just to see the header, which
3725                              will tell us whether this is really bug or zh */
3726                           if (ics_getting_history == H_FALSE) {
3727                             ics_getting_history = H_REQUESTED;
3728                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3729                             SendToICS(str);
3730                           }
3731                         }
3732                         new_piece[0] = NULLCHAR;
3733                         sscanf(parse, "game %d white [%s black [%s <- %s",
3734                                &gamenum, white_holding, black_holding,
3735                                new_piece);
3736                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3737                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3738                         /* [HGM] copy holdings to board holdings area */
3739                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3740                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3741                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3742 #if ZIPPY
3743                         if (appData.zippyPlay && first.initDone) {
3744                             ZippyHoldings(white_holding, black_holding,
3745                                           new_piece);
3746                         }
3747 #endif /*ZIPPY*/
3748                         if (tinyLayout || smallLayout) {
3749                             char wh[16], bh[16];
3750                             PackHolding(wh, white_holding);
3751                             PackHolding(bh, black_holding);
3752                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3753                                     gameInfo.white, gameInfo.black);
3754                         } else {
3755                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3756                                     gameInfo.white, white_holding,
3757                                     gameInfo.black, black_holding);
3758                         }
3759                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3760                         DrawPosition(FALSE, boards[currentMove]);
3761                         DisplayTitle(str);
3762                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3763                         sscanf(parse, "game %d white [%s black [%s <- %s",
3764                                &gamenum, white_holding, black_holding,
3765                                new_piece);
3766                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3767                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3768                         /* [HGM] copy holdings to partner-board holdings area */
3769                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3770                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3771                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3772                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3773                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3774                       }
3775                     }
3776                     /* Suppress following prompt */
3777                     if (looking_at(buf, &i, "*% ")) {
3778                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3779                         savingComment = FALSE;
3780                         suppressKibitz = 0;
3781                     }
3782                     next_out = i;
3783                 }
3784                 continue;
3785             }
3786
3787             i++;                /* skip unparsed character and loop back */
3788         }
3789
3790         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3791 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3792 //          SendToPlayer(&buf[next_out], i - next_out);
3793             started != STARTED_HOLDINGS && leftover_start > next_out) {
3794             SendToPlayer(&buf[next_out], leftover_start - next_out);
3795             next_out = i;
3796         }
3797
3798         leftover_len = buf_len - leftover_start;
3799         /* if buffer ends with something we couldn't parse,
3800            reparse it after appending the next read */
3801
3802     } else if (count == 0) {
3803         RemoveInputSource(isr);
3804         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3805     } else {
3806         DisplayFatalError(_("Error reading from ICS"), error, 1);
3807     }
3808 }
3809
3810
3811 /* Board style 12 looks like this:
3812
3813    <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
3814
3815  * The "<12> " is stripped before it gets to this routine.  The two
3816  * trailing 0's (flip state and clock ticking) are later addition, and
3817  * some chess servers may not have them, or may have only the first.
3818  * Additional trailing fields may be added in the future.
3819  */
3820
3821 #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"
3822
3823 #define RELATION_OBSERVING_PLAYED    0
3824 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3825 #define RELATION_PLAYING_MYMOVE      1
3826 #define RELATION_PLAYING_NOTMYMOVE  -1
3827 #define RELATION_EXAMINING           2
3828 #define RELATION_ISOLATED_BOARD     -3
3829 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3830
3831 void
3832 ParseBoard12(string)
3833      char *string;
3834 {
3835     GameMode newGameMode;
3836     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3837     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3838     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3839     char to_play, board_chars[200];
3840     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3841     char black[32], white[32];
3842     Board board;
3843     int prevMove = currentMove;
3844     int ticking = 2;
3845     ChessMove moveType;
3846     int fromX, fromY, toX, toY;
3847     char promoChar;
3848     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3849     char *bookHit = NULL; // [HGM] book
3850     Boolean weird = FALSE, reqFlag = FALSE;
3851
3852     fromX = fromY = toX = toY = -1;
3853
3854     newGame = FALSE;
3855
3856     if (appData.debugMode)
3857       fprintf(debugFP, _("Parsing board: %s\n"), string);
3858
3859     move_str[0] = NULLCHAR;
3860     elapsed_time[0] = NULLCHAR;
3861     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3862         int  i = 0, j;
3863         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3864             if(string[i] == ' ') { ranks++; files = 0; }
3865             else files++;
3866             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3867             i++;
3868         }
3869         for(j = 0; j <i; j++) board_chars[j] = string[j];
3870         board_chars[i] = '\0';
3871         string += i + 1;
3872     }
3873     n = sscanf(string, PATTERN, &to_play, &double_push,
3874                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3875                &gamenum, white, black, &relation, &basetime, &increment,
3876                &white_stren, &black_stren, &white_time, &black_time,
3877                &moveNum, str, elapsed_time, move_str, &ics_flip,
3878                &ticking);
3879
3880     if (n < 21) {
3881         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3882         DisplayError(str, 0);
3883         return;
3884     }
3885
3886     /* Convert the move number to internal form */
3887     moveNum = (moveNum - 1) * 2;
3888     if (to_play == 'B') moveNum++;
3889     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3890       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3891                         0, 1);
3892       return;
3893     }
3894
3895     switch (relation) {
3896       case RELATION_OBSERVING_PLAYED:
3897       case RELATION_OBSERVING_STATIC:
3898         if (gamenum == -1) {
3899             /* Old ICC buglet */
3900             relation = RELATION_OBSERVING_STATIC;
3901         }
3902         newGameMode = IcsObserving;
3903         break;
3904       case RELATION_PLAYING_MYMOVE:
3905       case RELATION_PLAYING_NOTMYMOVE:
3906         newGameMode =
3907           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3908             IcsPlayingWhite : IcsPlayingBlack;
3909         break;
3910       case RELATION_EXAMINING:
3911         newGameMode = IcsExamining;
3912         break;
3913       case RELATION_ISOLATED_BOARD:
3914       default:
3915         /* Just display this board.  If user was doing something else,
3916            we will forget about it until the next board comes. */
3917         newGameMode = IcsIdle;
3918         break;
3919       case RELATION_STARTING_POSITION:
3920         newGameMode = gameMode;
3921         break;
3922     }
3923
3924     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3925          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3926       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3927       char *toSqr;
3928       for (k = 0; k < ranks; k++) {
3929         for (j = 0; j < files; j++)
3930           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3931         if(gameInfo.holdingsWidth > 1) {
3932              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3933              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3934         }
3935       }
3936       CopyBoard(partnerBoard, board);
3937       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3938         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3939         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3940       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3941       if(toSqr = strchr(str, '-')) {
3942         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3943         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3944       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3945       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3946       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3947       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3948       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3949       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3950                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3951       DisplayMessage(partnerStatus, "");
3952         partnerBoardValid = TRUE;
3953       return;
3954     }
3955
3956     /* Modify behavior for initial board display on move listing
3957        of wild games.
3958        */
3959     switch (ics_getting_history) {
3960       case H_FALSE:
3961       case H_REQUESTED:
3962         break;
3963       case H_GOT_REQ_HEADER:
3964       case H_GOT_UNREQ_HEADER:
3965         /* This is the initial position of the current game */
3966         gamenum = ics_gamenum;
3967         moveNum = 0;            /* old ICS bug workaround */
3968         if (to_play == 'B') {
3969           startedFromSetupPosition = TRUE;
3970           blackPlaysFirst = TRUE;
3971           moveNum = 1;
3972           if (forwardMostMove == 0) forwardMostMove = 1;
3973           if (backwardMostMove == 0) backwardMostMove = 1;
3974           if (currentMove == 0) currentMove = 1;
3975         }
3976         newGameMode = gameMode;
3977         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3978         break;
3979       case H_GOT_UNWANTED_HEADER:
3980         /* This is an initial board that we don't want */
3981         return;
3982       case H_GETTING_MOVES:
3983         /* Should not happen */
3984         DisplayError(_("Error gathering move list: extra board"), 0);
3985         ics_getting_history = H_FALSE;
3986         return;
3987     }
3988
3989    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3990                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
3991      /* [HGM] We seem to have switched variant unexpectedly
3992       * Try to guess new variant from board size
3993       */
3994           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3995           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3996           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3997           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3998           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3999           if(!weird) newVariant = VariantNormal;
4000           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4001           /* Get a move list just to see the header, which
4002              will tell us whether this is really bug or zh */
4003           if (ics_getting_history == H_FALSE) {
4004             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4005             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4006             SendToICS(str);
4007           }
4008     }
4009
4010     /* Take action if this is the first board of a new game, or of a
4011        different game than is currently being displayed.  */
4012     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4013         relation == RELATION_ISOLATED_BOARD) {
4014
4015         /* Forget the old game and get the history (if any) of the new one */
4016         if (gameMode != BeginningOfGame) {
4017           Reset(TRUE, TRUE);
4018         }
4019         newGame = TRUE;
4020         if (appData.autoRaiseBoard) BoardToTop();
4021         prevMove = -3;
4022         if (gamenum == -1) {
4023             newGameMode = IcsIdle;
4024         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4025                    appData.getMoveList && !reqFlag) {
4026             /* Need to get game history */
4027             ics_getting_history = H_REQUESTED;
4028             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4029             SendToICS(str);
4030         }
4031
4032         /* Initially flip the board to have black on the bottom if playing
4033            black or if the ICS flip flag is set, but let the user change
4034            it with the Flip View button. */
4035         flipView = appData.autoFlipView ?
4036           (newGameMode == IcsPlayingBlack) || ics_flip :
4037           appData.flipView;
4038
4039         /* Done with values from previous mode; copy in new ones */
4040         gameMode = newGameMode;
4041         ModeHighlight();
4042         ics_gamenum = gamenum;
4043         if (gamenum == gs_gamenum) {
4044             int klen = strlen(gs_kind);
4045             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4046             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4047             gameInfo.event = StrSave(str);
4048         } else {
4049             gameInfo.event = StrSave("ICS game");
4050         }
4051         gameInfo.site = StrSave(appData.icsHost);
4052         gameInfo.date = PGNDate();
4053         gameInfo.round = StrSave("-");
4054         gameInfo.white = StrSave(white);
4055         gameInfo.black = StrSave(black);
4056         timeControl = basetime * 60 * 1000;
4057         timeControl_2 = 0;
4058         timeIncrement = increment * 1000;
4059         movesPerSession = 0;
4060         gameInfo.timeControl = TimeControlTagValue();
4061         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4062   if (appData.debugMode) {
4063     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4064     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4065     setbuf(debugFP, NULL);
4066   }
4067
4068         gameInfo.outOfBook = NULL;
4069
4070         /* Do we have the ratings? */
4071         if (strcmp(player1Name, white) == 0 &&
4072             strcmp(player2Name, black) == 0) {
4073             if (appData.debugMode)
4074               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4075                       player1Rating, player2Rating);
4076             gameInfo.whiteRating = player1Rating;
4077             gameInfo.blackRating = player2Rating;
4078         } else if (strcmp(player2Name, white) == 0 &&
4079                    strcmp(player1Name, black) == 0) {
4080             if (appData.debugMode)
4081               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4082                       player2Rating, player1Rating);
4083             gameInfo.whiteRating = player2Rating;
4084             gameInfo.blackRating = player1Rating;
4085         }
4086         player1Name[0] = player2Name[0] = NULLCHAR;
4087
4088         /* Silence shouts if requested */
4089         if (appData.quietPlay &&
4090             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4091             SendToICS(ics_prefix);
4092             SendToICS("set shout 0\n");
4093         }
4094     }
4095
4096     /* Deal with midgame name changes */
4097     if (!newGame) {
4098         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4099             if (gameInfo.white) free(gameInfo.white);
4100             gameInfo.white = StrSave(white);
4101         }
4102         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4103             if (gameInfo.black) free(gameInfo.black);
4104             gameInfo.black = StrSave(black);
4105         }
4106     }
4107
4108     /* Throw away game result if anything actually changes in examine mode */
4109     if (gameMode == IcsExamining && !newGame) {
4110         gameInfo.result = GameUnfinished;
4111         if (gameInfo.resultDetails != NULL) {
4112             free(gameInfo.resultDetails);
4113             gameInfo.resultDetails = NULL;
4114         }
4115     }
4116
4117     /* In pausing && IcsExamining mode, we ignore boards coming
4118        in if they are in a different variation than we are. */
4119     if (pauseExamInvalid) return;
4120     if (pausing && gameMode == IcsExamining) {
4121         if (moveNum <= pauseExamForwardMostMove) {
4122             pauseExamInvalid = TRUE;
4123             forwardMostMove = pauseExamForwardMostMove;
4124             return;
4125         }
4126     }
4127
4128   if (appData.debugMode) {
4129     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4130   }
4131     /* Parse the board */
4132     for (k = 0; k < ranks; k++) {
4133       for (j = 0; j < files; j++)
4134         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4135       if(gameInfo.holdingsWidth > 1) {
4136            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4137            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4138       }
4139     }
4140     CopyBoard(boards[moveNum], board);
4141     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4142     if (moveNum == 0) {
4143         startedFromSetupPosition =
4144           !CompareBoards(board, initialPosition);
4145         if(startedFromSetupPosition)
4146             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4147     }
4148
4149     /* [HGM] Set castling rights. Take the outermost Rooks,
4150        to make it also work for FRC opening positions. Note that board12
4151        is really defective for later FRC positions, as it has no way to
4152        indicate which Rook can castle if they are on the same side of King.
4153        For the initial position we grant rights to the outermost Rooks,
4154        and remember thos rights, and we then copy them on positions
4155        later in an FRC game. This means WB might not recognize castlings with
4156        Rooks that have moved back to their original position as illegal,
4157        but in ICS mode that is not its job anyway.
4158     */
4159     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4160     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4161
4162         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4163             if(board[0][i] == WhiteRook) j = i;
4164         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4165         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4166             if(board[0][i] == WhiteRook) j = i;
4167         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4168         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4169             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4170         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4171         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4172             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4173         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4174
4175         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4176         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4177             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4178         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4179             if(board[BOARD_HEIGHT-1][k] == bKing)
4180                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4181         if(gameInfo.variant == VariantTwoKings) {
4182             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4183             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4184             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4185         }
4186     } else { int r;
4187         r = boards[moveNum][CASTLING][0] = initialRights[0];
4188         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4189         r = boards[moveNum][CASTLING][1] = initialRights[1];
4190         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4191         r = boards[moveNum][CASTLING][3] = initialRights[3];
4192         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4193         r = boards[moveNum][CASTLING][4] = initialRights[4];
4194         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4195         /* wildcastle kludge: always assume King has rights */
4196         r = boards[moveNum][CASTLING][2] = initialRights[2];
4197         r = boards[moveNum][CASTLING][5] = initialRights[5];
4198     }
4199     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4200     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4201
4202
4203     if (ics_getting_history == H_GOT_REQ_HEADER ||
4204         ics_getting_history == H_GOT_UNREQ_HEADER) {
4205         /* This was an initial position from a move list, not
4206            the current position */
4207         return;
4208     }
4209
4210     /* Update currentMove and known move number limits */
4211     newMove = newGame || moveNum > forwardMostMove;
4212
4213     if (newGame) {
4214         forwardMostMove = backwardMostMove = currentMove = moveNum;
4215         if (gameMode == IcsExamining && moveNum == 0) {
4216           /* Workaround for ICS limitation: we are not told the wild
4217              type when starting to examine a game.  But if we ask for
4218              the move list, the move list header will tell us */
4219             ics_getting_history = H_REQUESTED;
4220             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4221             SendToICS(str);
4222         }
4223     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4224                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4225 #if ZIPPY
4226         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4227         /* [HGM] applied this also to an engine that is silently watching        */
4228         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4229             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4230             gameInfo.variant == currentlyInitializedVariant) {
4231           takeback = forwardMostMove - moveNum;
4232           for (i = 0; i < takeback; i++) {
4233             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4234             SendToProgram("undo\n", &first);
4235           }
4236         }
4237 #endif
4238
4239         forwardMostMove = moveNum;
4240         if (!pausing || currentMove > forwardMostMove)
4241           currentMove = forwardMostMove;
4242     } else {
4243         /* New part of history that is not contiguous with old part */
4244         if (pausing && gameMode == IcsExamining) {
4245             pauseExamInvalid = TRUE;
4246             forwardMostMove = pauseExamForwardMostMove;
4247             return;
4248         }
4249         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4250 #if ZIPPY
4251             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4252                 // [HGM] when we will receive the move list we now request, it will be
4253                 // fed to the engine from the first move on. So if the engine is not
4254                 // in the initial position now, bring it there.
4255                 InitChessProgram(&first, 0);
4256             }
4257 #endif
4258             ics_getting_history = H_REQUESTED;
4259             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4260             SendToICS(str);
4261         }
4262         forwardMostMove = backwardMostMove = currentMove = moveNum;
4263     }
4264
4265     /* Update the clocks */
4266     if (strchr(elapsed_time, '.')) {
4267       /* Time is in ms */
4268       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4269       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4270     } else {
4271       /* Time is in seconds */
4272       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4273       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4274     }
4275
4276
4277 #if ZIPPY
4278     if (appData.zippyPlay && newGame &&
4279         gameMode != IcsObserving && gameMode != IcsIdle &&
4280         gameMode != IcsExamining)
4281       ZippyFirstBoard(moveNum, basetime, increment);
4282 #endif
4283
4284     /* Put the move on the move list, first converting
4285        to canonical algebraic form. */
4286     if (moveNum > 0) {
4287   if (appData.debugMode) {
4288     if (appData.debugMode) { int f = forwardMostMove;
4289         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4290                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4291                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4292     }
4293     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4294     fprintf(debugFP, "moveNum = %d\n", moveNum);
4295     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4296     setbuf(debugFP, NULL);
4297   }
4298         if (moveNum <= backwardMostMove) {
4299             /* We don't know what the board looked like before
4300                this move.  Punt. */
4301           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4302             strcat(parseList[moveNum - 1], " ");
4303             strcat(parseList[moveNum - 1], elapsed_time);
4304             moveList[moveNum - 1][0] = NULLCHAR;
4305         } else if (strcmp(move_str, "none") == 0) {
4306             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4307             /* Again, we don't know what the board looked like;
4308                this is really the start of the game. */
4309             parseList[moveNum - 1][0] = NULLCHAR;
4310             moveList[moveNum - 1][0] = NULLCHAR;
4311             backwardMostMove = moveNum;
4312             startedFromSetupPosition = TRUE;
4313             fromX = fromY = toX = toY = -1;
4314         } else {
4315           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4316           //                 So we parse the long-algebraic move string in stead of the SAN move
4317           int valid; char buf[MSG_SIZ], *prom;
4318
4319           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4320                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4321           // str looks something like "Q/a1-a2"; kill the slash
4322           if(str[1] == '/')
4323             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4324           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4325           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4326                 strcat(buf, prom); // long move lacks promo specification!
4327           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4328                 if(appData.debugMode)
4329                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4330                 safeStrCpy(move_str, buf, MSG_SIZ);
4331           }
4332           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4333                                 &fromX, &fromY, &toX, &toY, &promoChar)
4334                || ParseOneMove(buf, moveNum - 1, &moveType,
4335                                 &fromX, &fromY, &toX, &toY, &promoChar);
4336           // end of long SAN patch
4337           if (valid) {
4338             (void) CoordsToAlgebraic(boards[moveNum - 1],
4339                                      PosFlags(moveNum - 1),
4340                                      fromY, fromX, toY, toX, promoChar,
4341                                      parseList[moveNum-1]);
4342             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4343               case MT_NONE:
4344               case MT_STALEMATE:
4345               default:
4346                 break;
4347               case MT_CHECK:
4348                 if(gameInfo.variant != VariantShogi)
4349                     strcat(parseList[moveNum - 1], "+");
4350                 break;
4351               case MT_CHECKMATE:
4352               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4353                 strcat(parseList[moveNum - 1], "#");
4354                 break;
4355             }
4356             strcat(parseList[moveNum - 1], " ");
4357             strcat(parseList[moveNum - 1], elapsed_time);
4358             /* currentMoveString is set as a side-effect of ParseOneMove */
4359             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4360             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4361             strcat(moveList[moveNum - 1], "\n");
4362
4363             if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4364               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4365                 ChessSquare old, new = boards[moveNum][k][j];
4366                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4367                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4368                   if(old == new) continue;
4369                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4370                   else if(new == WhiteWazir || new == BlackWazir) {
4371                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4372                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4373                       else boards[moveNum][k][j] = old; // preserve type of Gold
4374                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4375                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4376               }
4377           } else {
4378             /* Move from ICS was illegal!?  Punt. */
4379             if (appData.debugMode) {
4380               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4381               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4382             }
4383             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4384             strcat(parseList[moveNum - 1], " ");
4385             strcat(parseList[moveNum - 1], elapsed_time);
4386             moveList[moveNum - 1][0] = NULLCHAR;
4387             fromX = fromY = toX = toY = -1;
4388           }
4389         }
4390   if (appData.debugMode) {
4391     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4392     setbuf(debugFP, NULL);
4393   }
4394
4395 #if ZIPPY
4396         /* Send move to chess program (BEFORE animating it). */
4397         if (appData.zippyPlay && !newGame && newMove &&
4398            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4399
4400             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4401                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4402                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4403                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4404                             move_str);
4405                     DisplayError(str, 0);
4406                 } else {
4407                     if (first.sendTime) {
4408                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4409                     }
4410                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4411                     if (firstMove && !bookHit) {
4412                         firstMove = FALSE;
4413                         if (first.useColors) {
4414                           SendToProgram(gameMode == IcsPlayingWhite ?
4415                                         "white\ngo\n" :
4416                                         "black\ngo\n", &first);
4417                         } else {
4418                           SendToProgram("go\n", &first);
4419                         }
4420                         first.maybeThinking = TRUE;
4421                     }
4422                 }
4423             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4424               if (moveList[moveNum - 1][0] == NULLCHAR) {
4425                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4426                 DisplayError(str, 0);
4427               } else {
4428                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4429                 SendMoveToProgram(moveNum - 1, &first);
4430               }
4431             }
4432         }
4433 #endif
4434     }
4435
4436     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4437         /* If move comes from a remote source, animate it.  If it
4438            isn't remote, it will have already been animated. */
4439         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4440             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4441         }
4442         if (!pausing && appData.highlightLastMove) {
4443             SetHighlights(fromX, fromY, toX, toY);
4444         }
4445     }
4446
4447     /* Start the clocks */
4448     whiteFlag = blackFlag = FALSE;
4449     appData.clockMode = !(basetime == 0 && increment == 0);
4450     if (ticking == 0) {
4451       ics_clock_paused = TRUE;
4452       StopClocks();
4453     } else if (ticking == 1) {
4454       ics_clock_paused = FALSE;
4455     }
4456     if (gameMode == IcsIdle ||
4457         relation == RELATION_OBSERVING_STATIC ||
4458         relation == RELATION_EXAMINING ||
4459         ics_clock_paused)
4460       DisplayBothClocks();
4461     else
4462       StartClocks();
4463
4464     /* Display opponents and material strengths */
4465     if (gameInfo.variant != VariantBughouse &&
4466         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4467         if (tinyLayout || smallLayout) {
4468             if(gameInfo.variant == VariantNormal)
4469               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4470                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4471                     basetime, increment);
4472             else
4473               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4474                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4475                     basetime, increment, (int) gameInfo.variant);
4476         } else {
4477             if(gameInfo.variant == VariantNormal)
4478               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4479                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4480                     basetime, increment);
4481             else
4482               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4483                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4484                     basetime, increment, VariantName(gameInfo.variant));
4485         }
4486         DisplayTitle(str);
4487   if (appData.debugMode) {
4488     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4489   }
4490     }
4491
4492
4493     /* Display the board */
4494     if (!pausing && !appData.noGUI) {
4495
4496       if (appData.premove)
4497           if (!gotPremove ||
4498              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4499              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4500               ClearPremoveHighlights();
4501
4502       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4503         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4504       DrawPosition(j, boards[currentMove]);
4505
4506       DisplayMove(moveNum - 1);
4507       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4508             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4509               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4510         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4511       }
4512     }
4513
4514     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4515 #if ZIPPY
4516     if(bookHit) { // [HGM] book: simulate book reply
4517         static char bookMove[MSG_SIZ]; // a bit generous?
4518
4519         programStats.nodes = programStats.depth = programStats.time =
4520         programStats.score = programStats.got_only_move = 0;
4521         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4522
4523         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4524         strcat(bookMove, bookHit);
4525         HandleMachineMove(bookMove, &first);
4526     }
4527 #endif
4528 }
4529
4530 void
4531 GetMoveListEvent()
4532 {
4533     char buf[MSG_SIZ];
4534     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4535         ics_getting_history = H_REQUESTED;
4536         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4537         SendToICS(buf);
4538     }
4539 }
4540
4541 void
4542 AnalysisPeriodicEvent(force)
4543      int force;
4544 {
4545     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4546          && !force) || !appData.periodicUpdates)
4547       return;
4548
4549     /* Send . command to Crafty to collect stats */
4550     SendToProgram(".\n", &first);
4551
4552     /* Don't send another until we get a response (this makes
4553        us stop sending to old Crafty's which don't understand
4554        the "." command (sending illegal cmds resets node count & time,
4555        which looks bad)) */
4556     programStats.ok_to_send = 0;
4557 }
4558
4559 void ics_update_width(new_width)
4560         int new_width;
4561 {
4562         ics_printf("set width %d\n", new_width);
4563 }
4564
4565 void
4566 SendMoveToProgram(moveNum, cps)
4567      int moveNum;
4568      ChessProgramState *cps;
4569 {
4570     char buf[MSG_SIZ];
4571
4572     if (cps->useUsermove) {
4573       SendToProgram("usermove ", cps);
4574     }
4575     if (cps->useSAN) {
4576       char *space;
4577       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4578         int len = space - parseList[moveNum];
4579         memcpy(buf, parseList[moveNum], len);
4580         buf[len++] = '\n';
4581         buf[len] = NULLCHAR;
4582       } else {
4583         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4584       }
4585       SendToProgram(buf, cps);
4586     } else {
4587       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4588         AlphaRank(moveList[moveNum], 4);
4589         SendToProgram(moveList[moveNum], cps);
4590         AlphaRank(moveList[moveNum], 4); // and back
4591       } else
4592       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4593        * the engine. It would be nice to have a better way to identify castle
4594        * moves here. */
4595       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4596                                                                          && cps->useOOCastle) {
4597         int fromX = moveList[moveNum][0] - AAA;
4598         int fromY = moveList[moveNum][1] - ONE;
4599         int toX = moveList[moveNum][2] - AAA;
4600         int toY = moveList[moveNum][3] - ONE;
4601         if((boards[moveNum][fromY][fromX] == WhiteKing
4602             && boards[moveNum][toY][toX] == WhiteRook)
4603            || (boards[moveNum][fromY][fromX] == BlackKing
4604                && boards[moveNum][toY][toX] == BlackRook)) {
4605           if(toX > fromX) SendToProgram("O-O\n", cps);
4606           else SendToProgram("O-O-O\n", cps);
4607         }
4608         else SendToProgram(moveList[moveNum], cps);
4609       }
4610       else SendToProgram(moveList[moveNum], cps);
4611       /* End of additions by Tord */
4612     }
4613
4614     /* [HGM] setting up the opening has brought engine in force mode! */
4615     /*       Send 'go' if we are in a mode where machine should play. */
4616     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4617         (gameMode == TwoMachinesPlay   ||
4618 #if ZIPPY
4619          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4620 #endif
4621          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4622         SendToProgram("go\n", cps);
4623   if (appData.debugMode) {
4624     fprintf(debugFP, "(extra)\n");
4625   }
4626     }
4627     setboardSpoiledMachineBlack = 0;
4628 }
4629
4630 void
4631 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4632      ChessMove moveType;
4633      int fromX, fromY, toX, toY;
4634      char promoChar;
4635 {
4636     char user_move[MSG_SIZ];
4637
4638     switch (moveType) {
4639       default:
4640         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4641                 (int)moveType, fromX, fromY, toX, toY);
4642         DisplayError(user_move + strlen("say "), 0);
4643         break;
4644       case WhiteKingSideCastle:
4645       case BlackKingSideCastle:
4646       case WhiteQueenSideCastleWild:
4647       case BlackQueenSideCastleWild:
4648       /* PUSH Fabien */
4649       case WhiteHSideCastleFR:
4650       case BlackHSideCastleFR:
4651       /* POP Fabien */
4652         snprintf(user_move, MSG_SIZ, "o-o\n");
4653         break;
4654       case WhiteQueenSideCastle:
4655       case BlackQueenSideCastle:
4656       case WhiteKingSideCastleWild:
4657       case BlackKingSideCastleWild:
4658       /* PUSH Fabien */
4659       case WhiteASideCastleFR:
4660       case BlackASideCastleFR:
4661       /* POP Fabien */
4662         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4663         break;
4664       case WhiteNonPromotion:
4665       case BlackNonPromotion:
4666         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4667         break;
4668       case WhitePromotion:
4669       case BlackPromotion:
4670         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4671           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4672                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4673                 PieceToChar(WhiteFerz));
4674         else if(gameInfo.variant == VariantGreat)
4675           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4676                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4677                 PieceToChar(WhiteMan));
4678         else
4679           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4680                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4681                 promoChar);
4682         break;
4683       case WhiteDrop:
4684       case BlackDrop:
4685       drop:
4686         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4687                  ToUpper(PieceToChar((ChessSquare) fromX)),
4688                  AAA + toX, ONE + toY);
4689         break;
4690       case IllegalMove:  /* could be a variant we don't quite understand */
4691         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4692       case NormalMove:
4693       case WhiteCapturesEnPassant:
4694       case BlackCapturesEnPassant:
4695         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4696                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4697         break;
4698     }
4699     SendToICS(user_move);
4700     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4701         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4702 }
4703
4704 void
4705 UploadGameEvent()
4706 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4707     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4708     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4709     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4710         DisplayError("You cannot do this while you are playing or observing", 0);
4711         return;
4712     }
4713     if(gameMode != IcsExamining) { // is this ever not the case?
4714         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4715
4716         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4717           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4718         } else { // on FICS we must first go to general examine mode
4719           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4720         }
4721         if(gameInfo.variant != VariantNormal) {
4722             // try figure out wild number, as xboard names are not always valid on ICS
4723             for(i=1; i<=36; i++) {
4724               snprintf(buf, MSG_SIZ, "wild/%d", i);
4725                 if(StringToVariant(buf) == gameInfo.variant) break;
4726             }
4727             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4728             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4729             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4730         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4731         SendToICS(ics_prefix);
4732         SendToICS(buf);
4733         if(startedFromSetupPosition || backwardMostMove != 0) {
4734           fen = PositionToFEN(backwardMostMove, NULL);
4735           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4736             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4737             SendToICS(buf);
4738           } else { // FICS: everything has to set by separate bsetup commands
4739             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4740             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4741             SendToICS(buf);
4742             if(!WhiteOnMove(backwardMostMove)) {
4743                 SendToICS("bsetup tomove black\n");
4744             }
4745             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4746             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4747             SendToICS(buf);
4748             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4749             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4750             SendToICS(buf);
4751             i = boards[backwardMostMove][EP_STATUS];
4752             if(i >= 0) { // set e.p.
4753               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4754                 SendToICS(buf);
4755             }
4756             bsetup++;
4757           }
4758         }
4759       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4760             SendToICS("bsetup done\n"); // switch to normal examining.
4761     }
4762     for(i = backwardMostMove; i<last; i++) {
4763         char buf[20];
4764         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4765         SendToICS(buf);
4766     }
4767     SendToICS(ics_prefix);
4768     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4769 }
4770
4771 void
4772 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4773      int rf, ff, rt, ft;
4774      char promoChar;
4775      char move[7];
4776 {
4777     if (rf == DROP_RANK) {
4778       sprintf(move, "%c@%c%c\n",
4779                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4780     } else {
4781         if (promoChar == 'x' || promoChar == NULLCHAR) {
4782           sprintf(move, "%c%c%c%c\n",
4783                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4784         } else {
4785             sprintf(move, "%c%c%c%c%c\n",
4786                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4787         }
4788     }
4789 }
4790
4791 void
4792 ProcessICSInitScript(f)
4793      FILE *f;
4794 {
4795     char buf[MSG_SIZ];
4796
4797     while (fgets(buf, MSG_SIZ, f)) {
4798         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4799     }
4800
4801     fclose(f);
4802 }
4803
4804
4805 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4806 void
4807 AlphaRank(char *move, int n)
4808 {
4809 //    char *p = move, c; int x, y;
4810
4811     if (appData.debugMode) {
4812         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4813     }
4814
4815     if(move[1]=='*' &&
4816        move[2]>='0' && move[2]<='9' &&
4817        move[3]>='a' && move[3]<='x'    ) {
4818         move[1] = '@';
4819         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4820         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4821     } else
4822     if(move[0]>='0' && move[0]<='9' &&
4823        move[1]>='a' && move[1]<='x' &&
4824        move[2]>='0' && move[2]<='9' &&
4825        move[3]>='a' && move[3]<='x'    ) {
4826         /* input move, Shogi -> normal */
4827         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4828         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4829         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4830         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4831     } else
4832     if(move[1]=='@' &&
4833        move[3]>='0' && move[3]<='9' &&
4834        move[2]>='a' && move[2]<='x'    ) {
4835         move[1] = '*';
4836         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4837         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4838     } else
4839     if(
4840        move[0]>='a' && move[0]<='x' &&
4841        move[3]>='0' && move[3]<='9' &&
4842        move[2]>='a' && move[2]<='x'    ) {
4843          /* output move, normal -> Shogi */
4844         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4845         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4846         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4847         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4848         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4849     }
4850     if (appData.debugMode) {
4851         fprintf(debugFP, "   out = '%s'\n", move);
4852     }
4853 }
4854
4855 char yy_textstr[8000];
4856
4857 /* Parser for moves from gnuchess, ICS, or user typein box */
4858 Boolean
4859 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4860      char *move;
4861      int moveNum;
4862      ChessMove *moveType;
4863      int *fromX, *fromY, *toX, *toY;
4864      char *promoChar;
4865 {
4866     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4867
4868     switch (*moveType) {
4869       case WhitePromotion:
4870       case BlackPromotion:
4871       case WhiteNonPromotion:
4872       case BlackNonPromotion:
4873       case NormalMove:
4874       case WhiteCapturesEnPassant:
4875       case BlackCapturesEnPassant:
4876       case WhiteKingSideCastle:
4877       case WhiteQueenSideCastle:
4878       case BlackKingSideCastle:
4879       case BlackQueenSideCastle:
4880       case WhiteKingSideCastleWild:
4881       case WhiteQueenSideCastleWild:
4882       case BlackKingSideCastleWild:
4883       case BlackQueenSideCastleWild:
4884       /* Code added by Tord: */
4885       case WhiteHSideCastleFR:
4886       case WhiteASideCastleFR:
4887       case BlackHSideCastleFR:
4888       case BlackASideCastleFR:
4889       /* End of code added by Tord */
4890       case IllegalMove:         /* bug or odd chess variant */
4891         *fromX = currentMoveString[0] - AAA;
4892         *fromY = currentMoveString[1] - ONE;
4893         *toX = currentMoveString[2] - AAA;
4894         *toY = currentMoveString[3] - ONE;
4895         *promoChar = currentMoveString[4];
4896         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4897             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4898     if (appData.debugMode) {
4899         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4900     }
4901             *fromX = *fromY = *toX = *toY = 0;
4902             return FALSE;
4903         }
4904         if (appData.testLegality) {
4905           return (*moveType != IllegalMove);
4906         } else {
4907           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4908                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4909         }
4910
4911       case WhiteDrop:
4912       case BlackDrop:
4913         *fromX = *moveType == WhiteDrop ?
4914           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4915           (int) CharToPiece(ToLower(currentMoveString[0]));
4916         *fromY = DROP_RANK;
4917         *toX = currentMoveString[2] - AAA;
4918         *toY = currentMoveString[3] - ONE;
4919         *promoChar = NULLCHAR;
4920         return TRUE;
4921
4922       case AmbiguousMove:
4923       case ImpossibleMove:
4924       case EndOfFile:
4925       case ElapsedTime:
4926       case Comment:
4927       case PGNTag:
4928       case NAG:
4929       case WhiteWins:
4930       case BlackWins:
4931       case GameIsDrawn:
4932       default:
4933     if (appData.debugMode) {
4934         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4935     }
4936         /* bug? */
4937         *fromX = *fromY = *toX = *toY = 0;
4938         *promoChar = NULLCHAR;
4939         return FALSE;
4940     }
4941 }
4942
4943
4944 void
4945 ParsePV(char *pv, Boolean storeComments)
4946 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4947   int fromX, fromY, toX, toY; char promoChar;
4948   ChessMove moveType;
4949   Boolean valid;
4950   int nr = 0;
4951
4952   endPV = forwardMostMove;
4953   do {
4954     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4955     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4956     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4957 if(appData.debugMode){
4958 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);
4959 }
4960     if(!valid && nr == 0 &&
4961        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4962         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4963         // Hande case where played move is different from leading PV move
4964         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4965         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4966         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4967         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4968           endPV += 2; // if position different, keep this
4969           moveList[endPV-1][0] = fromX + AAA;
4970           moveList[endPV-1][1] = fromY + ONE;
4971           moveList[endPV-1][2] = toX + AAA;
4972           moveList[endPV-1][3] = toY + ONE;
4973           parseList[endPV-1][0] = NULLCHAR;
4974           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4975         }
4976       }
4977     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4978     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4979     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4980     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4981         valid++; // allow comments in PV
4982         continue;
4983     }
4984     nr++;
4985     if(endPV+1 > framePtr) break; // no space, truncate
4986     if(!valid) break;
4987     endPV++;
4988     CopyBoard(boards[endPV], boards[endPV-1]);
4989     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4990     moveList[endPV-1][0] = fromX + AAA;
4991     moveList[endPV-1][1] = fromY + ONE;
4992     moveList[endPV-1][2] = toX + AAA;
4993     moveList[endPV-1][3] = toY + ONE;
4994     moveList[endPV-1][4] = promoChar;
4995     moveList[endPV-1][5] = NULLCHAR;
4996     strncat(moveList[endPV-1], "\n", MOVE_LEN);
4997     if(storeComments)
4998         CoordsToAlgebraic(boards[endPV - 1],
4999                              PosFlags(endPV - 1),
5000                              fromY, fromX, toY, toX, promoChar,
5001                              parseList[endPV - 1]);
5002     else
5003         parseList[endPV-1][0] = NULLCHAR;
5004   } while(valid);
5005   currentMove = endPV;
5006   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5007   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5008                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5009   DrawPosition(TRUE, boards[currentMove]);
5010 }
5011
5012 static int lastX, lastY;
5013
5014 Boolean
5015 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5016 {
5017         int startPV;
5018         char *p;
5019
5020         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5021         lastX = x; lastY = y;
5022         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5023         startPV = index;
5024         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5025         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5026         index = startPV;
5027         do{ while(buf[index] && buf[index] != '\n') index++;
5028         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5029         buf[index] = 0;
5030         ParsePV(buf+startPV, FALSE);
5031         *start = startPV; *end = index-1;
5032         return TRUE;
5033 }
5034
5035 Boolean
5036 LoadPV(int x, int y)
5037 { // called on right mouse click to load PV
5038   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5039   lastX = x; lastY = y;
5040   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5041   return TRUE;
5042 }
5043
5044 void
5045 UnLoadPV()
5046 {
5047   if(endPV < 0) return;
5048   endPV = -1;
5049   currentMove = forwardMostMove;
5050   ClearPremoveHighlights();
5051   DrawPosition(TRUE, boards[currentMove]);
5052 }
5053
5054 void
5055 MovePV(int x, int y, int h)
5056 { // step through PV based on mouse coordinates (called on mouse move)
5057   int margin = h>>3, step = 0;
5058
5059   if(endPV < 0) return;
5060   // we must somehow check if right button is still down (might be released off board!)
5061   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5062   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5063   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5064   if(!step) return;
5065   lastX = x; lastY = y;
5066   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5067   currentMove += step;
5068   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5069   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5070                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5071   DrawPosition(FALSE, boards[currentMove]);
5072 }
5073
5074
5075 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5076 // All positions will have equal probability, but the current method will not provide a unique
5077 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5078 #define DARK 1
5079 #define LITE 2
5080 #define ANY 3
5081
5082 int squaresLeft[4];
5083 int piecesLeft[(int)BlackPawn];
5084 int seed, nrOfShuffles;
5085
5086 void GetPositionNumber()
5087 {       // sets global variable seed
5088         int i;
5089
5090         seed = appData.defaultFrcPosition;
5091         if(seed < 0) { // randomize based on time for negative FRC position numbers
5092                 for(i=0; i<50; i++) seed += random();
5093                 seed = random() ^ random() >> 8 ^ random() << 8;
5094                 if(seed<0) seed = -seed;
5095         }
5096 }
5097
5098 int put(Board board, int pieceType, int rank, int n, int shade)
5099 // put the piece on the (n-1)-th empty squares of the given shade
5100 {
5101         int i;
5102
5103         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5104                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5105                         board[rank][i] = (ChessSquare) pieceType;
5106                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5107                         squaresLeft[ANY]--;
5108                         piecesLeft[pieceType]--;
5109                         return i;
5110                 }
5111         }
5112         return -1;
5113 }
5114
5115
5116 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5117 // calculate where the next piece goes, (any empty square), and put it there
5118 {
5119         int i;
5120
5121         i = seed % squaresLeft[shade];
5122         nrOfShuffles *= squaresLeft[shade];
5123         seed /= squaresLeft[shade];
5124         put(board, pieceType, rank, i, shade);
5125 }
5126
5127 void AddTwoPieces(Board board, int pieceType, int rank)
5128 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5129 {
5130         int i, n=squaresLeft[ANY], j=n-1, k;
5131
5132         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5133         i = seed % k;  // pick one
5134         nrOfShuffles *= k;
5135         seed /= k;
5136         while(i >= j) i -= j--;
5137         j = n - 1 - j; i += j;
5138         put(board, pieceType, rank, j, ANY);
5139         put(board, pieceType, rank, i, ANY);
5140 }
5141
5142 void SetUpShuffle(Board board, int number)
5143 {
5144         int i, p, first=1;
5145
5146         GetPositionNumber(); nrOfShuffles = 1;
5147
5148         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5149         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5150         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5151
5152         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5153
5154         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5155             p = (int) board[0][i];
5156             if(p < (int) BlackPawn) piecesLeft[p] ++;
5157             board[0][i] = EmptySquare;
5158         }
5159
5160         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5161             // shuffles restricted to allow normal castling put KRR first
5162             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5163                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5164             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5165                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5166             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5167                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5168             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5169                 put(board, WhiteRook, 0, 0, ANY);
5170             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5171         }
5172
5173         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5174             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5175             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5176                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5177                 while(piecesLeft[p] >= 2) {
5178                     AddOnePiece(board, p, 0, LITE);
5179                     AddOnePiece(board, p, 0, DARK);
5180                 }
5181                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5182             }
5183
5184         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5185             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5186             // but we leave King and Rooks for last, to possibly obey FRC restriction
5187             if(p == (int)WhiteRook) continue;
5188             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5189             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5190         }
5191
5192         // now everything is placed, except perhaps King (Unicorn) and Rooks
5193
5194         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5195             // Last King gets castling rights
5196             while(piecesLeft[(int)WhiteUnicorn]) {
5197                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5198                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5199             }
5200
5201             while(piecesLeft[(int)WhiteKing]) {
5202                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5203                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5204             }
5205
5206
5207         } else {
5208             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5209             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5210         }
5211
5212         // Only Rooks can be left; simply place them all
5213         while(piecesLeft[(int)WhiteRook]) {
5214                 i = put(board, WhiteRook, 0, 0, ANY);
5215                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5216                         if(first) {
5217                                 first=0;
5218                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5219                         }
5220                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5221                 }
5222         }
5223         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5224             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5225         }
5226
5227         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5228 }
5229
5230 int SetCharTable( char *table, const char * map )
5231 /* [HGM] moved here from winboard.c because of its general usefulness */
5232 /*       Basically a safe strcpy that uses the last character as King */
5233 {
5234     int result = FALSE; int NrPieces;
5235
5236     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5237                     && NrPieces >= 12 && !(NrPieces&1)) {
5238         int i; /* [HGM] Accept even length from 12 to 34 */
5239
5240         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5241         for( i=0; i<NrPieces/2-1; i++ ) {
5242             table[i] = map[i];
5243             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5244         }
5245         table[(int) WhiteKing]  = map[NrPieces/2-1];
5246         table[(int) BlackKing]  = map[NrPieces-1];
5247
5248         result = TRUE;
5249     }
5250
5251     return result;
5252 }
5253
5254 void Prelude(Board board)
5255 {       // [HGM] superchess: random selection of exo-pieces
5256         int i, j, k; ChessSquare p;
5257         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5258
5259         GetPositionNumber(); // use FRC position number
5260
5261         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5262             SetCharTable(pieceToChar, appData.pieceToCharTable);
5263             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5264                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5265         }
5266
5267         j = seed%4;                 seed /= 4;
5268         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5269         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5270         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5271         j = seed%3 + (seed%3 >= j); seed /= 3;
5272         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5273         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5274         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5275         j = seed%3;                 seed /= 3;
5276         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5277         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5278         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5279         j = seed%2 + (seed%2 >= j); seed /= 2;
5280         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5281         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5282         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5283         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5284         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5285         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5286         put(board, exoPieces[0],    0, 0, ANY);
5287         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5288 }
5289
5290 void
5291 InitPosition(redraw)
5292      int redraw;
5293 {
5294     ChessSquare (* pieces)[BOARD_FILES];
5295     int i, j, pawnRow, overrule,
5296     oldx = gameInfo.boardWidth,
5297     oldy = gameInfo.boardHeight,
5298     oldh = gameInfo.holdingsWidth,
5299     oldv = gameInfo.variant;
5300
5301     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5302
5303     /* [AS] Initialize pv info list [HGM] and game status */
5304     {
5305         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5306             pvInfoList[i].depth = 0;
5307             boards[i][EP_STATUS] = EP_NONE;
5308             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5309         }
5310
5311         initialRulePlies = 0; /* 50-move counter start */
5312
5313         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5314         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5315     }
5316
5317
5318     /* [HGM] logic here is completely changed. In stead of full positions */
5319     /* the initialized data only consist of the two backranks. The switch */
5320     /* selects which one we will use, which is than copied to the Board   */
5321     /* initialPosition, which for the rest is initialized by Pawns and    */
5322     /* empty squares. This initial position is then copied to boards[0],  */
5323     /* possibly after shuffling, so that it remains available.            */
5324
5325     gameInfo.holdingsWidth = 0; /* default board sizes */
5326     gameInfo.boardWidth    = 8;
5327     gameInfo.boardHeight   = 8;
5328     gameInfo.holdingsSize  = 0;
5329     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5330     for(i=0; i<BOARD_FILES-2; i++)
5331       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5332     initialPosition[EP_STATUS] = EP_NONE;
5333     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5334     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5335          SetCharTable(pieceNickName, appData.pieceNickNames);
5336     else SetCharTable(pieceNickName, "............");
5337     pieces = FIDEArray;
5338
5339     switch (gameInfo.variant) {
5340     case VariantFischeRandom:
5341       shuffleOpenings = TRUE;
5342     default:
5343       break;
5344     case VariantShatranj:
5345       pieces = ShatranjArray;
5346       nrCastlingRights = 0;
5347       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5348       break;
5349     case VariantMakruk:
5350       pieces = makrukArray;
5351       nrCastlingRights = 0;
5352       startedFromSetupPosition = TRUE;
5353       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5354       break;
5355     case VariantTwoKings:
5356       pieces = twoKingsArray;
5357       break;
5358     case VariantCapaRandom:
5359       shuffleOpenings = TRUE;
5360     case VariantCapablanca:
5361       pieces = CapablancaArray;
5362       gameInfo.boardWidth = 10;
5363       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5364       break;
5365     case VariantGothic:
5366       pieces = GothicArray;
5367       gameInfo.boardWidth = 10;
5368       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5369       break;
5370     case VariantSChess:
5371       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5372       gameInfo.holdingsSize = 7;
5373       break;
5374     case VariantJanus:
5375       pieces = JanusArray;
5376       gameInfo.boardWidth = 10;
5377       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5378       nrCastlingRights = 6;
5379         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5380         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5381         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5382         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5383         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5384         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5385       break;
5386     case VariantFalcon:
5387       pieces = FalconArray;
5388       gameInfo.boardWidth = 10;
5389       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5390       break;
5391     case VariantXiangqi:
5392       pieces = XiangqiArray;
5393       gameInfo.boardWidth  = 9;
5394       gameInfo.boardHeight = 10;
5395       nrCastlingRights = 0;
5396       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5397       break;
5398     case VariantShogi:
5399       pieces = ShogiArray;
5400       gameInfo.boardWidth  = 9;
5401       gameInfo.boardHeight = 9;
5402       gameInfo.holdingsSize = 7;
5403       nrCastlingRights = 0;
5404       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5405       break;
5406     case VariantCourier:
5407       pieces = CourierArray;
5408       gameInfo.boardWidth  = 12;
5409       nrCastlingRights = 0;
5410       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5411       break;
5412     case VariantKnightmate:
5413       pieces = KnightmateArray;
5414       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5415       break;
5416     case VariantFairy:
5417       pieces = fairyArray;
5418       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5419       break;
5420     case VariantGreat:
5421       pieces = GreatArray;
5422       gameInfo.boardWidth = 10;
5423       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5424       gameInfo.holdingsSize = 8;
5425       break;
5426     case VariantSuper:
5427       pieces = FIDEArray;
5428       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5429       gameInfo.holdingsSize = 8;
5430       startedFromSetupPosition = TRUE;
5431       break;
5432     case VariantCrazyhouse:
5433     case VariantBughouse:
5434       pieces = FIDEArray;
5435       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5436       gameInfo.holdingsSize = 5;
5437       break;
5438     case VariantWildCastle:
5439       pieces = FIDEArray;
5440       /* !!?shuffle with kings guaranteed to be on d or e file */
5441       shuffleOpenings = 1;
5442       break;
5443     case VariantNoCastle:
5444       pieces = FIDEArray;
5445       nrCastlingRights = 0;
5446       /* !!?unconstrained back-rank shuffle */
5447       shuffleOpenings = 1;
5448       break;
5449     }
5450
5451     overrule = 0;
5452     if(appData.NrFiles >= 0) {
5453         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5454         gameInfo.boardWidth = appData.NrFiles;
5455     }
5456     if(appData.NrRanks >= 0) {
5457         gameInfo.boardHeight = appData.NrRanks;
5458     }
5459     if(appData.holdingsSize >= 0) {
5460         i = appData.holdingsSize;
5461         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5462         gameInfo.holdingsSize = i;
5463     }
5464     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5465     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5466         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5467
5468     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5469     if(pawnRow < 1) pawnRow = 1;
5470     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5471
5472     /* User pieceToChar list overrules defaults */
5473     if(appData.pieceToCharTable != NULL)
5474         SetCharTable(pieceToChar, appData.pieceToCharTable);
5475
5476     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5477
5478         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5479             s = (ChessSquare) 0; /* account holding counts in guard band */
5480         for( i=0; i<BOARD_HEIGHT; i++ )
5481             initialPosition[i][j] = s;
5482
5483         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5484         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5485         initialPosition[pawnRow][j] = WhitePawn;
5486         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5487         if(gameInfo.variant == VariantXiangqi) {
5488             if(j&1) {
5489                 initialPosition[pawnRow][j] =
5490                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5491                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5492                    initialPosition[2][j] = WhiteCannon;
5493                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5494                 }
5495             }
5496         }
5497         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5498     }
5499     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5500
5501             j=BOARD_LEFT+1;
5502             initialPosition[1][j] = WhiteBishop;
5503             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5504             j=BOARD_RGHT-2;
5505             initialPosition[1][j] = WhiteRook;
5506             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5507     }
5508
5509     if( nrCastlingRights == -1) {
5510         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5511         /*       This sets default castling rights from none to normal corners   */
5512         /* Variants with other castling rights must set them themselves above    */
5513         nrCastlingRights = 6;
5514
5515         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5516         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5517         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5518         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5519         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5520         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5521      }
5522
5523      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5524      if(gameInfo.variant == VariantGreat) { // promotion commoners
5525         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5526         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5527         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5528         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5529      }
5530      if( gameInfo.variant == VariantSChess ) {
5531       initialPosition[1][0] = BlackMarshall;
5532       initialPosition[2][0] = BlackAngel;
5533       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5534       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5535       initialPosition[1][1] = initialPosition[2][1] = 
5536       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5537      }
5538   if (appData.debugMode) {
5539     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5540   }
5541     if(shuffleOpenings) {
5542         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5543         startedFromSetupPosition = TRUE;
5544     }
5545     if(startedFromPositionFile) {
5546       /* [HGM] loadPos: use PositionFile for every new game */
5547       CopyBoard(initialPosition, filePosition);
5548       for(i=0; i<nrCastlingRights; i++)
5549           initialRights[i] = filePosition[CASTLING][i];
5550       startedFromSetupPosition = TRUE;
5551     }
5552
5553     CopyBoard(boards[0], initialPosition);
5554
5555     if(oldx != gameInfo.boardWidth ||
5556        oldy != gameInfo.boardHeight ||
5557        oldh != gameInfo.holdingsWidth
5558 #ifdef GOTHIC
5559        || oldv == VariantGothic ||        // For licensing popups
5560        gameInfo.variant == VariantGothic
5561 #endif
5562 #ifdef FALCON
5563        || oldv == VariantFalcon ||
5564        gameInfo.variant == VariantFalcon
5565 #endif
5566                                          )
5567             InitDrawingSizes(-2 ,0);
5568
5569     if (redraw)
5570       DrawPosition(TRUE, boards[currentMove]);
5571 }
5572
5573 void
5574 SendBoard(cps, moveNum)
5575      ChessProgramState *cps;
5576      int moveNum;
5577 {
5578     char message[MSG_SIZ];
5579
5580     if (cps->useSetboard) {
5581       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5582       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5583       SendToProgram(message, cps);
5584       free(fen);
5585
5586     } else {
5587       ChessSquare *bp;
5588       int i, j;
5589       /* Kludge to set black to move, avoiding the troublesome and now
5590        * deprecated "black" command.
5591        */
5592       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5593         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5594
5595       SendToProgram("edit\n", cps);
5596       SendToProgram("#\n", cps);
5597       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5598         bp = &boards[moveNum][i][BOARD_LEFT];
5599         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5600           if ((int) *bp < (int) BlackPawn) {
5601             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5602                     AAA + j, ONE + i);
5603             if(message[0] == '+' || message[0] == '~') {
5604               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5605                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5606                         AAA + j, ONE + i);
5607             }
5608             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5609                 message[1] = BOARD_RGHT   - 1 - j + '1';
5610                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5611             }
5612             SendToProgram(message, cps);
5613           }
5614         }
5615       }
5616
5617       SendToProgram("c\n", cps);
5618       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5619         bp = &boards[moveNum][i][BOARD_LEFT];
5620         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5621           if (((int) *bp != (int) EmptySquare)
5622               && ((int) *bp >= (int) BlackPawn)) {
5623             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5624                     AAA + j, ONE + i);
5625             if(message[0] == '+' || message[0] == '~') {
5626               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5627                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5628                         AAA + j, ONE + i);
5629             }
5630             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5631                 message[1] = BOARD_RGHT   - 1 - j + '1';
5632                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5633             }
5634             SendToProgram(message, cps);
5635           }
5636         }
5637       }
5638
5639       SendToProgram(".\n", cps);
5640     }
5641     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5642 }
5643
5644 static int autoQueen; // [HGM] oneclick
5645
5646 int
5647 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5648 {
5649     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5650     /* [HGM] add Shogi promotions */
5651     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5652     ChessSquare piece;
5653     ChessMove moveType;
5654     Boolean premove;
5655
5656     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5657     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5658
5659     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5660       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5661         return FALSE;
5662
5663     piece = boards[currentMove][fromY][fromX];
5664     if(gameInfo.variant == VariantShogi) {
5665         promotionZoneSize = BOARD_HEIGHT/3;
5666         highestPromotingPiece = (int)WhiteFerz;
5667     } else if(gameInfo.variant == VariantMakruk) {
5668         promotionZoneSize = 3;
5669     }
5670
5671     // Treat Lance as Pawn when it is not representing Amazon
5672     if(gameInfo.variant != VariantSuper) {
5673         if(piece == WhiteLance) piece = WhitePawn; else
5674         if(piece == BlackLance) piece = BlackPawn;
5675     }
5676
5677     // next weed out all moves that do not touch the promotion zone at all
5678     if((int)piece >= BlackPawn) {
5679         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5680              return FALSE;
5681         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5682     } else {
5683         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5684            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5685     }
5686
5687     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5688
5689     // weed out mandatory Shogi promotions
5690     if(gameInfo.variant == VariantShogi) {
5691         if(piece >= BlackPawn) {
5692             if(toY == 0 && piece == BlackPawn ||
5693                toY == 0 && piece == BlackQueen ||
5694                toY <= 1 && piece == BlackKnight) {
5695                 *promoChoice = '+';
5696                 return FALSE;
5697             }
5698         } else {
5699             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5700                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5701                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5702                 *promoChoice = '+';
5703                 return FALSE;
5704             }
5705         }
5706     }
5707
5708     // weed out obviously illegal Pawn moves
5709     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5710         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5711         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5712         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5713         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5714         // note we are not allowed to test for valid (non-)capture, due to premove
5715     }
5716
5717     // we either have a choice what to promote to, or (in Shogi) whether to promote
5718     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5719         *promoChoice = PieceToChar(BlackFerz);  // no choice
5720         return FALSE;
5721     }
5722     // no sense asking what we must promote to if it is going to explode...
5723     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5724         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5725         return FALSE;
5726     }
5727     if(autoQueen) { // predetermined
5728         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5729              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5730         else *promoChoice = PieceToChar(BlackQueen);
5731         return FALSE;
5732     }
5733
5734     // suppress promotion popup on illegal moves that are not premoves
5735     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5736               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5737     if(appData.testLegality && !premove) {
5738         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5739                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5740         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5741             return FALSE;
5742     }
5743
5744     return TRUE;
5745 }
5746
5747 int
5748 InPalace(row, column)
5749      int row, column;
5750 {   /* [HGM] for Xiangqi */
5751     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5752          column < (BOARD_WIDTH + 4)/2 &&
5753          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5754     return FALSE;
5755 }
5756
5757 int
5758 PieceForSquare (x, y)
5759      int x;
5760      int y;
5761 {
5762   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5763      return -1;
5764   else
5765      return boards[currentMove][y][x];
5766 }
5767
5768 int
5769 OKToStartUserMove(x, y)
5770      int x, y;
5771 {
5772     ChessSquare from_piece;
5773     int white_piece;
5774
5775     if (matchMode) return FALSE;
5776     if (gameMode == EditPosition) return TRUE;
5777
5778     if (x >= 0 && y >= 0)
5779       from_piece = boards[currentMove][y][x];
5780     else
5781       from_piece = EmptySquare;
5782
5783     if (from_piece == EmptySquare) return FALSE;
5784
5785     white_piece = (int)from_piece >= (int)WhitePawn &&
5786       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5787
5788     switch (gameMode) {
5789       case PlayFromGameFile:
5790       case AnalyzeFile:
5791       case TwoMachinesPlay:
5792       case EndOfGame:
5793         return FALSE;
5794
5795       case IcsObserving:
5796       case IcsIdle:
5797         return FALSE;
5798
5799       case MachinePlaysWhite:
5800       case IcsPlayingBlack:
5801         if (appData.zippyPlay) return FALSE;
5802         if (white_piece) {
5803             DisplayMoveError(_("You are playing Black"));
5804             return FALSE;
5805         }
5806         break;
5807
5808       case MachinePlaysBlack:
5809       case IcsPlayingWhite:
5810         if (appData.zippyPlay) return FALSE;
5811         if (!white_piece) {
5812             DisplayMoveError(_("You are playing White"));
5813             return FALSE;
5814         }
5815         break;
5816
5817       case EditGame:
5818         if (!white_piece && WhiteOnMove(currentMove)) {
5819             DisplayMoveError(_("It is White's turn"));
5820             return FALSE;
5821         }
5822         if (white_piece && !WhiteOnMove(currentMove)) {
5823             DisplayMoveError(_("It is Black's turn"));
5824             return FALSE;
5825         }
5826         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5827             /* Editing correspondence game history */
5828             /* Could disallow this or prompt for confirmation */
5829             cmailOldMove = -1;
5830         }
5831         break;
5832
5833       case BeginningOfGame:
5834         if (appData.icsActive) return FALSE;
5835         if (!appData.noChessProgram) {
5836             if (!white_piece) {
5837                 DisplayMoveError(_("You are playing White"));
5838                 return FALSE;
5839             }
5840         }
5841         break;
5842
5843       case Training:
5844         if (!white_piece && WhiteOnMove(currentMove)) {
5845             DisplayMoveError(_("It is White's turn"));
5846             return FALSE;
5847         }
5848         if (white_piece && !WhiteOnMove(currentMove)) {
5849             DisplayMoveError(_("It is Black's turn"));
5850             return FALSE;
5851         }
5852         break;
5853
5854       default:
5855       case IcsExamining:
5856         break;
5857     }
5858     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5859         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5860         && gameMode != AnalyzeFile && gameMode != Training) {
5861         DisplayMoveError(_("Displayed position is not current"));
5862         return FALSE;
5863     }
5864     return TRUE;
5865 }
5866
5867 Boolean
5868 OnlyMove(int *x, int *y, Boolean captures) {
5869     DisambiguateClosure cl;
5870     if (appData.zippyPlay) return FALSE;
5871     switch(gameMode) {
5872       case MachinePlaysBlack:
5873       case IcsPlayingWhite:
5874       case BeginningOfGame:
5875         if(!WhiteOnMove(currentMove)) return FALSE;
5876         break;
5877       case MachinePlaysWhite:
5878       case IcsPlayingBlack:
5879         if(WhiteOnMove(currentMove)) return FALSE;
5880         break;
5881       case EditGame:
5882         break;
5883       default:
5884         return FALSE;
5885     }
5886     cl.pieceIn = EmptySquare;
5887     cl.rfIn = *y;
5888     cl.ffIn = *x;
5889     cl.rtIn = -1;
5890     cl.ftIn = -1;
5891     cl.promoCharIn = NULLCHAR;
5892     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5893     if( cl.kind == NormalMove ||
5894         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5895         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5896         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5897       fromX = cl.ff;
5898       fromY = cl.rf;
5899       *x = cl.ft;
5900       *y = cl.rt;
5901       return TRUE;
5902     }
5903     if(cl.kind != ImpossibleMove) return FALSE;
5904     cl.pieceIn = EmptySquare;
5905     cl.rfIn = -1;
5906     cl.ffIn = -1;
5907     cl.rtIn = *y;
5908     cl.ftIn = *x;
5909     cl.promoCharIn = NULLCHAR;
5910     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5911     if( cl.kind == NormalMove ||
5912         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5913         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5914         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5915       fromX = cl.ff;
5916       fromY = cl.rf;
5917       *x = cl.ft;
5918       *y = cl.rt;
5919       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5920       return TRUE;
5921     }
5922     return FALSE;
5923 }
5924
5925 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5926 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5927 int lastLoadGameUseList = FALSE;
5928 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5929 ChessMove lastLoadGameStart = EndOfFile;
5930
5931 void
5932 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5933      int fromX, fromY, toX, toY;
5934      int promoChar;
5935 {
5936     ChessMove moveType;
5937     ChessSquare pdown, pup;
5938
5939     /* Check if the user is playing in turn.  This is complicated because we
5940        let the user "pick up" a piece before it is his turn.  So the piece he
5941        tried to pick up may have been captured by the time he puts it down!
5942        Therefore we use the color the user is supposed to be playing in this
5943        test, not the color of the piece that is currently on the starting
5944        square---except in EditGame mode, where the user is playing both
5945        sides; fortunately there the capture race can't happen.  (It can
5946        now happen in IcsExamining mode, but that's just too bad.  The user
5947        will get a somewhat confusing message in that case.)
5948        */
5949
5950     switch (gameMode) {
5951       case PlayFromGameFile:
5952       case AnalyzeFile:
5953       case TwoMachinesPlay:
5954       case EndOfGame:
5955       case IcsObserving:
5956       case IcsIdle:
5957         /* We switched into a game mode where moves are not accepted,
5958            perhaps while the mouse button was down. */
5959         return;
5960
5961       case MachinePlaysWhite:
5962         /* User is moving for Black */
5963         if (WhiteOnMove(currentMove)) {
5964             DisplayMoveError(_("It is White's turn"));
5965             return;
5966         }
5967         break;
5968
5969       case MachinePlaysBlack:
5970         /* User is moving for White */
5971         if (!WhiteOnMove(currentMove)) {
5972             DisplayMoveError(_("It is Black's turn"));
5973             return;
5974         }
5975         break;
5976
5977       case EditGame:
5978       case IcsExamining:
5979       case BeginningOfGame:
5980       case AnalyzeMode:
5981       case Training:
5982         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5983             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5984             /* User is moving for Black */
5985             if (WhiteOnMove(currentMove)) {
5986                 DisplayMoveError(_("It is White's turn"));
5987                 return;
5988             }
5989         } else {
5990             /* User is moving for White */
5991             if (!WhiteOnMove(currentMove)) {
5992                 DisplayMoveError(_("It is Black's turn"));
5993                 return;
5994             }
5995         }
5996         break;
5997
5998       case IcsPlayingBlack:
5999         /* User is moving for Black */
6000         if (WhiteOnMove(currentMove)) {
6001             if (!appData.premove) {
6002                 DisplayMoveError(_("It is White's turn"));
6003             } else if (toX >= 0 && toY >= 0) {
6004                 premoveToX = toX;
6005                 premoveToY = toY;
6006                 premoveFromX = fromX;
6007                 premoveFromY = fromY;
6008                 premovePromoChar = promoChar;
6009                 gotPremove = 1;
6010                 if (appData.debugMode)
6011                     fprintf(debugFP, "Got premove: fromX %d,"
6012                             "fromY %d, toX %d, toY %d\n",
6013                             fromX, fromY, toX, toY);
6014             }
6015             return;
6016         }
6017         break;
6018
6019       case IcsPlayingWhite:
6020         /* User is moving for White */
6021         if (!WhiteOnMove(currentMove)) {
6022             if (!appData.premove) {
6023                 DisplayMoveError(_("It is Black's turn"));
6024             } else if (toX >= 0 && toY >= 0) {
6025                 premoveToX = toX;
6026                 premoveToY = toY;
6027                 premoveFromX = fromX;
6028                 premoveFromY = fromY;
6029                 premovePromoChar = promoChar;
6030                 gotPremove = 1;
6031                 if (appData.debugMode)
6032                     fprintf(debugFP, "Got premove: fromX %d,"
6033                             "fromY %d, toX %d, toY %d\n",
6034                             fromX, fromY, toX, toY);
6035             }
6036             return;
6037         }
6038         break;
6039
6040       default:
6041         break;
6042
6043       case EditPosition:
6044         /* EditPosition, empty square, or different color piece;
6045            click-click move is possible */
6046         if (toX == -2 || toY == -2) {
6047             boards[0][fromY][fromX] = EmptySquare;
6048             DrawPosition(FALSE, boards[currentMove]);
6049             return;
6050         } else if (toX >= 0 && toY >= 0) {
6051             boards[0][toY][toX] = boards[0][fromY][fromX];
6052             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6053                 if(boards[0][fromY][0] != EmptySquare) {
6054                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6055                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6056                 }
6057             } else
6058             if(fromX == BOARD_RGHT+1) {
6059                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6060                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6061                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6062                 }
6063             } else
6064             boards[0][fromY][fromX] = EmptySquare;
6065             DrawPosition(FALSE, boards[currentMove]);
6066             return;
6067         }
6068         return;
6069     }
6070
6071     if(toX < 0 || toY < 0) return;
6072     pdown = boards[currentMove][fromY][fromX];
6073     pup = boards[currentMove][toY][toX];
6074
6075     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6076     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
6077          if( pup != EmptySquare ) return;
6078          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6079            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6080                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6081            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6082            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6083            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6084            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6085          fromY = DROP_RANK;
6086     }
6087
6088     /* [HGM] always test for legality, to get promotion info */
6089     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6090                                          fromY, fromX, toY, toX, promoChar);
6091     /* [HGM] but possibly ignore an IllegalMove result */
6092     if (appData.testLegality) {
6093         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6094             DisplayMoveError(_("Illegal move"));
6095             return;
6096         }
6097     }
6098
6099     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6100 }
6101
6102 /* Common tail of UserMoveEvent and DropMenuEvent */
6103 int
6104 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6105      ChessMove moveType;
6106      int fromX, fromY, toX, toY;
6107      /*char*/int promoChar;
6108 {
6109     char *bookHit = 0;
6110
6111     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6112         // [HGM] superchess: suppress promotions to non-available piece
6113         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6114         if(WhiteOnMove(currentMove)) {
6115             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6116         } else {
6117             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6118         }
6119     }
6120
6121     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6122        move type in caller when we know the move is a legal promotion */
6123     if(moveType == NormalMove && promoChar)
6124         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6125
6126     /* [HGM] <popupFix> The following if has been moved here from
6127        UserMoveEvent(). Because it seemed to belong here (why not allow
6128        piece drops in training games?), and because it can only be
6129        performed after it is known to what we promote. */
6130     if (gameMode == Training) {
6131       /* compare the move played on the board to the next move in the
6132        * game. If they match, display the move and the opponent's response.
6133        * If they don't match, display an error message.
6134        */
6135       int saveAnimate;
6136       Board testBoard;
6137       CopyBoard(testBoard, boards[currentMove]);
6138       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6139
6140       if (CompareBoards(testBoard, boards[currentMove+1])) {
6141         ForwardInner(currentMove+1);
6142
6143         /* Autoplay the opponent's response.
6144          * if appData.animate was TRUE when Training mode was entered,
6145          * the response will be animated.
6146          */
6147         saveAnimate = appData.animate;
6148         appData.animate = animateTraining;
6149         ForwardInner(currentMove+1);
6150         appData.animate = saveAnimate;
6151
6152         /* check for the end of the game */
6153         if (currentMove >= forwardMostMove) {
6154           gameMode = PlayFromGameFile;
6155           ModeHighlight();
6156           SetTrainingModeOff();
6157           DisplayInformation(_("End of game"));
6158         }
6159       } else {
6160         DisplayError(_("Incorrect move"), 0);
6161       }
6162       return 1;
6163     }
6164
6165   /* Ok, now we know that the move is good, so we can kill
6166      the previous line in Analysis Mode */
6167   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6168                                 && currentMove < forwardMostMove) {
6169     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6170     else forwardMostMove = currentMove;
6171   }
6172
6173   /* If we need the chess program but it's dead, restart it */
6174   ResurrectChessProgram();
6175
6176   /* A user move restarts a paused game*/
6177   if (pausing)
6178     PauseEvent();
6179
6180   thinkOutput[0] = NULLCHAR;
6181
6182   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6183
6184   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6185
6186   if (gameMode == BeginningOfGame) {
6187     if (appData.noChessProgram) {
6188       gameMode = EditGame;
6189       SetGameInfo();
6190     } else {
6191       char buf[MSG_SIZ];
6192       gameMode = MachinePlaysBlack;
6193       StartClocks();
6194       SetGameInfo();
6195       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6196       DisplayTitle(buf);
6197       if (first.sendName) {
6198         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6199         SendToProgram(buf, &first);
6200       }
6201       StartClocks();
6202     }
6203     ModeHighlight();
6204   }
6205
6206   /* Relay move to ICS or chess engine */
6207   if (appData.icsActive) {
6208     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6209         gameMode == IcsExamining) {
6210       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6211         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6212         SendToICS("draw ");
6213         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6214       }
6215       // also send plain move, in case ICS does not understand atomic claims
6216       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6217       ics_user_moved = 1;
6218     }
6219   } else {
6220     if (first.sendTime && (gameMode == BeginningOfGame ||
6221                            gameMode == MachinePlaysWhite ||
6222                            gameMode == MachinePlaysBlack)) {
6223       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6224     }
6225     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6226          // [HGM] book: if program might be playing, let it use book
6227         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6228         first.maybeThinking = TRUE;
6229     } else SendMoveToProgram(forwardMostMove-1, &first);
6230     if (currentMove == cmailOldMove + 1) {
6231       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6232     }
6233   }
6234
6235   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6236
6237   switch (gameMode) {
6238   case EditGame:
6239     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6240     case MT_NONE:
6241     case MT_CHECK:
6242       break;
6243     case MT_CHECKMATE:
6244     case MT_STAINMATE:
6245       if (WhiteOnMove(currentMove)) {
6246         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6247       } else {
6248         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6249       }
6250       break;
6251     case MT_STALEMATE:
6252       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6253       break;
6254     }
6255     break;
6256
6257   case MachinePlaysBlack:
6258   case MachinePlaysWhite:
6259     /* disable certain menu options while machine is thinking */
6260     SetMachineThinkingEnables();
6261     break;
6262
6263   default:
6264     break;
6265   }
6266
6267   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6268
6269   if(bookHit) { // [HGM] book: simulate book reply
6270         static char bookMove[MSG_SIZ]; // a bit generous?
6271
6272         programStats.nodes = programStats.depth = programStats.time =
6273         programStats.score = programStats.got_only_move = 0;
6274         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6275
6276         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6277         strcat(bookMove, bookHit);
6278         HandleMachineMove(bookMove, &first);
6279   }
6280   return 1;
6281 }
6282
6283 void
6284 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6285      Board board;
6286      int flags;
6287      ChessMove kind;
6288      int rf, ff, rt, ft;
6289      VOIDSTAR closure;
6290 {
6291     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6292     Markers *m = (Markers *) closure;
6293     if(rf == fromY && ff == fromX)
6294         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6295                          || kind == WhiteCapturesEnPassant
6296                          || kind == BlackCapturesEnPassant);
6297     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6298 }
6299
6300 void
6301 MarkTargetSquares(int clear)
6302 {
6303   int x, y;
6304   if(!appData.markers || !appData.highlightDragging ||
6305      !appData.testLegality || gameMode == EditPosition) return;
6306   if(clear) {
6307     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6308   } else {
6309     int capt = 0;
6310     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6311     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6312       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6313       if(capt)
6314       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6315     }
6316   }
6317   DrawPosition(TRUE, NULL);
6318 }
6319
6320 int
6321 Explode(Board board, int fromX, int fromY, int toX, int toY)
6322 {
6323     if(gameInfo.variant == VariantAtomic &&
6324        (board[toY][toX] != EmptySquare ||                     // capture?
6325         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6326                          board[fromY][fromX] == BlackPawn   )
6327       )) {
6328         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6329         return TRUE;
6330     }
6331     return FALSE;
6332 }
6333
6334 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6335
6336 void LeftClick(ClickType clickType, int xPix, int yPix)
6337 {
6338     int x, y;
6339     Boolean saveAnimate;
6340     static int second = 0, promotionChoice = 0, dragging = 0;
6341     char promoChoice = NULLCHAR;
6342
6343     if(appData.seekGraph && appData.icsActive && loggedOn &&
6344         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6345         SeekGraphClick(clickType, xPix, yPix, 0);
6346         return;
6347     }
6348
6349     if (clickType == Press) ErrorPopDown();
6350     MarkTargetSquares(1);
6351
6352     x = EventToSquare(xPix, BOARD_WIDTH);
6353     y = EventToSquare(yPix, BOARD_HEIGHT);
6354     if (!flipView && y >= 0) {
6355         y = BOARD_HEIGHT - 1 - y;
6356     }
6357     if (flipView && x >= 0) {
6358         x = BOARD_WIDTH - 1 - x;
6359     }
6360
6361     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6362         if(clickType == Release) return; // ignore upclick of click-click destination
6363         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6364         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6365         if(gameInfo.holdingsWidth &&
6366                 (WhiteOnMove(currentMove)
6367                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6368                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6369             // click in right holdings, for determining promotion piece
6370             ChessSquare p = boards[currentMove][y][x];
6371             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6372             if(p != EmptySquare) {
6373                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6374                 fromX = fromY = -1;
6375                 return;
6376             }
6377         }
6378         DrawPosition(FALSE, boards[currentMove]);
6379         return;
6380     }
6381
6382     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6383     if(clickType == Press
6384             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6385               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6386               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6387         return;
6388
6389     autoQueen = appData.alwaysPromoteToQueen;
6390
6391     if (fromX == -1) {
6392       gatingPiece = EmptySquare;
6393       if (clickType != Press) {
6394         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6395             DragPieceEnd(xPix, yPix); dragging = 0;
6396             DrawPosition(FALSE, NULL);
6397         }
6398         return;
6399       }
6400       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6401             /* First square */
6402             if (OKToStartUserMove(x, y)) {
6403                 fromX = x;
6404                 fromY = y;
6405                 second = 0;
6406                 MarkTargetSquares(0);
6407                 DragPieceBegin(xPix, yPix); dragging = 1;
6408                 if (appData.highlightDragging) {
6409                     SetHighlights(x, y, -1, -1);
6410                 }
6411             }
6412             return;
6413         }
6414     }
6415
6416     /* fromX != -1 */
6417     if (clickType == Press && gameMode != EditPosition) {
6418         ChessSquare fromP;
6419         ChessSquare toP;
6420         int frc;
6421
6422         // ignore off-board to clicks
6423         if(y < 0 || x < 0) return;
6424
6425         /* Check if clicking again on the same color piece */
6426         fromP = boards[currentMove][fromY][fromX];
6427         toP = boards[currentMove][y][x];
6428         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6429         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6430              WhitePawn <= toP && toP <= WhiteKing &&
6431              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6432              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6433             (BlackPawn <= fromP && fromP <= BlackKing &&
6434              BlackPawn <= toP && toP <= BlackKing &&
6435              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6436              !(fromP == BlackKing && toP == BlackRook && frc))) {
6437             /* Clicked again on same color piece -- changed his mind */
6438             second = (x == fromX && y == fromY);
6439            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6440             if (appData.highlightDragging) {
6441                 SetHighlights(x, y, -1, -1);
6442             } else {
6443                 ClearHighlights();
6444             }
6445             if (OKToStartUserMove(x, y)) {
6446                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6447                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6448                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6449                  gatingPiece = boards[currentMove][fromY][fromX];
6450                 else gatingPiece = EmptySquare;
6451                 fromX = x;
6452                 fromY = y; dragging = 1;
6453                 MarkTargetSquares(0);
6454                 DragPieceBegin(xPix, yPix);
6455             }
6456            }
6457            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6458            second = FALSE; 
6459         }
6460         // ignore clicks on holdings
6461         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6462     }
6463
6464     if (clickType == Release && x == fromX && y == fromY) {
6465         DragPieceEnd(xPix, yPix); dragging = 0;
6466         if (appData.animateDragging) {
6467             /* Undo animation damage if any */
6468             DrawPosition(FALSE, NULL);
6469         }
6470         if (second) {
6471             /* Second up/down in same square; just abort move */
6472             second = 0;
6473             fromX = fromY = -1;
6474             gatingPiece = EmptySquare;
6475             ClearHighlights();
6476             gotPremove = 0;
6477             ClearPremoveHighlights();
6478         } else {
6479             /* First upclick in same square; start click-click mode */
6480             SetHighlights(x, y, -1, -1);
6481         }
6482         return;
6483     }
6484
6485     /* we now have a different from- and (possibly off-board) to-square */
6486     /* Completed move */
6487     toX = x;
6488     toY = y;
6489     saveAnimate = appData.animate;
6490     if (clickType == Press) {
6491         /* Finish clickclick move */
6492         if (appData.animate || appData.highlightLastMove) {
6493             SetHighlights(fromX, fromY, toX, toY);
6494         } else {
6495             ClearHighlights();
6496         }
6497     } else {
6498         /* Finish drag move */
6499         if (appData.highlightLastMove) {
6500             SetHighlights(fromX, fromY, toX, toY);
6501         } else {
6502             ClearHighlights();
6503         }
6504         DragPieceEnd(xPix, yPix); dragging = 0;
6505         /* Don't animate move and drag both */
6506         appData.animate = FALSE;
6507     }
6508
6509     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6510     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6511         ChessSquare piece = boards[currentMove][fromY][fromX];
6512         if(gameMode == EditPosition && piece != EmptySquare &&
6513            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6514             int n;
6515
6516             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6517                 n = PieceToNumber(piece - (int)BlackPawn);
6518                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6519                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6520                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6521             } else
6522             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6523                 n = PieceToNumber(piece);
6524                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6525                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6526                 boards[currentMove][n][BOARD_WIDTH-2]++;
6527             }
6528             boards[currentMove][fromY][fromX] = EmptySquare;
6529         }
6530         ClearHighlights();
6531         fromX = fromY = -1;
6532         DrawPosition(TRUE, boards[currentMove]);
6533         return;
6534     }
6535
6536     // off-board moves should not be highlighted
6537     if(x < 0 || y < 0) ClearHighlights();
6538
6539     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6540
6541     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6542         SetHighlights(fromX, fromY, toX, toY);
6543         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6544             // [HGM] super: promotion to captured piece selected from holdings
6545             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6546             promotionChoice = TRUE;
6547             // kludge follows to temporarily execute move on display, without promoting yet
6548             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6549             boards[currentMove][toY][toX] = p;
6550             DrawPosition(FALSE, boards[currentMove]);
6551             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6552             boards[currentMove][toY][toX] = q;
6553             DisplayMessage("Click in holdings to choose piece", "");
6554             return;
6555         }
6556         PromotionPopUp();
6557     } else {
6558         int oldMove = currentMove;
6559         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6560         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6561         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6562         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6563            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6564             DrawPosition(TRUE, boards[currentMove]);
6565         fromX = fromY = -1;
6566     }
6567     appData.animate = saveAnimate;
6568     if (appData.animate || appData.animateDragging) {
6569         /* Undo animation damage if needed */
6570         DrawPosition(FALSE, NULL);
6571     }
6572 }
6573
6574 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6575 {   // front-end-free part taken out of PieceMenuPopup
6576     int whichMenu; int xSqr, ySqr;
6577
6578     if(seekGraphUp) { // [HGM] seekgraph
6579         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6580         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6581         return -2;
6582     }
6583
6584     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6585          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6586         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6587         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6588         if(action == Press)   {
6589             originalFlip = flipView;
6590             flipView = !flipView; // temporarily flip board to see game from partners perspective
6591             DrawPosition(TRUE, partnerBoard);
6592             DisplayMessage(partnerStatus, "");
6593             partnerUp = TRUE;
6594         } else if(action == Release) {
6595             flipView = originalFlip;
6596             DrawPosition(TRUE, boards[currentMove]);
6597             partnerUp = FALSE;
6598         }
6599         return -2;
6600     }
6601
6602     xSqr = EventToSquare(x, BOARD_WIDTH);
6603     ySqr = EventToSquare(y, BOARD_HEIGHT);
6604     if (action == Release) UnLoadPV(); // [HGM] pv
6605     if (action != Press) return -2; // return code to be ignored
6606     switch (gameMode) {
6607       case IcsExamining:
6608         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6609       case EditPosition:
6610         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6611         if (xSqr < 0 || ySqr < 0) return -1;\r
6612         whichMenu = 0; // edit-position menu
6613         break;
6614       case IcsObserving:
6615         if(!appData.icsEngineAnalyze) return -1;
6616       case IcsPlayingWhite:
6617       case IcsPlayingBlack:
6618         if(!appData.zippyPlay) goto noZip;
6619       case AnalyzeMode:
6620       case AnalyzeFile:
6621       case MachinePlaysWhite:
6622       case MachinePlaysBlack:
6623       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6624         if (!appData.dropMenu) {
6625           LoadPV(x, y);
6626           return 2; // flag front-end to grab mouse events
6627         }
6628         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6629            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6630       case EditGame:
6631       noZip:
6632         if (xSqr < 0 || ySqr < 0) return -1;
6633         if (!appData.dropMenu || appData.testLegality &&
6634             gameInfo.variant != VariantBughouse &&
6635             gameInfo.variant != VariantCrazyhouse) return -1;
6636         whichMenu = 1; // drop menu
6637         break;
6638       default:
6639         return -1;
6640     }
6641
6642     if (((*fromX = xSqr) < 0) ||
6643         ((*fromY = ySqr) < 0)) {
6644         *fromX = *fromY = -1;
6645         return -1;
6646     }
6647     if (flipView)
6648       *fromX = BOARD_WIDTH - 1 - *fromX;
6649     else
6650       *fromY = BOARD_HEIGHT - 1 - *fromY;
6651
6652     return whichMenu;
6653 }
6654
6655 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6656 {
6657 //    char * hint = lastHint;
6658     FrontEndProgramStats stats;
6659
6660     stats.which = cps == &first ? 0 : 1;
6661     stats.depth = cpstats->depth;
6662     stats.nodes = cpstats->nodes;
6663     stats.score = cpstats->score;
6664     stats.time = cpstats->time;
6665     stats.pv = cpstats->movelist;
6666     stats.hint = lastHint;
6667     stats.an_move_index = 0;
6668     stats.an_move_count = 0;
6669
6670     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6671         stats.hint = cpstats->move_name;
6672         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6673         stats.an_move_count = cpstats->nr_moves;
6674     }
6675
6676     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
6677
6678     SetProgramStats( &stats );
6679 }
6680
6681 void
6682 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6683 {       // count all piece types
6684         int p, f, r;
6685         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6686         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6687         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6688                 p = board[r][f];
6689                 pCnt[p]++;
6690                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6691                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6692                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6693                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6694                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6695                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6696         }
6697 }
6698
6699 int
6700 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6701 {
6702         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6703         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6704
6705         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6706         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6707         if(myPawns == 2 && nMine == 3) // KPP
6708             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6709         if(myPawns == 1 && nMine == 2) // KP
6710             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6711         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6712             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6713         if(myPawns) return FALSE;
6714         if(pCnt[WhiteRook+side])
6715             return pCnt[BlackRook-side] ||
6716                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6717                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6718                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6719         if(pCnt[WhiteCannon+side]) {
6720             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6721             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6722         }
6723         if(pCnt[WhiteKnight+side])
6724             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6725         return FALSE;
6726 }
6727
6728 int
6729 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6730 {
6731         VariantClass v = gameInfo.variant;
6732
6733         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6734         if(v == VariantShatranj) return TRUE; // always winnable through baring
6735         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6736         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6737
6738         if(v == VariantXiangqi) {
6739                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6740
6741                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6742                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6743                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6744                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6745                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6746                 if(stale) // we have at least one last-rank P plus perhaps C
6747                     return majors // KPKX
6748                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6749                 else // KCA*E*
6750                     return pCnt[WhiteFerz+side] // KCAK
6751                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6752                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6753                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6754
6755         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6756                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6757
6758                 if(nMine == 1) return FALSE; // bare King
6759                 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
6760                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6761                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6762                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6763                 if(pCnt[WhiteKnight+side])
6764                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6765                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6766                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6767                 if(nBishops)
6768                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6769                 if(pCnt[WhiteAlfil+side])
6770                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6771                 if(pCnt[WhiteWazir+side])
6772                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6773         }
6774
6775         return TRUE;
6776 }
6777
6778 int
6779 Adjudicate(ChessProgramState *cps)
6780 {       // [HGM] some adjudications useful with buggy engines
6781         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6782         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6783         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6784         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6785         int k, count = 0; static int bare = 1;
6786         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6787         Boolean canAdjudicate = !appData.icsActive;
6788
6789         // most tests only when we understand the game, i.e. legality-checking on
6790             if( appData.testLegality )
6791             {   /* [HGM] Some more adjudications for obstinate engines */
6792                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6793                 static int moveCount = 6;
6794                 ChessMove result;
6795                 char *reason = NULL;
6796
6797                 /* Count what is on board. */
6798                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6799
6800                 /* Some material-based adjudications that have to be made before stalemate test */
6801                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6802                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6803                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6804                      if(canAdjudicate && appData.checkMates) {
6805                          if(engineOpponent)
6806                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6807                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6808                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6809                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6810                          return 1;
6811                      }
6812                 }
6813
6814                 /* Bare King in Shatranj (loses) or Losers (wins) */
6815                 if( nrW == 1 || nrB == 1) {
6816                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6817                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6818                      if(canAdjudicate && appData.checkMates) {
6819                          if(engineOpponent)
6820                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6821                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6822                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6823                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6824                          return 1;
6825                      }
6826                   } else
6827                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6828                   {    /* bare King */
6829                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6830                         if(canAdjudicate && appData.checkMates) {
6831                             /* but only adjudicate if adjudication enabled */
6832                             if(engineOpponent)
6833                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6834                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6835                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6836                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6837                             return 1;
6838                         }
6839                   }
6840                 } else bare = 1;
6841
6842
6843             // don't wait for engine to announce game end if we can judge ourselves
6844             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6845               case MT_CHECK:
6846                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6847                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6848                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6849                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6850                             checkCnt++;
6851                         if(checkCnt >= 2) {
6852                             reason = "Xboard adjudication: 3rd check";
6853                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6854                             break;
6855                         }
6856                     }
6857                 }
6858               case MT_NONE:
6859               default:
6860                 break;
6861               case MT_STALEMATE:
6862               case MT_STAINMATE:
6863                 reason = "Xboard adjudication: Stalemate";
6864                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6865                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6866                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6867                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6868                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6869                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6870                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6871                                                                         EP_CHECKMATE : EP_WINS);
6872                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6873                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6874                 }
6875                 break;
6876               case MT_CHECKMATE:
6877                 reason = "Xboard adjudication: Checkmate";
6878                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6879                 break;
6880             }
6881
6882                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6883                     case EP_STALEMATE:
6884                         result = GameIsDrawn; break;
6885                     case EP_CHECKMATE:
6886                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6887                     case EP_WINS:
6888                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6889                     default:
6890                         result = EndOfFile;
6891                 }
6892                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6893                     if(engineOpponent)
6894                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6895                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6896                     GameEnds( result, reason, GE_XBOARD );
6897                     return 1;
6898                 }
6899
6900                 /* Next absolutely insufficient mating material. */
6901                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6902                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6903                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6904
6905                      /* always flag draws, for judging claims */
6906                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6907
6908                      if(canAdjudicate && appData.materialDraws) {
6909                          /* but only adjudicate them if adjudication enabled */
6910                          if(engineOpponent) {
6911                            SendToProgram("force\n", engineOpponent); // suppress reply
6912                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6913                          }
6914                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6915                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6916                          return 1;
6917                      }
6918                 }
6919
6920                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6921                 if(gameInfo.variant == VariantXiangqi ?
6922                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6923                  : nrW + nrB == 4 &&
6924                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6925                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6926                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6927                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6928                    ) ) {
6929                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6930                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6931                           if(engineOpponent) {
6932                             SendToProgram("force\n", engineOpponent); // suppress reply
6933                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6934                           }
6935                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6936                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6937                           return 1;
6938                      }
6939                 } else moveCount = 6;
6940             }
6941         if (appData.debugMode) { int i;
6942             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6943                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6944                     appData.drawRepeats);
6945             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6946               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6947
6948         }
6949
6950         // Repetition draws and 50-move rule can be applied independently of legality testing
6951
6952                 /* Check for rep-draws */
6953                 count = 0;
6954                 for(k = forwardMostMove-2;
6955                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6956                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6957                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6958                     k-=2)
6959                 {   int rights=0;
6960                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6961                         /* compare castling rights */
6962                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6963                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6964                                 rights++; /* King lost rights, while rook still had them */
6965                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6966                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6967                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6968                                    rights++; /* but at least one rook lost them */
6969                         }
6970                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6971                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6972                                 rights++;
6973                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6974                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6975                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6976                                    rights++;
6977                         }
6978                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6979                             && appData.drawRepeats > 1) {
6980                              /* adjudicate after user-specified nr of repeats */
6981                              int result = GameIsDrawn;
6982                              char *details = "XBoard adjudication: repetition draw";
6983                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6984                                 // [HGM] xiangqi: check for forbidden perpetuals
6985                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6986                                 for(m=forwardMostMove; m>k; m-=2) {
6987                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6988                                         ourPerpetual = 0; // the current mover did not always check
6989                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6990                                         hisPerpetual = 0; // the opponent did not always check
6991                                 }
6992                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6993                                                                         ourPerpetual, hisPerpetual);
6994                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6995                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6996                                     details = "Xboard adjudication: perpetual checking";
6997                                 } else
6998                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6999                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7000                                 } else
7001                                 // Now check for perpetual chases
7002                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7003                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7004                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7005                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7006                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7007                                         details = "Xboard adjudication: perpetual chasing";
7008                                     } else
7009                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7010                                         break; // Abort repetition-checking loop.
7011                                 }
7012                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7013                              }
7014                              if(engineOpponent) {
7015                                SendToProgram("force\n", engineOpponent); // suppress reply
7016                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7017                              }
7018                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7019                              GameEnds( result, details, GE_XBOARD );
7020                              return 1;
7021                         }
7022                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7023                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7024                     }
7025                 }
7026
7027                 /* Now we test for 50-move draws. Determine ply count */
7028                 count = forwardMostMove;
7029                 /* look for last irreversble move */
7030                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7031                     count--;
7032                 /* if we hit starting position, add initial plies */
7033                 if( count == backwardMostMove )
7034                     count -= initialRulePlies;
7035                 count = forwardMostMove - count;
7036                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7037                         // adjust reversible move counter for checks in Xiangqi
7038                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7039                         if(i < backwardMostMove) i = backwardMostMove;
7040                         while(i <= forwardMostMove) {
7041                                 lastCheck = inCheck; // check evasion does not count
7042                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7043                                 if(inCheck || lastCheck) count--; // check does not count
7044                                 i++;
7045                         }
7046                 }
7047                 if( count >= 100)
7048                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7049                          /* this is used to judge if draw claims are legal */
7050                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7051                          if(engineOpponent) {
7052                            SendToProgram("force\n", engineOpponent); // suppress reply
7053                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7054                          }
7055                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7056                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7057                          return 1;
7058                 }
7059
7060                 /* if draw offer is pending, treat it as a draw claim
7061                  * when draw condition present, to allow engines a way to
7062                  * claim draws before making their move to avoid a race
7063                  * condition occurring after their move
7064                  */
7065                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7066                          char *p = NULL;
7067                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7068                              p = "Draw claim: 50-move rule";
7069                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7070                              p = "Draw claim: 3-fold repetition";
7071                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7072                              p = "Draw claim: insufficient mating material";
7073                          if( p != NULL && canAdjudicate) {
7074                              if(engineOpponent) {
7075                                SendToProgram("force\n", engineOpponent); // suppress reply
7076                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7077                              }
7078                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7079                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7080                              return 1;
7081                          }
7082                 }
7083
7084                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7085                     if(engineOpponent) {
7086                       SendToProgram("force\n", engineOpponent); // suppress reply
7087                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7088                     }
7089                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7090                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7091                     return 1;
7092                 }
7093         return 0;
7094 }
7095
7096 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7097 {   // [HGM] book: this routine intercepts moves to simulate book replies
7098     char *bookHit = NULL;
7099
7100     //first determine if the incoming move brings opponent into his book
7101     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7102         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7103     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7104     if(bookHit != NULL && !cps->bookSuspend) {
7105         // make sure opponent is not going to reply after receiving move to book position
7106         SendToProgram("force\n", cps);
7107         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7108     }
7109     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7110     // now arrange restart after book miss
7111     if(bookHit) {
7112         // after a book hit we never send 'go', and the code after the call to this routine
7113         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7114         char buf[MSG_SIZ];
7115         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7116         SendToProgram(buf, cps);
7117         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7118     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7119         SendToProgram("go\n", cps);
7120         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7121     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7122         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7123             SendToProgram("go\n", cps);
7124         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7125     }
7126     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7127 }
7128
7129 char *savedMessage;
7130 ChessProgramState *savedState;
7131 void DeferredBookMove(void)
7132 {
7133         if(savedState->lastPing != savedState->lastPong)
7134                     ScheduleDelayedEvent(DeferredBookMove, 10);
7135         else
7136         HandleMachineMove(savedMessage, savedState);
7137 }
7138
7139 void
7140 HandleMachineMove(message, cps)
7141      char *message;
7142      ChessProgramState *cps;
7143 {
7144     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7145     char realname[MSG_SIZ];
7146     int fromX, fromY, toX, toY;
7147     ChessMove moveType;
7148     char promoChar;
7149     char *p;
7150     int machineWhite;
7151     char *bookHit;
7152
7153     cps->userError = 0;
7154
7155 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7156     /*
7157      * Kludge to ignore BEL characters
7158      */
7159     while (*message == '\007') message++;
7160
7161     /*
7162      * [HGM] engine debug message: ignore lines starting with '#' character
7163      */
7164     if(cps->debug && *message == '#') return;
7165
7166     /*
7167      * Look for book output
7168      */
7169     if (cps == &first && bookRequested) {
7170         if (message[0] == '\t' || message[0] == ' ') {
7171             /* Part of the book output is here; append it */
7172             strcat(bookOutput, message);
7173             strcat(bookOutput, "  \n");
7174             return;
7175         } else if (bookOutput[0] != NULLCHAR) {
7176             /* All of book output has arrived; display it */
7177             char *p = bookOutput;
7178             while (*p != NULLCHAR) {
7179                 if (*p == '\t') *p = ' ';
7180                 p++;
7181             }
7182             DisplayInformation(bookOutput);
7183             bookRequested = FALSE;
7184             /* Fall through to parse the current output */
7185         }
7186     }
7187
7188     /*
7189      * Look for machine move.
7190      */
7191     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7192         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7193     {
7194         /* This method is only useful on engines that support ping */
7195         if (cps->lastPing != cps->lastPong) {
7196           if (gameMode == BeginningOfGame) {
7197             /* Extra move from before last new; ignore */
7198             if (appData.debugMode) {
7199                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7200             }
7201           } else {
7202             if (appData.debugMode) {
7203                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7204                         cps->which, gameMode);
7205             }
7206
7207             SendToProgram("undo\n", cps);
7208           }
7209           return;
7210         }
7211
7212         switch (gameMode) {
7213           case BeginningOfGame:
7214             /* Extra move from before last reset; ignore */
7215             if (appData.debugMode) {
7216                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7217             }
7218             return;
7219
7220           case EndOfGame:
7221           case IcsIdle:
7222           default:
7223             /* Extra move after we tried to stop.  The mode test is
7224                not a reliable way of detecting this problem, but it's
7225                the best we can do on engines that don't support ping.
7226             */
7227             if (appData.debugMode) {
7228                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7229                         cps->which, gameMode);
7230             }
7231             SendToProgram("undo\n", cps);
7232             return;
7233
7234           case MachinePlaysWhite:
7235           case IcsPlayingWhite:
7236             machineWhite = TRUE;
7237             break;
7238
7239           case MachinePlaysBlack:
7240           case IcsPlayingBlack:
7241             machineWhite = FALSE;
7242             break;
7243
7244           case TwoMachinesPlay:
7245             machineWhite = (cps->twoMachinesColor[0] == 'w');
7246             break;
7247         }
7248         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7249             if (appData.debugMode) {
7250                 fprintf(debugFP,
7251                         "Ignoring move out of turn by %s, gameMode %d"
7252                         ", forwardMost %d\n",
7253                         cps->which, gameMode, forwardMostMove);
7254             }
7255             return;
7256         }
7257
7258     if (appData.debugMode) { int f = forwardMostMove;
7259         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7260                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7261                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7262     }
7263         if(cps->alphaRank) AlphaRank(machineMove, 4);
7264         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7265                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7266             /* Machine move could not be parsed; ignore it. */
7267           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7268                     machineMove, cps->which);
7269             DisplayError(buf1, 0);
7270             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7271                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7272             if (gameMode == TwoMachinesPlay) {
7273               GameEnds(machineWhite ? BlackWins : WhiteWins,
7274                        buf1, GE_XBOARD);
7275             }
7276             return;
7277         }
7278
7279         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7280         /* So we have to redo legality test with true e.p. status here,  */
7281         /* to make sure an illegal e.p. capture does not slip through,   */
7282         /* to cause a forfeit on a justified illegal-move complaint      */
7283         /* of the opponent.                                              */
7284         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7285            ChessMove moveType;
7286            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7287                              fromY, fromX, toY, toX, promoChar);
7288             if (appData.debugMode) {
7289                 int i;
7290                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7291                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7292                 fprintf(debugFP, "castling rights\n");
7293             }
7294             if(moveType == IllegalMove) {
7295               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7296                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7297                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7298                            buf1, GE_XBOARD);
7299                 return;
7300            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7301            /* [HGM] Kludge to handle engines that send FRC-style castling
7302               when they shouldn't (like TSCP-Gothic) */
7303            switch(moveType) {
7304              case WhiteASideCastleFR:
7305              case BlackASideCastleFR:
7306                toX+=2;
7307                currentMoveString[2]++;
7308                break;
7309              case WhiteHSideCastleFR:
7310              case BlackHSideCastleFR:
7311                toX--;
7312                currentMoveString[2]--;
7313                break;
7314              default: ; // nothing to do, but suppresses warning of pedantic compilers
7315            }
7316         }
7317         hintRequested = FALSE;
7318         lastHint[0] = NULLCHAR;
7319         bookRequested = FALSE;
7320         /* Program may be pondering now */
7321         cps->maybeThinking = TRUE;
7322         if (cps->sendTime == 2) cps->sendTime = 1;
7323         if (cps->offeredDraw) cps->offeredDraw--;
7324
7325         /* currentMoveString is set as a side-effect of ParseOneMove */
7326         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7327         strcat(machineMove, "\n");
7328         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7329
7330         /* [AS] Save move info*/
7331         pvInfoList[ forwardMostMove ].score = programStats.score;
7332         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7333         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7334
7335         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7336
7337         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7338         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7339             int count = 0;
7340
7341             while( count < adjudicateLossPlies ) {
7342                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7343
7344                 if( count & 1 ) {
7345                     score = -score; /* Flip score for winning side */
7346                 }
7347
7348                 if( score > adjudicateLossThreshold ) {
7349                     break;
7350                 }
7351
7352                 count++;
7353             }
7354
7355             if( count >= adjudicateLossPlies ) {
7356                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7357
7358                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7359                     "Xboard adjudication",
7360                     GE_XBOARD );
7361
7362                 return;
7363             }
7364         }
7365
7366         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7367
7368 #if ZIPPY
7369         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7370             first.initDone) {
7371           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7372                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7373                 SendToICS("draw ");
7374                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7375           }
7376           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7377           ics_user_moved = 1;
7378           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7379                 char buf[3*MSG_SIZ];
7380
7381                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7382                         programStats.score / 100.,
7383                         programStats.depth,
7384                         programStats.time / 100.,
7385                         (unsigned int)programStats.nodes,
7386                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7387                         programStats.movelist);
7388                 SendToICS(buf);
7389 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7390           }
7391         }
7392 #endif
7393
7394         /* [AS] Clear stats for next move */
7395         ClearProgramStats();
7396         thinkOutput[0] = NULLCHAR;
7397         hiddenThinkOutputState = 0;
7398
7399         bookHit = NULL;
7400         if (gameMode == TwoMachinesPlay) {
7401             /* [HGM] relaying draw offers moved to after reception of move */
7402             /* and interpreting offer as claim if it brings draw condition */
7403             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7404                 SendToProgram("draw\n", cps->other);
7405             }
7406             if (cps->other->sendTime) {
7407                 SendTimeRemaining(cps->other,
7408                                   cps->other->twoMachinesColor[0] == 'w');
7409             }
7410             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7411             if (firstMove && !bookHit) {
7412                 firstMove = FALSE;
7413                 if (cps->other->useColors) {
7414                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7415                 }
7416                 SendToProgram("go\n", cps->other);
7417             }
7418             cps->other->maybeThinking = TRUE;
7419         }
7420
7421         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7422
7423         if (!pausing && appData.ringBellAfterMoves) {
7424             RingBell();
7425         }
7426
7427         /*
7428          * Reenable menu items that were disabled while
7429          * machine was thinking
7430          */
7431         if (gameMode != TwoMachinesPlay)
7432             SetUserThinkingEnables();
7433
7434         // [HGM] book: after book hit opponent has received move and is now in force mode
7435         // force the book reply into it, and then fake that it outputted this move by jumping
7436         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7437         if(bookHit) {
7438                 static char bookMove[MSG_SIZ]; // a bit generous?
7439
7440                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7441                 strcat(bookMove, bookHit);
7442                 message = bookMove;
7443                 cps = cps->other;
7444                 programStats.nodes = programStats.depth = programStats.time =
7445                 programStats.score = programStats.got_only_move = 0;
7446                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7447
7448                 if(cps->lastPing != cps->lastPong) {
7449                     savedMessage = message; // args for deferred call
7450                     savedState = cps;
7451                     ScheduleDelayedEvent(DeferredBookMove, 10);
7452                     return;
7453                 }
7454                 goto FakeBookMove;
7455         }
7456
7457         return;
7458     }
7459
7460     /* Set special modes for chess engines.  Later something general
7461      *  could be added here; for now there is just one kludge feature,
7462      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7463      *  when "xboard" is given as an interactive command.
7464      */
7465     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7466         cps->useSigint = FALSE;
7467         cps->useSigterm = FALSE;
7468     }
7469     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7470       ParseFeatures(message+8, cps);
7471       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7472     }
7473
7474     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7475       int dummy, s=6; char buf[MSG_SIZ];
7476       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7477       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7478       ParseFEN(boards[0], &dummy, message+s);
7479       DrawPosition(TRUE, boards[0]);
7480       startedFromSetupPosition = TRUE;
7481       return;
7482     }
7483     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7484      * want this, I was asked to put it in, and obliged.
7485      */
7486     if (!strncmp(message, "setboard ", 9)) {
7487         Board initial_position;
7488
7489         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7490
7491         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7492             DisplayError(_("Bad FEN received from engine"), 0);
7493             return ;
7494         } else {
7495            Reset(TRUE, FALSE);
7496            CopyBoard(boards[0], initial_position);
7497            initialRulePlies = FENrulePlies;
7498            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7499            else gameMode = MachinePlaysBlack;
7500            DrawPosition(FALSE, boards[currentMove]);
7501         }
7502         return;
7503     }
7504
7505     /*
7506      * Look for communication commands
7507      */
7508     if (!strncmp(message, "telluser ", 9)) {
7509         if(message[9] == '\\' && message[10] == '\\')
7510             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7511         DisplayNote(message + 9);
7512         return;
7513     }
7514     if (!strncmp(message, "tellusererror ", 14)) {
7515         cps->userError = 1;
7516         if(message[14] == '\\' && message[15] == '\\')
7517             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7518         DisplayError(message + 14, 0);
7519         return;
7520     }
7521     if (!strncmp(message, "tellopponent ", 13)) {
7522       if (appData.icsActive) {
7523         if (loggedOn) {
7524           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7525           SendToICS(buf1);
7526         }
7527       } else {
7528         DisplayNote(message + 13);
7529       }
7530       return;
7531     }
7532     if (!strncmp(message, "tellothers ", 11)) {
7533       if (appData.icsActive) {
7534         if (loggedOn) {
7535           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7536           SendToICS(buf1);
7537         }
7538       }
7539       return;
7540     }
7541     if (!strncmp(message, "tellall ", 8)) {
7542       if (appData.icsActive) {
7543         if (loggedOn) {
7544           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7545           SendToICS(buf1);
7546         }
7547       } else {
7548         DisplayNote(message + 8);
7549       }
7550       return;
7551     }
7552     if (strncmp(message, "warning", 7) == 0) {
7553         /* Undocumented feature, use tellusererror in new code */
7554         DisplayError(message, 0);
7555         return;
7556     }
7557     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7558         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7559         strcat(realname, " query");
7560         AskQuestion(realname, buf2, buf1, cps->pr);
7561         return;
7562     }
7563     /* Commands from the engine directly to ICS.  We don't allow these to be
7564      *  sent until we are logged on. Crafty kibitzes have been known to
7565      *  interfere with the login process.
7566      */
7567     if (loggedOn) {
7568         if (!strncmp(message, "tellics ", 8)) {
7569             SendToICS(message + 8);
7570             SendToICS("\n");
7571             return;
7572         }
7573         if (!strncmp(message, "tellicsnoalias ", 15)) {
7574             SendToICS(ics_prefix);
7575             SendToICS(message + 15);
7576             SendToICS("\n");
7577             return;
7578         }
7579         /* The following are for backward compatibility only */
7580         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7581             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7582             SendToICS(ics_prefix);
7583             SendToICS(message);
7584             SendToICS("\n");
7585             return;
7586         }
7587     }
7588     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7589         return;
7590     }
7591     /*
7592      * If the move is illegal, cancel it and redraw the board.
7593      * Also deal with other error cases.  Matching is rather loose
7594      * here to accommodate engines written before the spec.
7595      */
7596     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7597         strncmp(message, "Error", 5) == 0) {
7598         if (StrStr(message, "name") ||
7599             StrStr(message, "rating") || StrStr(message, "?") ||
7600             StrStr(message, "result") || StrStr(message, "board") ||
7601             StrStr(message, "bk") || StrStr(message, "computer") ||
7602             StrStr(message, "variant") || StrStr(message, "hint") ||
7603             StrStr(message, "random") || StrStr(message, "depth") ||
7604             StrStr(message, "accepted")) {
7605             return;
7606         }
7607         if (StrStr(message, "protover")) {
7608           /* Program is responding to input, so it's apparently done
7609              initializing, and this error message indicates it is
7610              protocol version 1.  So we don't need to wait any longer
7611              for it to initialize and send feature commands. */
7612           FeatureDone(cps, 1);
7613           cps->protocolVersion = 1;
7614           return;
7615         }
7616         cps->maybeThinking = FALSE;
7617
7618         if (StrStr(message, "draw")) {
7619             /* Program doesn't have "draw" command */
7620             cps->sendDrawOffers = 0;
7621             return;
7622         }
7623         if (cps->sendTime != 1 &&
7624             (StrStr(message, "time") || StrStr(message, "otim"))) {
7625           /* Program apparently doesn't have "time" or "otim" command */
7626           cps->sendTime = 0;
7627           return;
7628         }
7629         if (StrStr(message, "analyze")) {
7630             cps->analysisSupport = FALSE;
7631             cps->analyzing = FALSE;
7632             Reset(FALSE, TRUE);
7633             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7634             DisplayError(buf2, 0);
7635             return;
7636         }
7637         if (StrStr(message, "(no matching move)st")) {
7638           /* Special kludge for GNU Chess 4 only */
7639           cps->stKludge = TRUE;
7640           SendTimeControl(cps, movesPerSession, timeControl,
7641                           timeIncrement, appData.searchDepth,
7642                           searchTime);
7643           return;
7644         }
7645         if (StrStr(message, "(no matching move)sd")) {
7646           /* Special kludge for GNU Chess 4 only */
7647           cps->sdKludge = TRUE;
7648           SendTimeControl(cps, movesPerSession, timeControl,
7649                           timeIncrement, appData.searchDepth,
7650                           searchTime);
7651           return;
7652         }
7653         if (!StrStr(message, "llegal")) {
7654             return;
7655         }
7656         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7657             gameMode == IcsIdle) return;
7658         if (forwardMostMove <= backwardMostMove) return;
7659         if (pausing) PauseEvent();
7660       if(appData.forceIllegal) {
7661             // [HGM] illegal: machine refused move; force position after move into it
7662           SendToProgram("force\n", cps);
7663           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7664                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7665                 // when black is to move, while there might be nothing on a2 or black
7666                 // might already have the move. So send the board as if white has the move.
7667                 // But first we must change the stm of the engine, as it refused the last move
7668                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7669                 if(WhiteOnMove(forwardMostMove)) {
7670                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7671                     SendBoard(cps, forwardMostMove); // kludgeless board
7672                 } else {
7673                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7674                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7675                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7676                 }
7677           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7678             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7679                  gameMode == TwoMachinesPlay)
7680               SendToProgram("go\n", cps);
7681             return;
7682       } else
7683         if (gameMode == PlayFromGameFile) {
7684             /* Stop reading this game file */
7685             gameMode = EditGame;
7686             ModeHighlight();
7687         }
7688         currentMove = forwardMostMove-1;
7689         DisplayMove(currentMove-1); /* before DisplayMoveError */
7690         SwitchClocks(forwardMostMove-1); // [HGM] race
7691         DisplayBothClocks();
7692         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7693                 parseList[currentMove], cps->which);
7694         DisplayMoveError(buf1);
7695         DrawPosition(FALSE, boards[currentMove]);
7696
7697         /* [HGM] illegal-move claim should forfeit game when Xboard */
7698         /* only passes fully legal moves                            */
7699         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7700             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7701                                 "False illegal-move claim", GE_XBOARD );
7702         }
7703         return;
7704     }
7705     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7706         /* Program has a broken "time" command that
7707            outputs a string not ending in newline.
7708            Don't use it. */
7709         cps->sendTime = 0;
7710     }
7711
7712     /*
7713      * If chess program startup fails, exit with an error message.
7714      * Attempts to recover here are futile.
7715      */
7716     if ((StrStr(message, "unknown host") != NULL)
7717         || (StrStr(message, "No remote directory") != NULL)
7718         || (StrStr(message, "not found") != NULL)
7719         || (StrStr(message, "No such file") != NULL)
7720         || (StrStr(message, "can't alloc") != NULL)
7721         || (StrStr(message, "Permission denied") != NULL)) {
7722
7723         cps->maybeThinking = FALSE;
7724         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7725                 cps->which, cps->program, cps->host, message);
7726         RemoveInputSource(cps->isr);
7727         DisplayFatalError(buf1, 0, 1);
7728         return;
7729     }
7730
7731     /*
7732      * Look for hint output
7733      */
7734     if (sscanf(message, "Hint: %s", buf1) == 1) {
7735         if (cps == &first && hintRequested) {
7736             hintRequested = FALSE;
7737             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7738                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7739                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7740                                     PosFlags(forwardMostMove),
7741                                     fromY, fromX, toY, toX, promoChar, buf1);
7742                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7743                 DisplayInformation(buf2);
7744             } else {
7745                 /* Hint move could not be parsed!? */
7746               snprintf(buf2, sizeof(buf2),
7747                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7748                         buf1, cps->which);
7749                 DisplayError(buf2, 0);
7750             }
7751         } else {
7752           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7753         }
7754         return;
7755     }
7756
7757     /*
7758      * Ignore other messages if game is not in progress
7759      */
7760     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7761         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7762
7763     /*
7764      * look for win, lose, draw, or draw offer
7765      */
7766     if (strncmp(message, "1-0", 3) == 0) {
7767         char *p, *q, *r = "";
7768         p = strchr(message, '{');
7769         if (p) {
7770             q = strchr(p, '}');
7771             if (q) {
7772                 *q = NULLCHAR;
7773                 r = p + 1;
7774             }
7775         }
7776         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7777         return;
7778     } else if (strncmp(message, "0-1", 3) == 0) {
7779         char *p, *q, *r = "";
7780         p = strchr(message, '{');
7781         if (p) {
7782             q = strchr(p, '}');
7783             if (q) {
7784                 *q = NULLCHAR;
7785                 r = p + 1;
7786             }
7787         }
7788         /* Kludge for Arasan 4.1 bug */
7789         if (strcmp(r, "Black resigns") == 0) {
7790             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7791             return;
7792         }
7793         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7794         return;
7795     } else if (strncmp(message, "1/2", 3) == 0) {
7796         char *p, *q, *r = "";
7797         p = strchr(message, '{');
7798         if (p) {
7799             q = strchr(p, '}');
7800             if (q) {
7801                 *q = NULLCHAR;
7802                 r = p + 1;
7803             }
7804         }
7805
7806         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7807         return;
7808
7809     } else if (strncmp(message, "White resign", 12) == 0) {
7810         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7811         return;
7812     } else if (strncmp(message, "Black resign", 12) == 0) {
7813         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7814         return;
7815     } else if (strncmp(message, "White matches", 13) == 0 ||
7816                strncmp(message, "Black matches", 13) == 0   ) {
7817         /* [HGM] ignore GNUShogi noises */
7818         return;
7819     } else if (strncmp(message, "White", 5) == 0 &&
7820                message[5] != '(' &&
7821                StrStr(message, "Black") == NULL) {
7822         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7823         return;
7824     } else if (strncmp(message, "Black", 5) == 0 &&
7825                message[5] != '(') {
7826         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7827         return;
7828     } else if (strcmp(message, "resign") == 0 ||
7829                strcmp(message, "computer resigns") == 0) {
7830         switch (gameMode) {
7831           case MachinePlaysBlack:
7832           case IcsPlayingBlack:
7833             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7834             break;
7835           case MachinePlaysWhite:
7836           case IcsPlayingWhite:
7837             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7838             break;
7839           case TwoMachinesPlay:
7840             if (cps->twoMachinesColor[0] == 'w')
7841               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7842             else
7843               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7844             break;
7845           default:
7846             /* can't happen */
7847             break;
7848         }
7849         return;
7850     } else if (strncmp(message, "opponent mates", 14) == 0) {
7851         switch (gameMode) {
7852           case MachinePlaysBlack:
7853           case IcsPlayingBlack:
7854             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7855             break;
7856           case MachinePlaysWhite:
7857           case IcsPlayingWhite:
7858             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7859             break;
7860           case TwoMachinesPlay:
7861             if (cps->twoMachinesColor[0] == 'w')
7862               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7863             else
7864               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7865             break;
7866           default:
7867             /* can't happen */
7868             break;
7869         }
7870         return;
7871     } else if (strncmp(message, "computer mates", 14) == 0) {
7872         switch (gameMode) {
7873           case MachinePlaysBlack:
7874           case IcsPlayingBlack:
7875             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7876             break;
7877           case MachinePlaysWhite:
7878           case IcsPlayingWhite:
7879             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7880             break;
7881           case TwoMachinesPlay:
7882             if (cps->twoMachinesColor[0] == 'w')
7883               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7884             else
7885               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7886             break;
7887           default:
7888             /* can't happen */
7889             break;
7890         }
7891         return;
7892     } else if (strncmp(message, "checkmate", 9) == 0) {
7893         if (WhiteOnMove(forwardMostMove)) {
7894             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7895         } else {
7896             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7897         }
7898         return;
7899     } else if (strstr(message, "Draw") != NULL ||
7900                strstr(message, "game is a draw") != NULL) {
7901         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7902         return;
7903     } else if (strstr(message, "offer") != NULL &&
7904                strstr(message, "draw") != NULL) {
7905 #if ZIPPY
7906         if (appData.zippyPlay && first.initDone) {
7907             /* Relay offer to ICS */
7908             SendToICS(ics_prefix);
7909             SendToICS("draw\n");
7910         }
7911 #endif
7912         cps->offeredDraw = 2; /* valid until this engine moves twice */
7913         if (gameMode == TwoMachinesPlay) {
7914             if (cps->other->offeredDraw) {
7915                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7916             /* [HGM] in two-machine mode we delay relaying draw offer      */
7917             /* until after we also have move, to see if it is really claim */
7918             }
7919         } else if (gameMode == MachinePlaysWhite ||
7920                    gameMode == MachinePlaysBlack) {
7921           if (userOfferedDraw) {
7922             DisplayInformation(_("Machine accepts your draw offer"));
7923             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7924           } else {
7925             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7926           }
7927         }
7928     }
7929
7930
7931     /*
7932      * Look for thinking output
7933      */
7934     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7935           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7936                                 ) {
7937         int plylev, mvleft, mvtot, curscore, time;
7938         char mvname[MOVE_LEN];
7939         u64 nodes; // [DM]
7940         char plyext;
7941         int ignore = FALSE;
7942         int prefixHint = FALSE;
7943         mvname[0] = NULLCHAR;
7944
7945         switch (gameMode) {
7946           case MachinePlaysBlack:
7947           case IcsPlayingBlack:
7948             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7949             break;
7950           case MachinePlaysWhite:
7951           case IcsPlayingWhite:
7952             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7953             break;
7954           case AnalyzeMode:
7955           case AnalyzeFile:
7956             break;
7957           case IcsObserving: /* [DM] icsEngineAnalyze */
7958             if (!appData.icsEngineAnalyze) ignore = TRUE;
7959             break;
7960           case TwoMachinesPlay:
7961             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7962                 ignore = TRUE;
7963             }
7964             break;
7965           default:
7966             ignore = TRUE;
7967             break;
7968         }
7969
7970         if (!ignore) {
7971             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7972             buf1[0] = NULLCHAR;
7973             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7974                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7975
7976                 if (plyext != ' ' && plyext != '\t') {
7977                     time *= 100;
7978                 }
7979
7980                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7981                 if( cps->scoreIsAbsolute &&
7982                     ( gameMode == MachinePlaysBlack ||
7983                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7984                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7985                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7986                      !WhiteOnMove(currentMove)
7987                     ) )
7988                 {
7989                     curscore = -curscore;
7990                 }
7991
7992
7993                 tempStats.depth = plylev;
7994                 tempStats.nodes = nodes;
7995                 tempStats.time = time;
7996                 tempStats.score = curscore;
7997                 tempStats.got_only_move = 0;
7998
7999                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8000                         int ticklen;
8001
8002                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8003                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8004                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8005                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8006                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8007                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8008                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8009                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8010                 }
8011
8012                 /* Buffer overflow protection */
8013                 if (buf1[0] != NULLCHAR) {
8014                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8015                         && appData.debugMode) {
8016                         fprintf(debugFP,
8017                                 "PV is too long; using the first %u bytes.\n",
8018                                 (unsigned) sizeof(tempStats.movelist) - 1);
8019                     }
8020
8021                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8022                 } else {
8023                     sprintf(tempStats.movelist, " no PV\n");
8024                 }
8025
8026                 if (tempStats.seen_stat) {
8027                     tempStats.ok_to_send = 1;
8028                 }
8029
8030                 if (strchr(tempStats.movelist, '(') != NULL) {
8031                     tempStats.line_is_book = 1;
8032                     tempStats.nr_moves = 0;
8033                     tempStats.moves_left = 0;
8034                 } else {
8035                     tempStats.line_is_book = 0;
8036                 }
8037
8038                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8039                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8040
8041                 SendProgramStatsToFrontend( cps, &tempStats );
8042
8043                 /*
8044                     [AS] Protect the thinkOutput buffer from overflow... this
8045                     is only useful if buf1 hasn't overflowed first!
8046                 */
8047                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8048                          plylev,
8049                          (gameMode == TwoMachinesPlay ?
8050                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8051                          ((double) curscore) / 100.0,
8052                          prefixHint ? lastHint : "",
8053                          prefixHint ? " " : "" );
8054
8055                 if( buf1[0] != NULLCHAR ) {
8056                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8057
8058                     if( strlen(buf1) > max_len ) {
8059                         if( appData.debugMode) {
8060                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8061                         }
8062                         buf1[max_len+1] = '\0';
8063                     }
8064
8065                     strcat( thinkOutput, buf1 );
8066                 }
8067
8068                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8069                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8070                     DisplayMove(currentMove - 1);
8071                 }
8072                 return;
8073
8074             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8075                 /* crafty (9.25+) says "(only move) <move>"
8076                  * if there is only 1 legal move
8077                  */
8078                 sscanf(p, "(only move) %s", buf1);
8079                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8080                 sprintf(programStats.movelist, "%s (only move)", buf1);
8081                 programStats.depth = 1;
8082                 programStats.nr_moves = 1;
8083                 programStats.moves_left = 1;
8084                 programStats.nodes = 1;
8085                 programStats.time = 1;
8086                 programStats.got_only_move = 1;
8087
8088                 /* Not really, but we also use this member to
8089                    mean "line isn't going to change" (Crafty
8090                    isn't searching, so stats won't change) */
8091                 programStats.line_is_book = 1;
8092
8093                 SendProgramStatsToFrontend( cps, &programStats );
8094
8095                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8096                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8097                     DisplayMove(currentMove - 1);
8098                 }
8099                 return;
8100             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8101                               &time, &nodes, &plylev, &mvleft,
8102                               &mvtot, mvname) >= 5) {
8103                 /* The stat01: line is from Crafty (9.29+) in response
8104                    to the "." command */
8105                 programStats.seen_stat = 1;
8106                 cps->maybeThinking = TRUE;
8107
8108                 if (programStats.got_only_move || !appData.periodicUpdates)
8109                   return;
8110
8111                 programStats.depth = plylev;
8112                 programStats.time = time;
8113                 programStats.nodes = nodes;
8114                 programStats.moves_left = mvleft;
8115                 programStats.nr_moves = mvtot;
8116                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8117                 programStats.ok_to_send = 1;
8118                 programStats.movelist[0] = '\0';
8119
8120                 SendProgramStatsToFrontend( cps, &programStats );
8121
8122                 return;
8123
8124             } else if (strncmp(message,"++",2) == 0) {
8125                 /* Crafty 9.29+ outputs this */
8126                 programStats.got_fail = 2;
8127                 return;
8128
8129             } else if (strncmp(message,"--",2) == 0) {
8130                 /* Crafty 9.29+ outputs this */
8131                 programStats.got_fail = 1;
8132                 return;
8133
8134             } else if (thinkOutput[0] != NULLCHAR &&
8135                        strncmp(message, "    ", 4) == 0) {
8136                 unsigned message_len;
8137
8138                 p = message;
8139                 while (*p && *p == ' ') p++;
8140
8141                 message_len = strlen( p );
8142
8143                 /* [AS] Avoid buffer overflow */
8144                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8145                     strcat(thinkOutput, " ");
8146                     strcat(thinkOutput, p);
8147                 }
8148
8149                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8150                     strcat(programStats.movelist, " ");
8151                     strcat(programStats.movelist, p);
8152                 }
8153
8154                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8155                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8156                     DisplayMove(currentMove - 1);
8157                 }
8158                 return;
8159             }
8160         }
8161         else {
8162             buf1[0] = NULLCHAR;
8163
8164             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8165                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8166             {
8167                 ChessProgramStats cpstats;
8168
8169                 if (plyext != ' ' && plyext != '\t') {
8170                     time *= 100;
8171                 }
8172
8173                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8174                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8175                     curscore = -curscore;
8176                 }
8177
8178                 cpstats.depth = plylev;
8179                 cpstats.nodes = nodes;
8180                 cpstats.time = time;
8181                 cpstats.score = curscore;
8182                 cpstats.got_only_move = 0;
8183                 cpstats.movelist[0] = '\0';
8184
8185                 if (buf1[0] != NULLCHAR) {
8186                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8187                 }
8188
8189                 cpstats.ok_to_send = 0;
8190                 cpstats.line_is_book = 0;
8191                 cpstats.nr_moves = 0;
8192                 cpstats.moves_left = 0;
8193
8194                 SendProgramStatsToFrontend( cps, &cpstats );
8195             }
8196         }
8197     }
8198 }
8199
8200
8201 /* Parse a game score from the character string "game", and
8202    record it as the history of the current game.  The game
8203    score is NOT assumed to start from the standard position.
8204    The display is not updated in any way.
8205    */
8206 void
8207 ParseGameHistory(game)
8208      char *game;
8209 {
8210     ChessMove moveType;
8211     int fromX, fromY, toX, toY, boardIndex;
8212     char promoChar;
8213     char *p, *q;
8214     char buf[MSG_SIZ];
8215
8216     if (appData.debugMode)
8217       fprintf(debugFP, "Parsing game history: %s\n", game);
8218
8219     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8220     gameInfo.site = StrSave(appData.icsHost);
8221     gameInfo.date = PGNDate();
8222     gameInfo.round = StrSave("-");
8223
8224     /* Parse out names of players */
8225     while (*game == ' ') game++;
8226     p = buf;
8227     while (*game != ' ') *p++ = *game++;
8228     *p = NULLCHAR;
8229     gameInfo.white = StrSave(buf);
8230     while (*game == ' ') game++;
8231     p = buf;
8232     while (*game != ' ' && *game != '\n') *p++ = *game++;
8233     *p = NULLCHAR;
8234     gameInfo.black = StrSave(buf);
8235
8236     /* Parse moves */
8237     boardIndex = blackPlaysFirst ? 1 : 0;
8238     yynewstr(game);
8239     for (;;) {
8240         yyboardindex = boardIndex;
8241         moveType = (ChessMove) Myylex();
8242         switch (moveType) {
8243           case IllegalMove:             /* maybe suicide chess, etc. */
8244   if (appData.debugMode) {
8245     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8246     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8247     setbuf(debugFP, NULL);
8248   }
8249           case WhitePromotion:
8250           case BlackPromotion:
8251           case WhiteNonPromotion:
8252           case BlackNonPromotion:
8253           case NormalMove:
8254           case WhiteCapturesEnPassant:
8255           case BlackCapturesEnPassant:
8256           case WhiteKingSideCastle:
8257           case WhiteQueenSideCastle:
8258           case BlackKingSideCastle:
8259           case BlackQueenSideCastle:
8260           case WhiteKingSideCastleWild:
8261           case WhiteQueenSideCastleWild:
8262           case BlackKingSideCastleWild:
8263           case BlackQueenSideCastleWild:
8264           /* PUSH Fabien */
8265           case WhiteHSideCastleFR:
8266           case WhiteASideCastleFR:
8267           case BlackHSideCastleFR:
8268           case BlackASideCastleFR:
8269           /* POP Fabien */
8270             fromX = currentMoveString[0] - AAA;
8271             fromY = currentMoveString[1] - ONE;
8272             toX = currentMoveString[2] - AAA;
8273             toY = currentMoveString[3] - ONE;
8274             promoChar = currentMoveString[4];
8275             break;
8276           case WhiteDrop:
8277           case BlackDrop:
8278             fromX = moveType == WhiteDrop ?
8279               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8280             (int) CharToPiece(ToLower(currentMoveString[0]));
8281             fromY = DROP_RANK;
8282             toX = currentMoveString[2] - AAA;
8283             toY = currentMoveString[3] - ONE;
8284             promoChar = NULLCHAR;
8285             break;
8286           case AmbiguousMove:
8287             /* bug? */
8288             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8289   if (appData.debugMode) {
8290     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8291     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8292     setbuf(debugFP, NULL);
8293   }
8294             DisplayError(buf, 0);
8295             return;
8296           case ImpossibleMove:
8297             /* bug? */
8298             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8299   if (appData.debugMode) {
8300     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8301     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8302     setbuf(debugFP, NULL);
8303   }
8304             DisplayError(buf, 0);
8305             return;
8306           case EndOfFile:
8307             if (boardIndex < backwardMostMove) {
8308                 /* Oops, gap.  How did that happen? */
8309                 DisplayError(_("Gap in move list"), 0);
8310                 return;
8311             }
8312             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8313             if (boardIndex > forwardMostMove) {
8314                 forwardMostMove = boardIndex;
8315             }
8316             return;
8317           case ElapsedTime:
8318             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8319                 strcat(parseList[boardIndex-1], " ");
8320                 strcat(parseList[boardIndex-1], yy_text);
8321             }
8322             continue;
8323           case Comment:
8324           case PGNTag:
8325           case NAG:
8326           default:
8327             /* ignore */
8328             continue;
8329           case WhiteWins:
8330           case BlackWins:
8331           case GameIsDrawn:
8332           case GameUnfinished:
8333             if (gameMode == IcsExamining) {
8334                 if (boardIndex < backwardMostMove) {
8335                     /* Oops, gap.  How did that happen? */
8336                     return;
8337                 }
8338                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8339                 return;
8340             }
8341             gameInfo.result = moveType;
8342             p = strchr(yy_text, '{');
8343             if (p == NULL) p = strchr(yy_text, '(');
8344             if (p == NULL) {
8345                 p = yy_text;
8346                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8347             } else {
8348                 q = strchr(p, *p == '{' ? '}' : ')');
8349                 if (q != NULL) *q = NULLCHAR;
8350                 p++;
8351             }
8352             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8353             gameInfo.resultDetails = StrSave(p);
8354             continue;
8355         }
8356         if (boardIndex >= forwardMostMove &&
8357             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8358             backwardMostMove = blackPlaysFirst ? 1 : 0;
8359             return;
8360         }
8361         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8362                                  fromY, fromX, toY, toX, promoChar,
8363                                  parseList[boardIndex]);
8364         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8365         /* currentMoveString is set as a side-effect of yylex */
8366         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8367         strcat(moveList[boardIndex], "\n");
8368         boardIndex++;
8369         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8370         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8371           case MT_NONE:
8372           case MT_STALEMATE:
8373           default:
8374             break;
8375           case MT_CHECK:
8376             if(gameInfo.variant != VariantShogi)
8377                 strcat(parseList[boardIndex - 1], "+");
8378             break;
8379           case MT_CHECKMATE:
8380           case MT_STAINMATE:
8381             strcat(parseList[boardIndex - 1], "#");
8382             break;
8383         }
8384     }
8385 }
8386
8387
8388 /* Apply a move to the given board  */
8389 void
8390 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8391      int fromX, fromY, toX, toY;
8392      int promoChar;
8393      Board board;
8394 {
8395   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8396   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8397
8398     /* [HGM] compute & store e.p. status and castling rights for new position */
8399     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8400
8401       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8402       oldEP = (signed char)board[EP_STATUS];
8403       board[EP_STATUS] = EP_NONE;
8404
8405       if( board[toY][toX] != EmptySquare )
8406            board[EP_STATUS] = EP_CAPTURE;
8407
8408   if (fromY == DROP_RANK) {
8409         /* must be first */
8410         piece = board[toY][toX] = (ChessSquare) fromX;
8411   } else {
8412       int i;
8413
8414       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8415            if( gameInfo.variant == VariantFairy ) board[EP_STATUS] = EP_PAWN_MOVE; // Lance in fairy is Pawn-like
8416       } else
8417       if( board[fromY][fromX] == WhitePawn ) {
8418            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8419                board[EP_STATUS] = EP_PAWN_MOVE;
8420            if( toY-fromY==2) {
8421                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8422                         gameInfo.variant != VariantBerolina || toX < fromX)
8423                       board[EP_STATUS] = toX | berolina;
8424                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8425                         gameInfo.variant != VariantBerolina || toX > fromX)
8426                       board[EP_STATUS] = toX;
8427            }
8428       } else
8429       if( board[fromY][fromX] == BlackPawn ) {
8430            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8431                board[EP_STATUS] = EP_PAWN_MOVE;
8432            if( toY-fromY== -2) {
8433                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8434                         gameInfo.variant != VariantBerolina || toX < fromX)
8435                       board[EP_STATUS] = toX | berolina;
8436                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8437                         gameInfo.variant != VariantBerolina || toX > fromX)
8438                       board[EP_STATUS] = toX;
8439            }
8440        }
8441
8442        for(i=0; i<nrCastlingRights; i++) {
8443            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8444               board[CASTLING][i] == toX   && castlingRank[i] == toY
8445              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8446        }
8447
8448      if (fromX == toX && fromY == toY) return;
8449
8450      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8451      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8452      if(gameInfo.variant == VariantKnightmate)
8453          king += (int) WhiteUnicorn - (int) WhiteKing;
8454
8455     /* Code added by Tord: */
8456     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8457     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8458         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8459       board[fromY][fromX] = EmptySquare;
8460       board[toY][toX] = EmptySquare;
8461       if((toX > fromX) != (piece == WhiteRook)) {
8462         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8463       } else {
8464         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8465       }
8466     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8467                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8468       board[fromY][fromX] = EmptySquare;
8469       board[toY][toX] = EmptySquare;
8470       if((toX > fromX) != (piece == BlackRook)) {
8471         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8472       } else {
8473         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8474       }
8475     /* End of code added by Tord */
8476
8477     } else if (board[fromY][fromX] == king
8478         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8479         && toY == fromY && toX > fromX+1) {
8480         board[fromY][fromX] = EmptySquare;
8481         board[toY][toX] = king;
8482         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8483         board[fromY][BOARD_RGHT-1] = EmptySquare;
8484     } else if (board[fromY][fromX] == king
8485         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8486                && toY == fromY && toX < fromX-1) {
8487         board[fromY][fromX] = EmptySquare;
8488         board[toY][toX] = king;
8489         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8490         board[fromY][BOARD_LEFT] = EmptySquare;
8491     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8492                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8493                && toY >= BOARD_HEIGHT-promoRank
8494                ) {
8495         /* white pawn promotion */
8496         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8497         if (board[toY][toX] == EmptySquare) {
8498             board[toY][toX] = WhiteQueen;
8499         }
8500         if(gameInfo.variant==VariantBughouse ||
8501            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8502             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8503         board[fromY][fromX] = EmptySquare;
8504     } else if ((fromY == BOARD_HEIGHT-4)
8505                && (toX != fromX)
8506                && gameInfo.variant != VariantXiangqi
8507                && gameInfo.variant != VariantBerolina
8508                && (board[fromY][fromX] == WhitePawn)
8509                && (board[toY][toX] == EmptySquare)) {
8510         board[fromY][fromX] = EmptySquare;
8511         board[toY][toX] = WhitePawn;
8512         captured = board[toY - 1][toX];
8513         board[toY - 1][toX] = EmptySquare;
8514     } else if ((fromY == BOARD_HEIGHT-4)
8515                && (toX == fromX)
8516                && gameInfo.variant == VariantBerolina
8517                && (board[fromY][fromX] == WhitePawn)
8518                && (board[toY][toX] == EmptySquare)) {
8519         board[fromY][fromX] = EmptySquare;
8520         board[toY][toX] = WhitePawn;
8521         if(oldEP & EP_BEROLIN_A) {
8522                 captured = board[fromY][fromX-1];
8523                 board[fromY][fromX-1] = EmptySquare;
8524         }else{  captured = board[fromY][fromX+1];
8525                 board[fromY][fromX+1] = EmptySquare;
8526         }
8527     } else if (board[fromY][fromX] == king
8528         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8529                && toY == fromY && toX > fromX+1) {
8530         board[fromY][fromX] = EmptySquare;
8531         board[toY][toX] = king;
8532         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8533         board[fromY][BOARD_RGHT-1] = EmptySquare;
8534     } else if (board[fromY][fromX] == king
8535         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8536                && toY == fromY && toX < fromX-1) {
8537         board[fromY][fromX] = EmptySquare;
8538         board[toY][toX] = king;
8539         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8540         board[fromY][BOARD_LEFT] = EmptySquare;
8541     } else if (fromY == 7 && fromX == 3
8542                && board[fromY][fromX] == BlackKing
8543                && toY == 7 && toX == 5) {
8544         board[fromY][fromX] = EmptySquare;
8545         board[toY][toX] = BlackKing;
8546         board[fromY][7] = EmptySquare;
8547         board[toY][4] = BlackRook;
8548     } else if (fromY == 7 && fromX == 3
8549                && board[fromY][fromX] == BlackKing
8550                && toY == 7 && toX == 1) {
8551         board[fromY][fromX] = EmptySquare;
8552         board[toY][toX] = BlackKing;
8553         board[fromY][0] = EmptySquare;
8554         board[toY][2] = BlackRook;
8555     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8556                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8557                && toY < promoRank
8558                ) {
8559         /* black pawn promotion */
8560         board[toY][toX] = CharToPiece(ToLower(promoChar));
8561         if (board[toY][toX] == EmptySquare) {
8562             board[toY][toX] = BlackQueen;
8563         }
8564         if(gameInfo.variant==VariantBughouse ||
8565            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8566             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8567         board[fromY][fromX] = EmptySquare;
8568     } else if ((fromY == 3)
8569                && (toX != fromX)
8570                && gameInfo.variant != VariantXiangqi
8571                && gameInfo.variant != VariantBerolina
8572                && (board[fromY][fromX] == BlackPawn)
8573                && (board[toY][toX] == EmptySquare)) {
8574         board[fromY][fromX] = EmptySquare;
8575         board[toY][toX] = BlackPawn;
8576         captured = board[toY + 1][toX];
8577         board[toY + 1][toX] = EmptySquare;
8578     } else if ((fromY == 3)
8579                && (toX == fromX)
8580                && gameInfo.variant == VariantBerolina
8581                && (board[fromY][fromX] == BlackPawn)
8582                && (board[toY][toX] == EmptySquare)) {
8583         board[fromY][fromX] = EmptySquare;
8584         board[toY][toX] = BlackPawn;
8585         if(oldEP & EP_BEROLIN_A) {
8586                 captured = board[fromY][fromX-1];
8587                 board[fromY][fromX-1] = EmptySquare;
8588         }else{  captured = board[fromY][fromX+1];
8589                 board[fromY][fromX+1] = EmptySquare;
8590         }
8591     } else {
8592         board[toY][toX] = board[fromY][fromX];
8593         board[fromY][fromX] = EmptySquare;
8594     }
8595   }
8596
8597     if (gameInfo.holdingsWidth != 0) {
8598
8599       /* !!A lot more code needs to be written to support holdings  */
8600       /* [HGM] OK, so I have written it. Holdings are stored in the */
8601       /* penultimate board files, so they are automaticlly stored   */
8602       /* in the game history.                                       */
8603       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8604                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8605         /* Delete from holdings, by decreasing count */
8606         /* and erasing image if necessary            */
8607         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8608         if(p < (int) BlackPawn) { /* white drop */
8609              p -= (int)WhitePawn;
8610                  p = PieceToNumber((ChessSquare)p);
8611              if(p >= gameInfo.holdingsSize) p = 0;
8612              if(--board[p][BOARD_WIDTH-2] <= 0)
8613                   board[p][BOARD_WIDTH-1] = EmptySquare;
8614              if((int)board[p][BOARD_WIDTH-2] < 0)
8615                         board[p][BOARD_WIDTH-2] = 0;
8616         } else {                  /* black drop */
8617              p -= (int)BlackPawn;
8618                  p = PieceToNumber((ChessSquare)p);
8619              if(p >= gameInfo.holdingsSize) p = 0;
8620              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8621                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8622              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8623                         board[BOARD_HEIGHT-1-p][1] = 0;
8624         }
8625       }
8626       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8627           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8628         /* [HGM] holdings: Add to holdings, if holdings exist */
8629         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8630                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8631                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8632         }
8633         p = (int) captured;
8634         if (p >= (int) BlackPawn) {
8635           p -= (int)BlackPawn;
8636           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8637                   /* in Shogi restore piece to its original  first */
8638                   captured = (ChessSquare) (DEMOTED captured);
8639                   p = DEMOTED p;
8640           }
8641           p = PieceToNumber((ChessSquare)p);
8642           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8643           board[p][BOARD_WIDTH-2]++;
8644           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8645         } else {
8646           p -= (int)WhitePawn;
8647           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8648                   captured = (ChessSquare) (DEMOTED captured);
8649                   p = DEMOTED p;
8650           }
8651           p = PieceToNumber((ChessSquare)p);
8652           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8653           board[BOARD_HEIGHT-1-p][1]++;
8654           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8655         }
8656       }
8657     } else if (gameInfo.variant == VariantAtomic) {
8658       if (captured != EmptySquare) {
8659         int y, x;
8660         for (y = toY-1; y <= toY+1; y++) {
8661           for (x = toX-1; x <= toX+1; x++) {
8662             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8663                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8664               board[y][x] = EmptySquare;
8665             }
8666           }
8667         }
8668         board[toY][toX] = EmptySquare;
8669       }
8670     }
8671     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8672         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8673     } else
8674     if(promoChar == '+') {
8675         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8676         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8677     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8678         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8679     }
8680     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8681                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8682         // [HGM] superchess: take promotion piece out of holdings
8683         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8684         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8685             if(!--board[k][BOARD_WIDTH-2])
8686                 board[k][BOARD_WIDTH-1] = EmptySquare;
8687         } else {
8688             if(!--board[BOARD_HEIGHT-1-k][1])
8689                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8690         }
8691     }
8692
8693 }
8694
8695 /* Updates forwardMostMove */
8696 void
8697 MakeMove(fromX, fromY, toX, toY, promoChar)
8698      int fromX, fromY, toX, toY;
8699      int promoChar;
8700 {
8701 //    forwardMostMove++; // [HGM] bare: moved downstream
8702
8703     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8704         int timeLeft; static int lastLoadFlag=0; int king, piece;
8705         piece = boards[forwardMostMove][fromY][fromX];
8706         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8707         if(gameInfo.variant == VariantKnightmate)
8708             king += (int) WhiteUnicorn - (int) WhiteKing;
8709         if(forwardMostMove == 0) {
8710             if(blackPlaysFirst)
8711                 fprintf(serverMoves, "%s;", second.tidy);
8712             fprintf(serverMoves, "%s;", first.tidy);
8713             if(!blackPlaysFirst)
8714                 fprintf(serverMoves, "%s;", second.tidy);
8715         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8716         lastLoadFlag = loadFlag;
8717         // print base move
8718         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8719         // print castling suffix
8720         if( toY == fromY && piece == king ) {
8721             if(toX-fromX > 1)
8722                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8723             if(fromX-toX >1)
8724                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8725         }
8726         // e.p. suffix
8727         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8728              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8729              boards[forwardMostMove][toY][toX] == EmptySquare
8730              && fromX != toX && fromY != toY)
8731                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8732         // promotion suffix
8733         if(promoChar != NULLCHAR)
8734                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8735         if(!loadFlag) {
8736             fprintf(serverMoves, "/%d/%d",
8737                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8738             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8739             else                      timeLeft = blackTimeRemaining/1000;
8740             fprintf(serverMoves, "/%d", timeLeft);
8741         }
8742         fflush(serverMoves);
8743     }
8744
8745     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8746       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8747                         0, 1);
8748       return;
8749     }
8750     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8751     if (commentList[forwardMostMove+1] != NULL) {
8752         free(commentList[forwardMostMove+1]);
8753         commentList[forwardMostMove+1] = NULL;
8754     }
8755     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8756     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8757     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8758     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8759     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8760     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8761     gameInfo.result = GameUnfinished;
8762     if (gameInfo.resultDetails != NULL) {
8763         free(gameInfo.resultDetails);
8764         gameInfo.resultDetails = NULL;
8765     }
8766     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8767                               moveList[forwardMostMove - 1]);
8768     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8769                              PosFlags(forwardMostMove - 1),
8770                              fromY, fromX, toY, toX, promoChar,
8771                              parseList[forwardMostMove - 1]);
8772     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8773       case MT_NONE:
8774       case MT_STALEMATE:
8775       default:
8776         break;
8777       case MT_CHECK:
8778         if(gameInfo.variant != VariantShogi)
8779             strcat(parseList[forwardMostMove - 1], "+");
8780         break;
8781       case MT_CHECKMATE:
8782       case MT_STAINMATE:
8783         strcat(parseList[forwardMostMove - 1], "#");
8784         break;
8785     }
8786     if (appData.debugMode) {
8787         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8788     }
8789
8790 }
8791
8792 /* Updates currentMove if not pausing */
8793 void
8794 ShowMove(fromX, fromY, toX, toY)
8795 {
8796     int instant = (gameMode == PlayFromGameFile) ?
8797         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8798     if(appData.noGUI) return;
8799     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8800         if (!instant) {
8801             if (forwardMostMove == currentMove + 1) {
8802                 AnimateMove(boards[forwardMostMove - 1],
8803                             fromX, fromY, toX, toY);
8804             }
8805             if (appData.highlightLastMove) {
8806                 SetHighlights(fromX, fromY, toX, toY);
8807             }
8808         }
8809         currentMove = forwardMostMove;
8810     }
8811
8812     if (instant) return;
8813
8814     DisplayMove(currentMove - 1);
8815     DrawPosition(FALSE, boards[currentMove]);
8816     DisplayBothClocks();
8817     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8818 }
8819
8820 void SendEgtPath(ChessProgramState *cps)
8821 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8822         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8823
8824         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8825
8826         while(*p) {
8827             char c, *q = name+1, *r, *s;
8828
8829             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8830             while(*p && *p != ',') *q++ = *p++;
8831             *q++ = ':'; *q = 0;
8832             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8833                 strcmp(name, ",nalimov:") == 0 ) {
8834                 // take nalimov path from the menu-changeable option first, if it is defined
8835               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8836                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8837             } else
8838             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8839                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8840                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8841                 s = r = StrStr(s, ":") + 1; // beginning of path info
8842                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8843                 c = *r; *r = 0;             // temporarily null-terminate path info
8844                     *--q = 0;               // strip of trailig ':' from name
8845                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8846                 *r = c;
8847                 SendToProgram(buf,cps);     // send egtbpath command for this format
8848             }
8849             if(*p == ',') p++; // read away comma to position for next format name
8850         }
8851 }
8852
8853 void
8854 InitChessProgram(cps, setup)
8855      ChessProgramState *cps;
8856      int setup; /* [HGM] needed to setup FRC opening position */
8857 {
8858     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8859     if (appData.noChessProgram) return;
8860     hintRequested = FALSE;
8861     bookRequested = FALSE;
8862
8863     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8864     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8865     if(cps->memSize) { /* [HGM] memory */
8866       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8867         SendToProgram(buf, cps);
8868     }
8869     SendEgtPath(cps); /* [HGM] EGT */
8870     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8871       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8872         SendToProgram(buf, cps);
8873     }
8874
8875     SendToProgram(cps->initString, cps);
8876     if (gameInfo.variant != VariantNormal &&
8877         gameInfo.variant != VariantLoadable
8878         /* [HGM] also send variant if board size non-standard */
8879         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8880                                             ) {
8881       char *v = VariantName(gameInfo.variant);
8882       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8883         /* [HGM] in protocol 1 we have to assume all variants valid */
8884         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8885         DisplayFatalError(buf, 0, 1);
8886         return;
8887       }
8888
8889       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8890       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8891       if( gameInfo.variant == VariantXiangqi )
8892            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8893       if( gameInfo.variant == VariantShogi )
8894            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8895       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8896            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8897       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8898                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8899            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8900       if( gameInfo.variant == VariantCourier )
8901            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8902       if( gameInfo.variant == VariantSuper )
8903            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8904       if( gameInfo.variant == VariantGreat )
8905            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8906       if( gameInfo.variant == VariantSChess )
8907            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8908
8909       if(overruled) {
8910         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8911                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8912            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8913            if(StrStr(cps->variants, b) == NULL) {
8914                // specific sized variant not known, check if general sizing allowed
8915                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8916                    if(StrStr(cps->variants, "boardsize") == NULL) {
8917                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8918                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8919                        DisplayFatalError(buf, 0, 1);
8920                        return;
8921                    }
8922                    /* [HGM] here we really should compare with the maximum supported board size */
8923                }
8924            }
8925       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8926       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8927       SendToProgram(buf, cps);
8928     }
8929     currentlyInitializedVariant = gameInfo.variant;
8930
8931     /* [HGM] send opening position in FRC to first engine */
8932     if(setup) {
8933           SendToProgram("force\n", cps);
8934           SendBoard(cps, 0);
8935           /* engine is now in force mode! Set flag to wake it up after first move. */
8936           setboardSpoiledMachineBlack = 1;
8937     }
8938
8939     if (cps->sendICS) {
8940       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8941       SendToProgram(buf, cps);
8942     }
8943     cps->maybeThinking = FALSE;
8944     cps->offeredDraw = 0;
8945     if (!appData.icsActive) {
8946         SendTimeControl(cps, movesPerSession, timeControl,
8947                         timeIncrement, appData.searchDepth,
8948                         searchTime);
8949     }
8950     if (appData.showThinking
8951         // [HGM] thinking: four options require thinking output to be sent
8952         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8953                                 ) {
8954         SendToProgram("post\n", cps);
8955     }
8956     SendToProgram("hard\n", cps);
8957     if (!appData.ponderNextMove) {
8958         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8959            it without being sure what state we are in first.  "hard"
8960            is not a toggle, so that one is OK.
8961          */
8962         SendToProgram("easy\n", cps);
8963     }
8964     if (cps->usePing) {
8965       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8966       SendToProgram(buf, cps);
8967     }
8968     cps->initDone = TRUE;
8969 }
8970
8971
8972 void
8973 StartChessProgram(cps)
8974      ChessProgramState *cps;
8975 {
8976     char buf[MSG_SIZ];
8977     int err;
8978
8979     if (appData.noChessProgram) return;
8980     cps->initDone = FALSE;
8981
8982     if (strcmp(cps->host, "localhost") == 0) {
8983         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8984     } else if (*appData.remoteShell == NULLCHAR) {
8985         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8986     } else {
8987         if (*appData.remoteUser == NULLCHAR) {
8988           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8989                     cps->program);
8990         } else {
8991           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8992                     cps->host, appData.remoteUser, cps->program);
8993         }
8994         err = StartChildProcess(buf, "", &cps->pr);
8995     }
8996
8997     if (err != 0) {
8998       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8999         DisplayFatalError(buf, err, 1);
9000         cps->pr = NoProc;
9001         cps->isr = NULL;
9002         return;
9003     }
9004
9005     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9006     if (cps->protocolVersion > 1) {
9007       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9008       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9009       cps->comboCnt = 0;  //                and values of combo boxes
9010       SendToProgram(buf, cps);
9011     } else {
9012       SendToProgram("xboard\n", cps);
9013     }
9014 }
9015
9016
9017 void
9018 TwoMachinesEventIfReady P((void))
9019 {
9020   if (first.lastPing != first.lastPong) {
9021     DisplayMessage("", _("Waiting for first chess program"));
9022     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9023     return;
9024   }
9025   if (second.lastPing != second.lastPong) {
9026     DisplayMessage("", _("Waiting for second chess program"));
9027     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9028     return;
9029   }
9030   ThawUI();
9031   TwoMachinesEvent();
9032 }
9033
9034 void
9035 NextMatchGame P((void))
9036 {
9037     int index; /* [HGM] autoinc: step load index during match */
9038     Reset(FALSE, TRUE);
9039     if (*appData.loadGameFile != NULLCHAR) {
9040         index = appData.loadGameIndex;
9041         if(index < 0) { // [HGM] autoinc
9042             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9043             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9044         }
9045         LoadGameFromFile(appData.loadGameFile,
9046                          index,
9047                          appData.loadGameFile, FALSE);
9048     } else if (*appData.loadPositionFile != NULLCHAR) {
9049         index = appData.loadPositionIndex;
9050         if(index < 0) { // [HGM] autoinc
9051             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9052             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9053         }
9054         LoadPositionFromFile(appData.loadPositionFile,
9055                              index,
9056                              appData.loadPositionFile);
9057     }
9058     TwoMachinesEventIfReady();
9059 }
9060
9061 void UserAdjudicationEvent( int result )
9062 {
9063     ChessMove gameResult = GameIsDrawn;
9064
9065     if( result > 0 ) {
9066         gameResult = WhiteWins;
9067     }
9068     else if( result < 0 ) {
9069         gameResult = BlackWins;
9070     }
9071
9072     if( gameMode == TwoMachinesPlay ) {
9073         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9074     }
9075 }
9076
9077
9078 // [HGM] save: calculate checksum of game to make games easily identifiable
9079 int StringCheckSum(char *s)
9080 {
9081         int i = 0;
9082         if(s==NULL) return 0;
9083         while(*s) i = i*259 + *s++;
9084         return i;
9085 }
9086
9087 int GameCheckSum()
9088 {
9089         int i, sum=0;
9090         for(i=backwardMostMove; i<forwardMostMove; i++) {
9091                 sum += pvInfoList[i].depth;
9092                 sum += StringCheckSum(parseList[i]);
9093                 sum += StringCheckSum(commentList[i]);
9094                 sum *= 261;
9095         }
9096         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9097         return sum + StringCheckSum(commentList[i]);
9098 } // end of save patch
9099
9100 void
9101 GameEnds(result, resultDetails, whosays)
9102      ChessMove result;
9103      char *resultDetails;
9104      int whosays;
9105 {
9106     GameMode nextGameMode;
9107     int isIcsGame;
9108     char buf[MSG_SIZ], popupRequested = 0;
9109
9110     if(endingGame) return; /* [HGM] crash: forbid recursion */
9111     endingGame = 1;
9112     if(twoBoards) { // [HGM] dual: switch back to one board
9113         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9114         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9115     }
9116     if (appData.debugMode) {
9117       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9118               result, resultDetails ? resultDetails : "(null)", whosays);
9119     }
9120
9121     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9122
9123     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9124         /* If we are playing on ICS, the server decides when the
9125            game is over, but the engine can offer to draw, claim
9126            a draw, or resign.
9127          */
9128 #if ZIPPY
9129         if (appData.zippyPlay && first.initDone) {
9130             if (result == GameIsDrawn) {
9131                 /* In case draw still needs to be claimed */
9132                 SendToICS(ics_prefix);
9133                 SendToICS("draw\n");
9134             } else if (StrCaseStr(resultDetails, "resign")) {
9135                 SendToICS(ics_prefix);
9136                 SendToICS("resign\n");
9137             }
9138         }
9139 #endif
9140         endingGame = 0; /* [HGM] crash */
9141         return;
9142     }
9143
9144     /* If we're loading the game from a file, stop */
9145     if (whosays == GE_FILE) {
9146       (void) StopLoadGameTimer();
9147       gameFileFP = NULL;
9148     }
9149
9150     /* Cancel draw offers */
9151     first.offeredDraw = second.offeredDraw = 0;
9152
9153     /* If this is an ICS game, only ICS can really say it's done;
9154        if not, anyone can. */
9155     isIcsGame = (gameMode == IcsPlayingWhite ||
9156                  gameMode == IcsPlayingBlack ||
9157                  gameMode == IcsObserving    ||
9158                  gameMode == IcsExamining);
9159
9160     if (!isIcsGame || whosays == GE_ICS) {
9161         /* OK -- not an ICS game, or ICS said it was done */
9162         StopClocks();
9163         if (!isIcsGame && !appData.noChessProgram)
9164           SetUserThinkingEnables();
9165
9166         /* [HGM] if a machine claims the game end we verify this claim */
9167         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9168             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9169                 char claimer;
9170                 ChessMove trueResult = (ChessMove) -1;
9171
9172                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9173                                             first.twoMachinesColor[0] :
9174                                             second.twoMachinesColor[0] ;
9175
9176                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9177                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9178                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9179                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9180                 } else
9181                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9182                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9183                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9184                 } else
9185                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9186                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9187                 }
9188
9189                 // now verify win claims, but not in drop games, as we don't understand those yet
9190                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9191                                                  || gameInfo.variant == VariantGreat) &&
9192                     (result == WhiteWins && claimer == 'w' ||
9193                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9194                       if (appData.debugMode) {
9195                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9196                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9197                       }
9198                       if(result != trueResult) {
9199                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9200                               result = claimer == 'w' ? BlackWins : WhiteWins;
9201                               resultDetails = buf;
9202                       }
9203                 } else
9204                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9205                     && (forwardMostMove <= backwardMostMove ||
9206                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9207                         (claimer=='b')==(forwardMostMove&1))
9208                                                                                   ) {
9209                       /* [HGM] verify: draws that were not flagged are false claims */
9210                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9211                       result = claimer == 'w' ? BlackWins : WhiteWins;
9212                       resultDetails = buf;
9213                 }
9214                 /* (Claiming a loss is accepted no questions asked!) */
9215             }
9216             /* [HGM] bare: don't allow bare King to win */
9217             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9218                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9219                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9220                && result != GameIsDrawn)
9221             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9222                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9223                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9224                         if(p >= 0 && p <= (int)WhiteKing) k++;
9225                 }
9226                 if (appData.debugMode) {
9227                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9228                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9229                 }
9230                 if(k <= 1) {
9231                         result = GameIsDrawn;
9232                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9233                         resultDetails = buf;
9234                 }
9235             }
9236         }
9237
9238
9239         if(serverMoves != NULL && !loadFlag) { char c = '=';
9240             if(result==WhiteWins) c = '+';
9241             if(result==BlackWins) c = '-';
9242             if(resultDetails != NULL)
9243                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9244         }
9245         if (resultDetails != NULL) {
9246             gameInfo.result = result;
9247             gameInfo.resultDetails = StrSave(resultDetails);
9248
9249             /* display last move only if game was not loaded from file */
9250             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9251                 DisplayMove(currentMove - 1);
9252
9253             if (forwardMostMove != 0) {
9254                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9255                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9256                                                                 ) {
9257                     if (*appData.saveGameFile != NULLCHAR) {
9258                         SaveGameToFile(appData.saveGameFile, TRUE);
9259                     } else if (appData.autoSaveGames) {
9260                         AutoSaveGame();
9261                     }
9262                     if (*appData.savePositionFile != NULLCHAR) {
9263                         SavePositionToFile(appData.savePositionFile);
9264                     }
9265                 }
9266             }
9267
9268             /* Tell program how game ended in case it is learning */
9269             /* [HGM] Moved this to after saving the PGN, just in case */
9270             /* engine died and we got here through time loss. In that */
9271             /* case we will get a fatal error writing the pipe, which */
9272             /* would otherwise lose us the PGN.                       */
9273             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9274             /* output during GameEnds should never be fatal anymore   */
9275             if (gameMode == MachinePlaysWhite ||
9276                 gameMode == MachinePlaysBlack ||
9277                 gameMode == TwoMachinesPlay ||
9278                 gameMode == IcsPlayingWhite ||
9279                 gameMode == IcsPlayingBlack ||
9280                 gameMode == BeginningOfGame) {
9281                 char buf[MSG_SIZ];
9282                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9283                         resultDetails);
9284                 if (first.pr != NoProc) {
9285                     SendToProgram(buf, &first);
9286                 }
9287                 if (second.pr != NoProc &&
9288                     gameMode == TwoMachinesPlay) {
9289                     SendToProgram(buf, &second);
9290                 }
9291             }
9292         }
9293
9294         if (appData.icsActive) {
9295             if (appData.quietPlay &&
9296                 (gameMode == IcsPlayingWhite ||
9297                  gameMode == IcsPlayingBlack)) {
9298                 SendToICS(ics_prefix);
9299                 SendToICS("set shout 1\n");
9300             }
9301             nextGameMode = IcsIdle;
9302             ics_user_moved = FALSE;
9303             /* clean up premove.  It's ugly when the game has ended and the
9304              * premove highlights are still on the board.
9305              */
9306             if (gotPremove) {
9307               gotPremove = FALSE;
9308               ClearPremoveHighlights();
9309               DrawPosition(FALSE, boards[currentMove]);
9310             }
9311             if (whosays == GE_ICS) {
9312                 switch (result) {
9313                 case WhiteWins:
9314                     if (gameMode == IcsPlayingWhite)
9315                         PlayIcsWinSound();
9316                     else if(gameMode == IcsPlayingBlack)
9317                         PlayIcsLossSound();
9318                     break;
9319                 case BlackWins:
9320                     if (gameMode == IcsPlayingBlack)
9321                         PlayIcsWinSound();
9322                     else if(gameMode == IcsPlayingWhite)
9323                         PlayIcsLossSound();
9324                     break;
9325                 case GameIsDrawn:
9326                     PlayIcsDrawSound();
9327                     break;
9328                 default:
9329                     PlayIcsUnfinishedSound();
9330                 }
9331             }
9332         } else if (gameMode == EditGame ||
9333                    gameMode == PlayFromGameFile ||
9334                    gameMode == AnalyzeMode ||
9335                    gameMode == AnalyzeFile) {
9336             nextGameMode = gameMode;
9337         } else {
9338             nextGameMode = EndOfGame;
9339         }
9340         pausing = FALSE;
9341         ModeHighlight();
9342     } else {
9343         nextGameMode = gameMode;
9344     }
9345
9346     if (appData.noChessProgram) {
9347         gameMode = nextGameMode;
9348         ModeHighlight();
9349         endingGame = 0; /* [HGM] crash */
9350         return;
9351     }
9352
9353     if (first.reuse) {
9354         /* Put first chess program into idle state */
9355         if (first.pr != NoProc &&
9356             (gameMode == MachinePlaysWhite ||
9357              gameMode == MachinePlaysBlack ||
9358              gameMode == TwoMachinesPlay ||
9359              gameMode == IcsPlayingWhite ||
9360              gameMode == IcsPlayingBlack ||
9361              gameMode == BeginningOfGame)) {
9362             SendToProgram("force\n", &first);
9363             if (first.usePing) {
9364               char buf[MSG_SIZ];
9365               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9366               SendToProgram(buf, &first);
9367             }
9368         }
9369     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9370         /* Kill off first chess program */
9371         if (first.isr != NULL)
9372           RemoveInputSource(first.isr);
9373         first.isr = NULL;
9374
9375         if (first.pr != NoProc) {
9376             ExitAnalyzeMode();
9377             DoSleep( appData.delayBeforeQuit );
9378             SendToProgram("quit\n", &first);
9379             DoSleep( appData.delayAfterQuit );
9380             DestroyChildProcess(first.pr, first.useSigterm);
9381         }
9382         first.pr = NoProc;
9383     }
9384     if (second.reuse) {
9385         /* Put second chess program into idle state */
9386         if (second.pr != NoProc &&
9387             gameMode == TwoMachinesPlay) {
9388             SendToProgram("force\n", &second);
9389             if (second.usePing) {
9390               char buf[MSG_SIZ];
9391               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9392               SendToProgram(buf, &second);
9393             }
9394         }
9395     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9396         /* Kill off second chess program */
9397         if (second.isr != NULL)
9398           RemoveInputSource(second.isr);
9399         second.isr = NULL;
9400
9401         if (second.pr != NoProc) {
9402             DoSleep( appData.delayBeforeQuit );
9403             SendToProgram("quit\n", &second);
9404             DoSleep( appData.delayAfterQuit );
9405             DestroyChildProcess(second.pr, second.useSigterm);
9406         }
9407         second.pr = NoProc;
9408     }
9409
9410     if (matchMode && gameMode == TwoMachinesPlay) {
9411         switch (result) {
9412         case WhiteWins:
9413           if (first.twoMachinesColor[0] == 'w') {
9414             first.matchWins++;
9415           } else {
9416             second.matchWins++;
9417           }
9418           break;
9419         case BlackWins:
9420           if (first.twoMachinesColor[0] == 'b') {
9421             first.matchWins++;
9422           } else {
9423             second.matchWins++;
9424           }
9425           break;
9426         default:
9427           break;
9428         }
9429         if (matchGame < appData.matchGames) {
9430             char *tmp;
9431             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9432                 tmp = first.twoMachinesColor;
9433                 first.twoMachinesColor = second.twoMachinesColor;
9434                 second.twoMachinesColor = tmp;
9435             }
9436             gameMode = nextGameMode;
9437             matchGame++;
9438             if(appData.matchPause>10000 || appData.matchPause<10)
9439                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9440             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9441             endingGame = 0; /* [HGM] crash */
9442             return;
9443         } else {
9444             gameMode = nextGameMode;
9445             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9446                      first.tidy, second.tidy,
9447                      first.matchWins, second.matchWins,
9448                      appData.matchGames - (first.matchWins + second.matchWins));
9449             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9450         }
9451     }
9452     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9453         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9454       ExitAnalyzeMode();
9455     gameMode = nextGameMode;
9456     ModeHighlight();
9457     endingGame = 0;  /* [HGM] crash */
9458     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9459       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9460         matchMode = FALSE; appData.matchGames = matchGame = 0;
9461         DisplayNote(buf);
9462       }
9463     }
9464 }
9465
9466 /* Assumes program was just initialized (initString sent).
9467    Leaves program in force mode. */
9468 void
9469 FeedMovesToProgram(cps, upto)
9470      ChessProgramState *cps;
9471      int upto;
9472 {
9473     int i;
9474
9475     if (appData.debugMode)
9476       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9477               startedFromSetupPosition ? "position and " : "",
9478               backwardMostMove, upto, cps->which);
9479     if(currentlyInitializedVariant != gameInfo.variant) {
9480       char buf[MSG_SIZ];
9481         // [HGM] variantswitch: make engine aware of new variant
9482         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9483                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9484         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9485         SendToProgram(buf, cps);
9486         currentlyInitializedVariant = gameInfo.variant;
9487     }
9488     SendToProgram("force\n", cps);
9489     if (startedFromSetupPosition) {
9490         SendBoard(cps, backwardMostMove);
9491     if (appData.debugMode) {
9492         fprintf(debugFP, "feedMoves\n");
9493     }
9494     }
9495     for (i = backwardMostMove; i < upto; i++) {
9496         SendMoveToProgram(i, cps);
9497     }
9498 }
9499
9500
9501 void
9502 ResurrectChessProgram()
9503 {
9504      /* The chess program may have exited.
9505         If so, restart it and feed it all the moves made so far. */
9506
9507     if (appData.noChessProgram || first.pr != NoProc) return;
9508
9509     StartChessProgram(&first);
9510     InitChessProgram(&first, FALSE);
9511     FeedMovesToProgram(&first, currentMove);
9512
9513     if (!first.sendTime) {
9514         /* can't tell gnuchess what its clock should read,
9515            so we bow to its notion. */
9516         ResetClocks();
9517         timeRemaining[0][currentMove] = whiteTimeRemaining;
9518         timeRemaining[1][currentMove] = blackTimeRemaining;
9519     }
9520
9521     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9522                 appData.icsEngineAnalyze) && first.analysisSupport) {
9523       SendToProgram("analyze\n", &first);
9524       first.analyzing = TRUE;
9525     }
9526 }
9527
9528 /*
9529  * Button procedures
9530  */
9531 void
9532 Reset(redraw, init)
9533      int redraw, init;
9534 {
9535     int i;
9536
9537     if (appData.debugMode) {
9538         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9539                 redraw, init, gameMode);
9540     }
9541     CleanupTail(); // [HGM] vari: delete any stored variations
9542     pausing = pauseExamInvalid = FALSE;
9543     startedFromSetupPosition = blackPlaysFirst = FALSE;
9544     firstMove = TRUE;
9545     whiteFlag = blackFlag = FALSE;
9546     userOfferedDraw = FALSE;
9547     hintRequested = bookRequested = FALSE;
9548     first.maybeThinking = FALSE;
9549     second.maybeThinking = FALSE;
9550     first.bookSuspend = FALSE; // [HGM] book
9551     second.bookSuspend = FALSE;
9552     thinkOutput[0] = NULLCHAR;
9553     lastHint[0] = NULLCHAR;
9554     ClearGameInfo(&gameInfo);
9555     gameInfo.variant = StringToVariant(appData.variant);
9556     ics_user_moved = ics_clock_paused = FALSE;
9557     ics_getting_history = H_FALSE;
9558     ics_gamenum = -1;
9559     white_holding[0] = black_holding[0] = NULLCHAR;
9560     ClearProgramStats();
9561     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9562
9563     ResetFrontEnd();
9564     ClearHighlights();
9565     flipView = appData.flipView;
9566     ClearPremoveHighlights();
9567     gotPremove = FALSE;
9568     alarmSounded = FALSE;
9569
9570     GameEnds(EndOfFile, NULL, GE_PLAYER);
9571     if(appData.serverMovesName != NULL) {
9572         /* [HGM] prepare to make moves file for broadcasting */
9573         clock_t t = clock();
9574         if(serverMoves != NULL) fclose(serverMoves);
9575         serverMoves = fopen(appData.serverMovesName, "r");
9576         if(serverMoves != NULL) {
9577             fclose(serverMoves);
9578             /* delay 15 sec before overwriting, so all clients can see end */
9579             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9580         }
9581         serverMoves = fopen(appData.serverMovesName, "w");
9582     }
9583
9584     ExitAnalyzeMode();
9585     gameMode = BeginningOfGame;
9586     ModeHighlight();
9587     if(appData.icsActive) gameInfo.variant = VariantNormal;
9588     currentMove = forwardMostMove = backwardMostMove = 0;
9589     InitPosition(redraw);
9590     for (i = 0; i < MAX_MOVES; i++) {
9591         if (commentList[i] != NULL) {
9592             free(commentList[i]);
9593             commentList[i] = NULL;
9594         }
9595     }
9596     ResetClocks();
9597     timeRemaining[0][0] = whiteTimeRemaining;
9598     timeRemaining[1][0] = blackTimeRemaining;
9599     if (first.pr == NULL) {
9600         StartChessProgram(&first);
9601     }
9602     if (init) {
9603             InitChessProgram(&first, startedFromSetupPosition);
9604     }
9605     DisplayTitle("");
9606     DisplayMessage("", "");
9607     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9608     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9609 }
9610
9611 void
9612 AutoPlayGameLoop()
9613 {
9614     for (;;) {
9615         if (!AutoPlayOneMove())
9616           return;
9617         if (matchMode || appData.timeDelay == 0)
9618           continue;
9619         if (appData.timeDelay < 0)
9620           return;
9621         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9622         break;
9623     }
9624 }
9625
9626
9627 int
9628 AutoPlayOneMove()
9629 {
9630     int fromX, fromY, toX, toY;
9631
9632     if (appData.debugMode) {
9633       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9634     }
9635
9636     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9637       return FALSE;
9638
9639     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9640       pvInfoList[currentMove].depth = programStats.depth;
9641       pvInfoList[currentMove].score = programStats.score;
9642       pvInfoList[currentMove].time  = 0;
9643       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9644     }
9645
9646     if (currentMove >= forwardMostMove) {
9647       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9648       gameMode = EditGame;
9649       ModeHighlight();
9650
9651       /* [AS] Clear current move marker at the end of a game */
9652       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9653
9654       return FALSE;
9655     }
9656
9657     toX = moveList[currentMove][2] - AAA;
9658     toY = moveList[currentMove][3] - ONE;
9659
9660     if (moveList[currentMove][1] == '@') {
9661         if (appData.highlightLastMove) {
9662             SetHighlights(-1, -1, toX, toY);
9663         }
9664     } else {
9665         fromX = moveList[currentMove][0] - AAA;
9666         fromY = moveList[currentMove][1] - ONE;
9667
9668         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9669
9670         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9671
9672         if (appData.highlightLastMove) {
9673             SetHighlights(fromX, fromY, toX, toY);
9674         }
9675     }
9676     DisplayMove(currentMove);
9677     SendMoveToProgram(currentMove++, &first);
9678     DisplayBothClocks();
9679     DrawPosition(FALSE, boards[currentMove]);
9680     // [HGM] PV info: always display, routine tests if empty
9681     DisplayComment(currentMove - 1, commentList[currentMove]);
9682     return TRUE;
9683 }
9684
9685
9686 int
9687 LoadGameOneMove(readAhead)
9688      ChessMove readAhead;
9689 {
9690     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9691     char promoChar = NULLCHAR;
9692     ChessMove moveType;
9693     char move[MSG_SIZ];
9694     char *p, *q;
9695
9696     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9697         gameMode != AnalyzeMode && gameMode != Training) {
9698         gameFileFP = NULL;
9699         return FALSE;
9700     }
9701
9702     yyboardindex = forwardMostMove;
9703     if (readAhead != EndOfFile) {
9704       moveType = readAhead;
9705     } else {
9706       if (gameFileFP == NULL)
9707           return FALSE;
9708       moveType = (ChessMove) Myylex();
9709     }
9710
9711     done = FALSE;
9712     switch (moveType) {
9713       case Comment:
9714         if (appData.debugMode)
9715           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9716         p = yy_text;
9717
9718         /* append the comment but don't display it */
9719         AppendComment(currentMove, p, FALSE);
9720         return TRUE;
9721
9722       case WhiteCapturesEnPassant:
9723       case BlackCapturesEnPassant:
9724       case WhitePromotion:
9725       case BlackPromotion:
9726       case WhiteNonPromotion:
9727       case BlackNonPromotion:
9728       case NormalMove:
9729       case WhiteKingSideCastle:
9730       case WhiteQueenSideCastle:
9731       case BlackKingSideCastle:
9732       case BlackQueenSideCastle:
9733       case WhiteKingSideCastleWild:
9734       case WhiteQueenSideCastleWild:
9735       case BlackKingSideCastleWild:
9736       case BlackQueenSideCastleWild:
9737       /* PUSH Fabien */
9738       case WhiteHSideCastleFR:
9739       case WhiteASideCastleFR:
9740       case BlackHSideCastleFR:
9741       case BlackASideCastleFR:
9742       /* POP Fabien */
9743         if (appData.debugMode)
9744           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9745         fromX = currentMoveString[0] - AAA;
9746         fromY = currentMoveString[1] - ONE;
9747         toX = currentMoveString[2] - AAA;
9748         toY = currentMoveString[3] - ONE;
9749         promoChar = currentMoveString[4];
9750         break;
9751
9752       case WhiteDrop:
9753       case BlackDrop:
9754         if (appData.debugMode)
9755           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9756         fromX = moveType == WhiteDrop ?
9757           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9758         (int) CharToPiece(ToLower(currentMoveString[0]));
9759         fromY = DROP_RANK;
9760         toX = currentMoveString[2] - AAA;
9761         toY = currentMoveString[3] - ONE;
9762         break;
9763
9764       case WhiteWins:
9765       case BlackWins:
9766       case GameIsDrawn:
9767       case GameUnfinished:
9768         if (appData.debugMode)
9769           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9770         p = strchr(yy_text, '{');
9771         if (p == NULL) p = strchr(yy_text, '(');
9772         if (p == NULL) {
9773             p = yy_text;
9774             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9775         } else {
9776             q = strchr(p, *p == '{' ? '}' : ')');
9777             if (q != NULL) *q = NULLCHAR;
9778             p++;
9779         }
9780         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9781         GameEnds(moveType, p, GE_FILE);
9782         done = TRUE;
9783         if (cmailMsgLoaded) {
9784             ClearHighlights();
9785             flipView = WhiteOnMove(currentMove);
9786             if (moveType == GameUnfinished) flipView = !flipView;
9787             if (appData.debugMode)
9788               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9789         }
9790         break;
9791
9792       case EndOfFile:
9793         if (appData.debugMode)
9794           fprintf(debugFP, "Parser hit end of file\n");
9795         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9796           case MT_NONE:
9797           case MT_CHECK:
9798             break;
9799           case MT_CHECKMATE:
9800           case MT_STAINMATE:
9801             if (WhiteOnMove(currentMove)) {
9802                 GameEnds(BlackWins, "Black mates", GE_FILE);
9803             } else {
9804                 GameEnds(WhiteWins, "White mates", GE_FILE);
9805             }
9806             break;
9807           case MT_STALEMATE:
9808             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9809             break;
9810         }
9811         done = TRUE;
9812         break;
9813
9814       case MoveNumberOne:
9815         if (lastLoadGameStart == GNUChessGame) {
9816             /* GNUChessGames have numbers, but they aren't move numbers */
9817             if (appData.debugMode)
9818               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9819                       yy_text, (int) moveType);
9820             return LoadGameOneMove(EndOfFile); /* tail recursion */
9821         }
9822         /* else fall thru */
9823
9824       case XBoardGame:
9825       case GNUChessGame:
9826       case PGNTag:
9827         /* Reached start of next game in file */
9828         if (appData.debugMode)
9829           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9830         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9831           case MT_NONE:
9832           case MT_CHECK:
9833             break;
9834           case MT_CHECKMATE:
9835           case MT_STAINMATE:
9836             if (WhiteOnMove(currentMove)) {
9837                 GameEnds(BlackWins, "Black mates", GE_FILE);
9838             } else {
9839                 GameEnds(WhiteWins, "White mates", GE_FILE);
9840             }
9841             break;
9842           case MT_STALEMATE:
9843             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9844             break;
9845         }
9846         done = TRUE;
9847         break;
9848
9849       case PositionDiagram:     /* should not happen; ignore */
9850       case ElapsedTime:         /* ignore */
9851       case NAG:                 /* ignore */
9852         if (appData.debugMode)
9853           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9854                   yy_text, (int) moveType);
9855         return LoadGameOneMove(EndOfFile); /* tail recursion */
9856
9857       case IllegalMove:
9858         if (appData.testLegality) {
9859             if (appData.debugMode)
9860               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9861             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9862                     (forwardMostMove / 2) + 1,
9863                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9864             DisplayError(move, 0);
9865             done = TRUE;
9866         } else {
9867             if (appData.debugMode)
9868               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9869                       yy_text, currentMoveString);
9870             fromX = currentMoveString[0] - AAA;
9871             fromY = currentMoveString[1] - ONE;
9872             toX = currentMoveString[2] - AAA;
9873             toY = currentMoveString[3] - ONE;
9874             promoChar = currentMoveString[4];
9875         }
9876         break;
9877
9878       case AmbiguousMove:
9879         if (appData.debugMode)
9880           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9881         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9882                 (forwardMostMove / 2) + 1,
9883                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9884         DisplayError(move, 0);
9885         done = TRUE;
9886         break;
9887
9888       default:
9889       case ImpossibleMove:
9890         if (appData.debugMode)
9891           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9892         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9893                 (forwardMostMove / 2) + 1,
9894                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9895         DisplayError(move, 0);
9896         done = TRUE;
9897         break;
9898     }
9899
9900     if (done) {
9901         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9902             DrawPosition(FALSE, boards[currentMove]);
9903             DisplayBothClocks();
9904             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9905               DisplayComment(currentMove - 1, commentList[currentMove]);
9906         }
9907         (void) StopLoadGameTimer();
9908         gameFileFP = NULL;
9909         cmailOldMove = forwardMostMove;
9910         return FALSE;
9911     } else {
9912         /* currentMoveString is set as a side-effect of yylex */
9913         strcat(currentMoveString, "\n");
9914         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9915
9916         thinkOutput[0] = NULLCHAR;
9917         MakeMove(fromX, fromY, toX, toY, promoChar);
9918         currentMove = forwardMostMove;
9919         return TRUE;
9920     }
9921 }
9922
9923 /* Load the nth game from the given file */
9924 int
9925 LoadGameFromFile(filename, n, title, useList)
9926      char *filename;
9927      int n;
9928      char *title;
9929      /*Boolean*/ int useList;
9930 {
9931     FILE *f;
9932     char buf[MSG_SIZ];
9933
9934     if (strcmp(filename, "-") == 0) {
9935         f = stdin;
9936         title = "stdin";
9937     } else {
9938         f = fopen(filename, "rb");
9939         if (f == NULL) {
9940           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9941             DisplayError(buf, errno);
9942             return FALSE;
9943         }
9944     }
9945     if (fseek(f, 0, 0) == -1) {
9946         /* f is not seekable; probably a pipe */
9947         useList = FALSE;
9948     }
9949     if (useList && n == 0) {
9950         int error = GameListBuild(f);
9951         if (error) {
9952             DisplayError(_("Cannot build game list"), error);
9953         } else if (!ListEmpty(&gameList) &&
9954                    ((ListGame *) gameList.tailPred)->number > 1) {
9955             GameListPopUp(f, title);
9956             return TRUE;
9957         }
9958         GameListDestroy();
9959         n = 1;
9960     }
9961     if (n == 0) n = 1;
9962     return LoadGame(f, n, title, FALSE);
9963 }
9964
9965
9966 void
9967 MakeRegisteredMove()
9968 {
9969     int fromX, fromY, toX, toY;
9970     char promoChar;
9971     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9972         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9973           case CMAIL_MOVE:
9974           case CMAIL_DRAW:
9975             if (appData.debugMode)
9976               fprintf(debugFP, "Restoring %s for game %d\n",
9977                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9978
9979             thinkOutput[0] = NULLCHAR;
9980             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9981             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9982             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9983             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9984             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9985             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9986             MakeMove(fromX, fromY, toX, toY, promoChar);
9987             ShowMove(fromX, fromY, toX, toY);
9988
9989             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9990               case MT_NONE:
9991               case MT_CHECK:
9992                 break;
9993
9994               case MT_CHECKMATE:
9995               case MT_STAINMATE:
9996                 if (WhiteOnMove(currentMove)) {
9997                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9998                 } else {
9999                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10000                 }
10001                 break;
10002
10003               case MT_STALEMATE:
10004                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10005                 break;
10006             }
10007
10008             break;
10009
10010           case CMAIL_RESIGN:
10011             if (WhiteOnMove(currentMove)) {
10012                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10013             } else {
10014                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10015             }
10016             break;
10017
10018           case CMAIL_ACCEPT:
10019             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10020             break;
10021
10022           default:
10023             break;
10024         }
10025     }
10026
10027     return;
10028 }
10029
10030 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10031 int
10032 CmailLoadGame(f, gameNumber, title, useList)
10033      FILE *f;
10034      int gameNumber;
10035      char *title;
10036      int useList;
10037 {
10038     int retVal;
10039
10040     if (gameNumber > nCmailGames) {
10041         DisplayError(_("No more games in this message"), 0);
10042         return FALSE;
10043     }
10044     if (f == lastLoadGameFP) {
10045         int offset = gameNumber - lastLoadGameNumber;
10046         if (offset == 0) {
10047             cmailMsg[0] = NULLCHAR;
10048             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10049                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10050                 nCmailMovesRegistered--;
10051             }
10052             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10053             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10054                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10055             }
10056         } else {
10057             if (! RegisterMove()) return FALSE;
10058         }
10059     }
10060
10061     retVal = LoadGame(f, gameNumber, title, useList);
10062
10063     /* Make move registered during previous look at this game, if any */
10064     MakeRegisteredMove();
10065
10066     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10067         commentList[currentMove]
10068           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10069         DisplayComment(currentMove - 1, commentList[currentMove]);
10070     }
10071
10072     return retVal;
10073 }
10074
10075 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10076 int
10077 ReloadGame(offset)
10078      int offset;
10079 {
10080     int gameNumber = lastLoadGameNumber + offset;
10081     if (lastLoadGameFP == NULL) {
10082         DisplayError(_("No game has been loaded yet"), 0);
10083         return FALSE;
10084     }
10085     if (gameNumber <= 0) {
10086         DisplayError(_("Can't back up any further"), 0);
10087         return FALSE;
10088     }
10089     if (cmailMsgLoaded) {
10090         return CmailLoadGame(lastLoadGameFP, gameNumber,
10091                              lastLoadGameTitle, lastLoadGameUseList);
10092     } else {
10093         return LoadGame(lastLoadGameFP, gameNumber,
10094                         lastLoadGameTitle, lastLoadGameUseList);
10095     }
10096 }
10097
10098
10099
10100 /* Load the nth game from open file f */
10101 int
10102 LoadGame(f, gameNumber, title, useList)
10103      FILE *f;
10104      int gameNumber;
10105      char *title;
10106      int useList;
10107 {
10108     ChessMove cm;
10109     char buf[MSG_SIZ];
10110     int gn = gameNumber;
10111     ListGame *lg = NULL;
10112     int numPGNTags = 0;
10113     int err;
10114     GameMode oldGameMode;
10115     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10116
10117     if (appData.debugMode)
10118         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10119
10120     if (gameMode == Training )
10121         SetTrainingModeOff();
10122
10123     oldGameMode = gameMode;
10124     if (gameMode != BeginningOfGame) {
10125       Reset(FALSE, TRUE);
10126     }
10127
10128     gameFileFP = f;
10129     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10130         fclose(lastLoadGameFP);
10131     }
10132
10133     if (useList) {
10134         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10135
10136         if (lg) {
10137             fseek(f, lg->offset, 0);
10138             GameListHighlight(gameNumber);
10139             gn = 1;
10140         }
10141         else {
10142             DisplayError(_("Game number out of range"), 0);
10143             return FALSE;
10144         }
10145     } else {
10146         GameListDestroy();
10147         if (fseek(f, 0, 0) == -1) {
10148             if (f == lastLoadGameFP ?
10149                 gameNumber == lastLoadGameNumber + 1 :
10150                 gameNumber == 1) {
10151                 gn = 1;
10152             } else {
10153                 DisplayError(_("Can't seek on game file"), 0);
10154                 return FALSE;
10155             }
10156         }
10157     }
10158     lastLoadGameFP = f;
10159     lastLoadGameNumber = gameNumber;
10160     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10161     lastLoadGameUseList = useList;
10162
10163     yynewfile(f);
10164
10165     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10166       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10167                 lg->gameInfo.black);
10168             DisplayTitle(buf);
10169     } else if (*title != NULLCHAR) {
10170         if (gameNumber > 1) {
10171           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10172             DisplayTitle(buf);
10173         } else {
10174             DisplayTitle(title);
10175         }
10176     }
10177
10178     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10179         gameMode = PlayFromGameFile;
10180         ModeHighlight();
10181     }
10182
10183     currentMove = forwardMostMove = backwardMostMove = 0;
10184     CopyBoard(boards[0], initialPosition);
10185     StopClocks();
10186
10187     /*
10188      * Skip the first gn-1 games in the file.
10189      * Also skip over anything that precedes an identifiable
10190      * start of game marker, to avoid being confused by
10191      * garbage at the start of the file.  Currently
10192      * recognized start of game markers are the move number "1",
10193      * the pattern "gnuchess .* game", the pattern
10194      * "^[#;%] [^ ]* game file", and a PGN tag block.
10195      * A game that starts with one of the latter two patterns
10196      * will also have a move number 1, possibly
10197      * following a position diagram.
10198      * 5-4-02: Let's try being more lenient and allowing a game to
10199      * start with an unnumbered move.  Does that break anything?
10200      */
10201     cm = lastLoadGameStart = EndOfFile;
10202     while (gn > 0) {
10203         yyboardindex = forwardMostMove;
10204         cm = (ChessMove) Myylex();
10205         switch (cm) {
10206           case EndOfFile:
10207             if (cmailMsgLoaded) {
10208                 nCmailGames = CMAIL_MAX_GAMES - gn;
10209             } else {
10210                 Reset(TRUE, TRUE);
10211                 DisplayError(_("Game not found in file"), 0);
10212             }
10213             return FALSE;
10214
10215           case GNUChessGame:
10216           case XBoardGame:
10217             gn--;
10218             lastLoadGameStart = cm;
10219             break;
10220
10221           case MoveNumberOne:
10222             switch (lastLoadGameStart) {
10223               case GNUChessGame:
10224               case XBoardGame:
10225               case PGNTag:
10226                 break;
10227               case MoveNumberOne:
10228               case EndOfFile:
10229                 gn--;           /* count this game */
10230                 lastLoadGameStart = cm;
10231                 break;
10232               default:
10233                 /* impossible */
10234                 break;
10235             }
10236             break;
10237
10238           case PGNTag:
10239             switch (lastLoadGameStart) {
10240               case GNUChessGame:
10241               case PGNTag:
10242               case MoveNumberOne:
10243               case EndOfFile:
10244                 gn--;           /* count this game */
10245                 lastLoadGameStart = cm;
10246                 break;
10247               case XBoardGame:
10248                 lastLoadGameStart = cm; /* game counted already */
10249                 break;
10250               default:
10251                 /* impossible */
10252                 break;
10253             }
10254             if (gn > 0) {
10255                 do {
10256                     yyboardindex = forwardMostMove;
10257                     cm = (ChessMove) Myylex();
10258                 } while (cm == PGNTag || cm == Comment);
10259             }
10260             break;
10261
10262           case WhiteWins:
10263           case BlackWins:
10264           case GameIsDrawn:
10265             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10266                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10267                     != CMAIL_OLD_RESULT) {
10268                     nCmailResults ++ ;
10269                     cmailResult[  CMAIL_MAX_GAMES
10270                                 - gn - 1] = CMAIL_OLD_RESULT;
10271                 }
10272             }
10273             break;
10274
10275           case NormalMove:
10276             /* Only a NormalMove can be at the start of a game
10277              * without a position diagram. */
10278             if (lastLoadGameStart == EndOfFile ) {
10279               gn--;
10280               lastLoadGameStart = MoveNumberOne;
10281             }
10282             break;
10283
10284           default:
10285             break;
10286         }
10287     }
10288
10289     if (appData.debugMode)
10290       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10291
10292     if (cm == XBoardGame) {
10293         /* Skip any header junk before position diagram and/or move 1 */
10294         for (;;) {
10295             yyboardindex = forwardMostMove;
10296             cm = (ChessMove) Myylex();
10297
10298             if (cm == EndOfFile ||
10299                 cm == GNUChessGame || cm == XBoardGame) {
10300                 /* Empty game; pretend end-of-file and handle later */
10301                 cm = EndOfFile;
10302                 break;
10303             }
10304
10305             if (cm == MoveNumberOne || cm == PositionDiagram ||
10306                 cm == PGNTag || cm == Comment)
10307               break;
10308         }
10309     } else if (cm == GNUChessGame) {
10310         if (gameInfo.event != NULL) {
10311             free(gameInfo.event);
10312         }
10313         gameInfo.event = StrSave(yy_text);
10314     }
10315
10316     startedFromSetupPosition = FALSE;
10317     while (cm == PGNTag) {
10318         if (appData.debugMode)
10319           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10320         err = ParsePGNTag(yy_text, &gameInfo);
10321         if (!err) numPGNTags++;
10322
10323         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10324         if(gameInfo.variant != oldVariant) {
10325             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10326             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10327             InitPosition(TRUE);
10328             oldVariant = gameInfo.variant;
10329             if (appData.debugMode)
10330               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10331         }
10332
10333
10334         if (gameInfo.fen != NULL) {
10335           Board initial_position;
10336           startedFromSetupPosition = TRUE;
10337           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10338             Reset(TRUE, TRUE);
10339             DisplayError(_("Bad FEN position in file"), 0);
10340             return FALSE;
10341           }
10342           CopyBoard(boards[0], initial_position);
10343           if (blackPlaysFirst) {
10344             currentMove = forwardMostMove = backwardMostMove = 1;
10345             CopyBoard(boards[1], initial_position);
10346             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10347             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10348             timeRemaining[0][1] = whiteTimeRemaining;
10349             timeRemaining[1][1] = blackTimeRemaining;
10350             if (commentList[0] != NULL) {
10351               commentList[1] = commentList[0];
10352               commentList[0] = NULL;
10353             }
10354           } else {
10355             currentMove = forwardMostMove = backwardMostMove = 0;
10356           }
10357           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10358           {   int i;
10359               initialRulePlies = FENrulePlies;
10360               for( i=0; i< nrCastlingRights; i++ )
10361                   initialRights[i] = initial_position[CASTLING][i];
10362           }
10363           yyboardindex = forwardMostMove;
10364           free(gameInfo.fen);
10365           gameInfo.fen = NULL;
10366         }
10367
10368         yyboardindex = forwardMostMove;
10369         cm = (ChessMove) Myylex();
10370
10371         /* Handle comments interspersed among the tags */
10372         while (cm == Comment) {
10373             char *p;
10374             if (appData.debugMode)
10375               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10376             p = yy_text;
10377             AppendComment(currentMove, p, FALSE);
10378             yyboardindex = forwardMostMove;
10379             cm = (ChessMove) Myylex();
10380         }
10381     }
10382
10383     /* don't rely on existence of Event tag since if game was
10384      * pasted from clipboard the Event tag may not exist
10385      */
10386     if (numPGNTags > 0){
10387         char *tags;
10388         if (gameInfo.variant == VariantNormal) {
10389           VariantClass v = StringToVariant(gameInfo.event);
10390           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10391           if(v < VariantShogi) gameInfo.variant = v;
10392         }
10393         if (!matchMode) {
10394           if( appData.autoDisplayTags ) {
10395             tags = PGNTags(&gameInfo);
10396             TagsPopUp(tags, CmailMsg());
10397             free(tags);
10398           }
10399         }
10400     } else {
10401         /* Make something up, but don't display it now */
10402         SetGameInfo();
10403         TagsPopDown();
10404     }
10405
10406     if (cm == PositionDiagram) {
10407         int i, j;
10408         char *p;
10409         Board initial_position;
10410
10411         if (appData.debugMode)
10412           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10413
10414         if (!startedFromSetupPosition) {
10415             p = yy_text;
10416             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10417               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10418                 switch (*p) {
10419                   case '[':
10420                   case '-':
10421                   case ' ':
10422                   case '\t':
10423                   case '\n':
10424                   case '\r':
10425                     break;
10426                   default:
10427                     initial_position[i][j++] = CharToPiece(*p);
10428                     break;
10429                 }
10430             while (*p == ' ' || *p == '\t' ||
10431                    *p == '\n' || *p == '\r') p++;
10432
10433             if (strncmp(p, "black", strlen("black"))==0)
10434               blackPlaysFirst = TRUE;
10435             else
10436               blackPlaysFirst = FALSE;
10437             startedFromSetupPosition = TRUE;
10438
10439             CopyBoard(boards[0], initial_position);
10440             if (blackPlaysFirst) {
10441                 currentMove = forwardMostMove = backwardMostMove = 1;
10442                 CopyBoard(boards[1], initial_position);
10443                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10444                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10445                 timeRemaining[0][1] = whiteTimeRemaining;
10446                 timeRemaining[1][1] = blackTimeRemaining;
10447                 if (commentList[0] != NULL) {
10448                     commentList[1] = commentList[0];
10449                     commentList[0] = NULL;
10450                 }
10451             } else {
10452                 currentMove = forwardMostMove = backwardMostMove = 0;
10453             }
10454         }
10455         yyboardindex = forwardMostMove;
10456         cm = (ChessMove) Myylex();
10457     }
10458
10459     if (first.pr == NoProc) {
10460         StartChessProgram(&first);
10461     }
10462     InitChessProgram(&first, FALSE);
10463     SendToProgram("force\n", &first);
10464     if (startedFromSetupPosition) {
10465         SendBoard(&first, forwardMostMove);
10466     if (appData.debugMode) {
10467         fprintf(debugFP, "Load Game\n");
10468     }
10469         DisplayBothClocks();
10470     }
10471
10472     /* [HGM] server: flag to write setup moves in broadcast file as one */
10473     loadFlag = appData.suppressLoadMoves;
10474
10475     while (cm == Comment) {
10476         char *p;
10477         if (appData.debugMode)
10478           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10479         p = yy_text;
10480         AppendComment(currentMove, p, FALSE);
10481         yyboardindex = forwardMostMove;
10482         cm = (ChessMove) Myylex();
10483     }
10484
10485     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10486         cm == WhiteWins || cm == BlackWins ||
10487         cm == GameIsDrawn || cm == GameUnfinished) {
10488         DisplayMessage("", _("No moves in game"));
10489         if (cmailMsgLoaded) {
10490             if (appData.debugMode)
10491               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10492             ClearHighlights();
10493             flipView = FALSE;
10494         }
10495         DrawPosition(FALSE, boards[currentMove]);
10496         DisplayBothClocks();
10497         gameMode = EditGame;
10498         ModeHighlight();
10499         gameFileFP = NULL;
10500         cmailOldMove = 0;
10501         return TRUE;
10502     }
10503
10504     // [HGM] PV info: routine tests if comment empty
10505     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10506         DisplayComment(currentMove - 1, commentList[currentMove]);
10507     }
10508     if (!matchMode && appData.timeDelay != 0)
10509       DrawPosition(FALSE, boards[currentMove]);
10510
10511     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10512       programStats.ok_to_send = 1;
10513     }
10514
10515     /* if the first token after the PGN tags is a move
10516      * and not move number 1, retrieve it from the parser
10517      */
10518     if (cm != MoveNumberOne)
10519         LoadGameOneMove(cm);
10520
10521     /* load the remaining moves from the file */
10522     while (LoadGameOneMove(EndOfFile)) {
10523       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10524       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10525     }
10526
10527     /* rewind to the start of the game */
10528     currentMove = backwardMostMove;
10529
10530     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10531
10532     if (oldGameMode == AnalyzeFile ||
10533         oldGameMode == AnalyzeMode) {
10534       AnalyzeFileEvent();
10535     }
10536
10537     if (matchMode || appData.timeDelay == 0) {
10538       ToEndEvent();
10539       gameMode = EditGame;
10540       ModeHighlight();
10541     } else if (appData.timeDelay > 0) {
10542       AutoPlayGameLoop();
10543     }
10544
10545     if (appData.debugMode)
10546         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10547
10548     loadFlag = 0; /* [HGM] true game starts */
10549     return TRUE;
10550 }
10551
10552 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10553 int
10554 ReloadPosition(offset)
10555      int offset;
10556 {
10557     int positionNumber = lastLoadPositionNumber + offset;
10558     if (lastLoadPositionFP == NULL) {
10559         DisplayError(_("No position has been loaded yet"), 0);
10560         return FALSE;
10561     }
10562     if (positionNumber <= 0) {
10563         DisplayError(_("Can't back up any further"), 0);
10564         return FALSE;
10565     }
10566     return LoadPosition(lastLoadPositionFP, positionNumber,
10567                         lastLoadPositionTitle);
10568 }
10569
10570 /* Load the nth position from the given file */
10571 int
10572 LoadPositionFromFile(filename, n, title)
10573      char *filename;
10574      int n;
10575      char *title;
10576 {
10577     FILE *f;
10578     char buf[MSG_SIZ];
10579
10580     if (strcmp(filename, "-") == 0) {
10581         return LoadPosition(stdin, n, "stdin");
10582     } else {
10583         f = fopen(filename, "rb");
10584         if (f == NULL) {
10585             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10586             DisplayError(buf, errno);
10587             return FALSE;
10588         } else {
10589             return LoadPosition(f, n, title);
10590         }
10591     }
10592 }
10593
10594 /* Load the nth position from the given open file, and close it */
10595 int
10596 LoadPosition(f, positionNumber, title)
10597      FILE *f;
10598      int positionNumber;
10599      char *title;
10600 {
10601     char *p, line[MSG_SIZ];
10602     Board initial_position;
10603     int i, j, fenMode, pn;
10604
10605     if (gameMode == Training )
10606         SetTrainingModeOff();
10607
10608     if (gameMode != BeginningOfGame) {
10609         Reset(FALSE, TRUE);
10610     }
10611     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10612         fclose(lastLoadPositionFP);
10613     }
10614     if (positionNumber == 0) positionNumber = 1;
10615     lastLoadPositionFP = f;
10616     lastLoadPositionNumber = positionNumber;
10617     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10618     if (first.pr == NoProc) {
10619       StartChessProgram(&first);
10620       InitChessProgram(&first, FALSE);
10621     }
10622     pn = positionNumber;
10623     if (positionNumber < 0) {
10624         /* Negative position number means to seek to that byte offset */
10625         if (fseek(f, -positionNumber, 0) == -1) {
10626             DisplayError(_("Can't seek on position file"), 0);
10627             return FALSE;
10628         };
10629         pn = 1;
10630     } else {
10631         if (fseek(f, 0, 0) == -1) {
10632             if (f == lastLoadPositionFP ?
10633                 positionNumber == lastLoadPositionNumber + 1 :
10634                 positionNumber == 1) {
10635                 pn = 1;
10636             } else {
10637                 DisplayError(_("Can't seek on position file"), 0);
10638                 return FALSE;
10639             }
10640         }
10641     }
10642     /* See if this file is FEN or old-style xboard */
10643     if (fgets(line, MSG_SIZ, f) == NULL) {
10644         DisplayError(_("Position not found in file"), 0);
10645         return FALSE;
10646     }
10647     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10648     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10649
10650     if (pn >= 2) {
10651         if (fenMode || line[0] == '#') pn--;
10652         while (pn > 0) {
10653             /* skip positions before number pn */
10654             if (fgets(line, MSG_SIZ, f) == NULL) {
10655                 Reset(TRUE, TRUE);
10656                 DisplayError(_("Position not found in file"), 0);
10657                 return FALSE;
10658             }
10659             if (fenMode || line[0] == '#') pn--;
10660         }
10661     }
10662
10663     if (fenMode) {
10664         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10665             DisplayError(_("Bad FEN position in file"), 0);
10666             return FALSE;
10667         }
10668     } else {
10669         (void) fgets(line, MSG_SIZ, f);
10670         (void) fgets(line, MSG_SIZ, f);
10671
10672         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10673             (void) fgets(line, MSG_SIZ, f);
10674             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10675                 if (*p == ' ')
10676                   continue;
10677                 initial_position[i][j++] = CharToPiece(*p);
10678             }
10679         }
10680
10681         blackPlaysFirst = FALSE;
10682         if (!feof(f)) {
10683             (void) fgets(line, MSG_SIZ, f);
10684             if (strncmp(line, "black", strlen("black"))==0)
10685               blackPlaysFirst = TRUE;
10686         }
10687     }
10688     startedFromSetupPosition = TRUE;
10689
10690     SendToProgram("force\n", &first);
10691     CopyBoard(boards[0], initial_position);
10692     if (blackPlaysFirst) {
10693         currentMove = forwardMostMove = backwardMostMove = 1;
10694         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10695         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10696         CopyBoard(boards[1], initial_position);
10697         DisplayMessage("", _("Black to play"));
10698     } else {
10699         currentMove = forwardMostMove = backwardMostMove = 0;
10700         DisplayMessage("", _("White to play"));
10701     }
10702     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10703     SendBoard(&first, forwardMostMove);
10704     if (appData.debugMode) {
10705 int i, j;
10706   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10707   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10708         fprintf(debugFP, "Load Position\n");
10709     }
10710
10711     if (positionNumber > 1) {
10712       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10713         DisplayTitle(line);
10714     } else {
10715         DisplayTitle(title);
10716     }
10717     gameMode = EditGame;
10718     ModeHighlight();
10719     ResetClocks();
10720     timeRemaining[0][1] = whiteTimeRemaining;
10721     timeRemaining[1][1] = blackTimeRemaining;
10722     DrawPosition(FALSE, boards[currentMove]);
10723
10724     return TRUE;
10725 }
10726
10727
10728 void
10729 CopyPlayerNameIntoFileName(dest, src)
10730      char **dest, *src;
10731 {
10732     while (*src != NULLCHAR && *src != ',') {
10733         if (*src == ' ') {
10734             *(*dest)++ = '_';
10735             src++;
10736         } else {
10737             *(*dest)++ = *src++;
10738         }
10739     }
10740 }
10741
10742 char *DefaultFileName(ext)
10743      char *ext;
10744 {
10745     static char def[MSG_SIZ];
10746     char *p;
10747
10748     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10749         p = def;
10750         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10751         *p++ = '-';
10752         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10753         *p++ = '.';
10754         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10755     } else {
10756         def[0] = NULLCHAR;
10757     }
10758     return def;
10759 }
10760
10761 /* Save the current game to the given file */
10762 int
10763 SaveGameToFile(filename, append)
10764      char *filename;
10765      int append;
10766 {
10767     FILE *f;
10768     char buf[MSG_SIZ];
10769
10770     if (strcmp(filename, "-") == 0) {
10771         return SaveGame(stdout, 0, NULL);
10772     } else {
10773         f = fopen(filename, append ? "a" : "w");
10774         if (f == NULL) {
10775             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10776             DisplayError(buf, errno);
10777             return FALSE;
10778         } else {
10779             return SaveGame(f, 0, NULL);
10780         }
10781     }
10782 }
10783
10784 char *
10785 SavePart(str)
10786      char *str;
10787 {
10788     static char buf[MSG_SIZ];
10789     char *p;
10790
10791     p = strchr(str, ' ');
10792     if (p == NULL) return str;
10793     strncpy(buf, str, p - str);
10794     buf[p - str] = NULLCHAR;
10795     return buf;
10796 }
10797
10798 #define PGN_MAX_LINE 75
10799
10800 #define PGN_SIDE_WHITE  0
10801 #define PGN_SIDE_BLACK  1
10802
10803 /* [AS] */
10804 static int FindFirstMoveOutOfBook( int side )
10805 {
10806     int result = -1;
10807
10808     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10809         int index = backwardMostMove;
10810         int has_book_hit = 0;
10811
10812         if( (index % 2) != side ) {
10813             index++;
10814         }
10815
10816         while( index < forwardMostMove ) {
10817             /* Check to see if engine is in book */
10818             int depth = pvInfoList[index].depth;
10819             int score = pvInfoList[index].score;
10820             int in_book = 0;
10821
10822             if( depth <= 2 ) {
10823                 in_book = 1;
10824             }
10825             else if( score == 0 && depth == 63 ) {
10826                 in_book = 1; /* Zappa */
10827             }
10828             else if( score == 2 && depth == 99 ) {
10829                 in_book = 1; /* Abrok */
10830             }
10831
10832             has_book_hit += in_book;
10833
10834             if( ! in_book ) {
10835                 result = index;
10836
10837                 break;
10838             }
10839
10840             index += 2;
10841         }
10842     }
10843
10844     return result;
10845 }
10846
10847 /* [AS] */
10848 void GetOutOfBookInfo( char * buf )
10849 {
10850     int oob[2];
10851     int i;
10852     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10853
10854     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10855     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10856
10857     *buf = '\0';
10858
10859     if( oob[0] >= 0 || oob[1] >= 0 ) {
10860         for( i=0; i<2; i++ ) {
10861             int idx = oob[i];
10862
10863             if( idx >= 0 ) {
10864                 if( i > 0 && oob[0] >= 0 ) {
10865                     strcat( buf, "   " );
10866                 }
10867
10868                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10869                 sprintf( buf+strlen(buf), "%s%.2f",
10870                     pvInfoList[idx].score >= 0 ? "+" : "",
10871                     pvInfoList[idx].score / 100.0 );
10872             }
10873         }
10874     }
10875 }
10876
10877 /* Save game in PGN style and close the file */
10878 int
10879 SaveGamePGN(f)
10880      FILE *f;
10881 {
10882     int i, offset, linelen, newblock;
10883     time_t tm;
10884 //    char *movetext;
10885     char numtext[32];
10886     int movelen, numlen, blank;
10887     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10888
10889     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10890
10891     tm = time((time_t *) NULL);
10892
10893     PrintPGNTags(f, &gameInfo);
10894
10895     if (backwardMostMove > 0 || startedFromSetupPosition) {
10896         char *fen = PositionToFEN(backwardMostMove, NULL);
10897         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10898         fprintf(f, "\n{--------------\n");
10899         PrintPosition(f, backwardMostMove);
10900         fprintf(f, "--------------}\n");
10901         free(fen);
10902     }
10903     else {
10904         /* [AS] Out of book annotation */
10905         if( appData.saveOutOfBookInfo ) {
10906             char buf[64];
10907
10908             GetOutOfBookInfo( buf );
10909
10910             if( buf[0] != '\0' ) {
10911                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10912             }
10913         }
10914
10915         fprintf(f, "\n");
10916     }
10917
10918     i = backwardMostMove;
10919     linelen = 0;
10920     newblock = TRUE;
10921
10922     while (i < forwardMostMove) {
10923         /* Print comments preceding this move */
10924         if (commentList[i] != NULL) {
10925             if (linelen > 0) fprintf(f, "\n");
10926             fprintf(f, "%s", commentList[i]);
10927             linelen = 0;
10928             newblock = TRUE;
10929         }
10930
10931         /* Format move number */
10932         if ((i % 2) == 0)
10933           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10934         else
10935           if (newblock)
10936             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10937           else
10938             numtext[0] = NULLCHAR;
10939
10940         numlen = strlen(numtext);
10941         newblock = FALSE;
10942
10943         /* Print move number */
10944         blank = linelen > 0 && numlen > 0;
10945         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10946             fprintf(f, "\n");
10947             linelen = 0;
10948             blank = 0;
10949         }
10950         if (blank) {
10951             fprintf(f, " ");
10952             linelen++;
10953         }
10954         fprintf(f, "%s", numtext);
10955         linelen += numlen;
10956
10957         /* Get move */
10958         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10959         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10960
10961         /* Print move */
10962         blank = linelen > 0 && movelen > 0;
10963         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10964             fprintf(f, "\n");
10965             linelen = 0;
10966             blank = 0;
10967         }
10968         if (blank) {
10969             fprintf(f, " ");
10970             linelen++;
10971         }
10972         fprintf(f, "%s", move_buffer);
10973         linelen += movelen;
10974
10975         /* [AS] Add PV info if present */
10976         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10977             /* [HGM] add time */
10978             char buf[MSG_SIZ]; int seconds;
10979
10980             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10981
10982             if( seconds <= 0)
10983               buf[0] = 0;
10984             else
10985               if( seconds < 30 )
10986                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10987               else
10988                 {
10989                   seconds = (seconds + 4)/10; // round to full seconds
10990                   if( seconds < 60 )
10991                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10992                   else
10993                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10994                 }
10995
10996             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10997                       pvInfoList[i].score >= 0 ? "+" : "",
10998                       pvInfoList[i].score / 100.0,
10999                       pvInfoList[i].depth,
11000                       buf );
11001
11002             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11003
11004             /* Print score/depth */
11005             blank = linelen > 0 && movelen > 0;
11006             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11007                 fprintf(f, "\n");
11008                 linelen = 0;
11009                 blank = 0;
11010             }
11011             if (blank) {
11012                 fprintf(f, " ");
11013                 linelen++;
11014             }
11015             fprintf(f, "%s", move_buffer);
11016             linelen += movelen;
11017         }
11018
11019         i++;
11020     }
11021
11022     /* Start a new line */
11023     if (linelen > 0) fprintf(f, "\n");
11024
11025     /* Print comments after last move */
11026     if (commentList[i] != NULL) {
11027         fprintf(f, "%s\n", commentList[i]);
11028     }
11029
11030     /* Print result */
11031     if (gameInfo.resultDetails != NULL &&
11032         gameInfo.resultDetails[0] != NULLCHAR) {
11033         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11034                 PGNResult(gameInfo.result));
11035     } else {
11036         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11037     }
11038
11039     fclose(f);
11040     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11041     return TRUE;
11042 }
11043
11044 /* Save game in old style and close the file */
11045 int
11046 SaveGameOldStyle(f)
11047      FILE *f;
11048 {
11049     int i, offset;
11050     time_t tm;
11051
11052     tm = time((time_t *) NULL);
11053
11054     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11055     PrintOpponents(f);
11056
11057     if (backwardMostMove > 0 || startedFromSetupPosition) {
11058         fprintf(f, "\n[--------------\n");
11059         PrintPosition(f, backwardMostMove);
11060         fprintf(f, "--------------]\n");
11061     } else {
11062         fprintf(f, "\n");
11063     }
11064
11065     i = backwardMostMove;
11066     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11067
11068     while (i < forwardMostMove) {
11069         if (commentList[i] != NULL) {
11070             fprintf(f, "[%s]\n", commentList[i]);
11071         }
11072
11073         if ((i % 2) == 1) {
11074             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11075             i++;
11076         } else {
11077             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11078             i++;
11079             if (commentList[i] != NULL) {
11080                 fprintf(f, "\n");
11081                 continue;
11082             }
11083             if (i >= forwardMostMove) {
11084                 fprintf(f, "\n");
11085                 break;
11086             }
11087             fprintf(f, "%s\n", parseList[i]);
11088             i++;
11089         }
11090     }
11091
11092     if (commentList[i] != NULL) {
11093         fprintf(f, "[%s]\n", commentList[i]);
11094     }
11095
11096     /* This isn't really the old style, but it's close enough */
11097     if (gameInfo.resultDetails != NULL &&
11098         gameInfo.resultDetails[0] != NULLCHAR) {
11099         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11100                 gameInfo.resultDetails);
11101     } else {
11102         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11103     }
11104
11105     fclose(f);
11106     return TRUE;
11107 }
11108
11109 /* Save the current game to open file f and close the file */
11110 int
11111 SaveGame(f, dummy, dummy2)
11112      FILE *f;
11113      int dummy;
11114      char *dummy2;
11115 {
11116     if (gameMode == EditPosition) EditPositionDone(TRUE);
11117     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11118     if (appData.oldSaveStyle)
11119       return SaveGameOldStyle(f);
11120     else
11121       return SaveGamePGN(f);
11122 }
11123
11124 /* Save the current position to the given file */
11125 int
11126 SavePositionToFile(filename)
11127      char *filename;
11128 {
11129     FILE *f;
11130     char buf[MSG_SIZ];
11131
11132     if (strcmp(filename, "-") == 0) {
11133         return SavePosition(stdout, 0, NULL);
11134     } else {
11135         f = fopen(filename, "a");
11136         if (f == NULL) {
11137             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11138             DisplayError(buf, errno);
11139             return FALSE;
11140         } else {
11141             SavePosition(f, 0, NULL);
11142             return TRUE;
11143         }
11144     }
11145 }
11146
11147 /* Save the current position to the given open file and close the file */
11148 int
11149 SavePosition(f, dummy, dummy2)
11150      FILE *f;
11151      int dummy;
11152      char *dummy2;
11153 {
11154     time_t tm;
11155     char *fen;
11156
11157     if (gameMode == EditPosition) EditPositionDone(TRUE);
11158     if (appData.oldSaveStyle) {
11159         tm = time((time_t *) NULL);
11160
11161         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11162         PrintOpponents(f);
11163         fprintf(f, "[--------------\n");
11164         PrintPosition(f, currentMove);
11165         fprintf(f, "--------------]\n");
11166     } else {
11167         fen = PositionToFEN(currentMove, NULL);
11168         fprintf(f, "%s\n", fen);
11169         free(fen);
11170     }
11171     fclose(f);
11172     return TRUE;
11173 }
11174
11175 void
11176 ReloadCmailMsgEvent(unregister)
11177      int unregister;
11178 {
11179 #if !WIN32
11180     static char *inFilename = NULL;
11181     static char *outFilename;
11182     int i;
11183     struct stat inbuf, outbuf;
11184     int status;
11185
11186     /* Any registered moves are unregistered if unregister is set, */
11187     /* i.e. invoked by the signal handler */
11188     if (unregister) {
11189         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11190             cmailMoveRegistered[i] = FALSE;
11191             if (cmailCommentList[i] != NULL) {
11192                 free(cmailCommentList[i]);
11193                 cmailCommentList[i] = NULL;
11194             }
11195         }
11196         nCmailMovesRegistered = 0;
11197     }
11198
11199     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11200         cmailResult[i] = CMAIL_NOT_RESULT;
11201     }
11202     nCmailResults = 0;
11203
11204     if (inFilename == NULL) {
11205         /* Because the filenames are static they only get malloced once  */
11206         /* and they never get freed                                      */
11207         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11208         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11209
11210         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11211         sprintf(outFilename, "%s.out", appData.cmailGameName);
11212     }
11213
11214     status = stat(outFilename, &outbuf);
11215     if (status < 0) {
11216         cmailMailedMove = FALSE;
11217     } else {
11218         status = stat(inFilename, &inbuf);
11219         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11220     }
11221
11222     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11223        counts the games, notes how each one terminated, etc.
11224
11225        It would be nice to remove this kludge and instead gather all
11226        the information while building the game list.  (And to keep it
11227        in the game list nodes instead of having a bunch of fixed-size
11228        parallel arrays.)  Note this will require getting each game's
11229        termination from the PGN tags, as the game list builder does
11230        not process the game moves.  --mann
11231        */
11232     cmailMsgLoaded = TRUE;
11233     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11234
11235     /* Load first game in the file or popup game menu */
11236     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11237
11238 #endif /* !WIN32 */
11239     return;
11240 }
11241
11242 int
11243 RegisterMove()
11244 {
11245     FILE *f;
11246     char string[MSG_SIZ];
11247
11248     if (   cmailMailedMove
11249         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11250         return TRUE;            /* Allow free viewing  */
11251     }
11252
11253     /* Unregister move to ensure that we don't leave RegisterMove        */
11254     /* with the move registered when the conditions for registering no   */
11255     /* longer hold                                                       */
11256     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11257         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11258         nCmailMovesRegistered --;
11259
11260         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11261           {
11262               free(cmailCommentList[lastLoadGameNumber - 1]);
11263               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11264           }
11265     }
11266
11267     if (cmailOldMove == -1) {
11268         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11269         return FALSE;
11270     }
11271
11272     if (currentMove > cmailOldMove + 1) {
11273         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11274         return FALSE;
11275     }
11276
11277     if (currentMove < cmailOldMove) {
11278         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11279         return FALSE;
11280     }
11281
11282     if (forwardMostMove > currentMove) {
11283         /* Silently truncate extra moves */
11284         TruncateGame();
11285     }
11286
11287     if (   (currentMove == cmailOldMove + 1)
11288         || (   (currentMove == cmailOldMove)
11289             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11290                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11291         if (gameInfo.result != GameUnfinished) {
11292             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11293         }
11294
11295         if (commentList[currentMove] != NULL) {
11296             cmailCommentList[lastLoadGameNumber - 1]
11297               = StrSave(commentList[currentMove]);
11298         }
11299         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11300
11301         if (appData.debugMode)
11302           fprintf(debugFP, "Saving %s for game %d\n",
11303                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11304
11305         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11306
11307         f = fopen(string, "w");
11308         if (appData.oldSaveStyle) {
11309             SaveGameOldStyle(f); /* also closes the file */
11310
11311             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11312             f = fopen(string, "w");
11313             SavePosition(f, 0, NULL); /* also closes the file */
11314         } else {
11315             fprintf(f, "{--------------\n");
11316             PrintPosition(f, currentMove);
11317             fprintf(f, "--------------}\n\n");
11318
11319             SaveGame(f, 0, NULL); /* also closes the file*/
11320         }
11321
11322         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11323         nCmailMovesRegistered ++;
11324     } else if (nCmailGames == 1) {
11325         DisplayError(_("You have not made a move yet"), 0);
11326         return FALSE;
11327     }
11328
11329     return TRUE;
11330 }
11331
11332 void
11333 MailMoveEvent()
11334 {
11335 #if !WIN32
11336     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11337     FILE *commandOutput;
11338     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11339     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11340     int nBuffers;
11341     int i;
11342     int archived;
11343     char *arcDir;
11344
11345     if (! cmailMsgLoaded) {
11346         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11347         return;
11348     }
11349
11350     if (nCmailGames == nCmailResults) {
11351         DisplayError(_("No unfinished games"), 0);
11352         return;
11353     }
11354
11355 #if CMAIL_PROHIBIT_REMAIL
11356     if (cmailMailedMove) {
11357       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);
11358         DisplayError(msg, 0);
11359         return;
11360     }
11361 #endif
11362
11363     if (! (cmailMailedMove || RegisterMove())) return;
11364
11365     if (   cmailMailedMove
11366         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11367       snprintf(string, MSG_SIZ, partCommandString,
11368                appData.debugMode ? " -v" : "", appData.cmailGameName);
11369         commandOutput = popen(string, "r");
11370
11371         if (commandOutput == NULL) {
11372             DisplayError(_("Failed to invoke cmail"), 0);
11373         } else {
11374             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11375                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11376             }
11377             if (nBuffers > 1) {
11378                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11379                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11380                 nBytes = MSG_SIZ - 1;
11381             } else {
11382                 (void) memcpy(msg, buffer, nBytes);
11383             }
11384             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11385
11386             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11387                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11388
11389                 archived = TRUE;
11390                 for (i = 0; i < nCmailGames; i ++) {
11391                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11392                         archived = FALSE;
11393                     }
11394                 }
11395                 if (   archived
11396                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11397                         != NULL)) {
11398                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11399                            arcDir,
11400                            appData.cmailGameName,
11401                            gameInfo.date);
11402                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11403                     cmailMsgLoaded = FALSE;
11404                 }
11405             }
11406
11407             DisplayInformation(msg);
11408             pclose(commandOutput);
11409         }
11410     } else {
11411         if ((*cmailMsg) != '\0') {
11412             DisplayInformation(cmailMsg);
11413         }
11414     }
11415
11416     return;
11417 #endif /* !WIN32 */
11418 }
11419
11420 char *
11421 CmailMsg()
11422 {
11423 #if WIN32
11424     return NULL;
11425 #else
11426     int  prependComma = 0;
11427     char number[5];
11428     char string[MSG_SIZ];       /* Space for game-list */
11429     int  i;
11430
11431     if (!cmailMsgLoaded) return "";
11432
11433     if (cmailMailedMove) {
11434       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11435     } else {
11436         /* Create a list of games left */
11437       snprintf(string, MSG_SIZ, "[");
11438         for (i = 0; i < nCmailGames; i ++) {
11439             if (! (   cmailMoveRegistered[i]
11440                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11441                 if (prependComma) {
11442                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11443                 } else {
11444                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11445                     prependComma = 1;
11446                 }
11447
11448                 strcat(string, number);
11449             }
11450         }
11451         strcat(string, "]");
11452
11453         if (nCmailMovesRegistered + nCmailResults == 0) {
11454             switch (nCmailGames) {
11455               case 1:
11456                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11457                 break;
11458
11459               case 2:
11460                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11461                 break;
11462
11463               default:
11464                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11465                          nCmailGames);
11466                 break;
11467             }
11468         } else {
11469             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11470               case 1:
11471                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11472                          string);
11473                 break;
11474
11475               case 0:
11476                 if (nCmailResults == nCmailGames) {
11477                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11478                 } else {
11479                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11480                 }
11481                 break;
11482
11483               default:
11484                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11485                          string);
11486             }
11487         }
11488     }
11489     return cmailMsg;
11490 #endif /* WIN32 */
11491 }
11492
11493 void
11494 ResetGameEvent()
11495 {
11496     if (gameMode == Training)
11497       SetTrainingModeOff();
11498
11499     Reset(TRUE, TRUE);
11500     cmailMsgLoaded = FALSE;
11501     if (appData.icsActive) {
11502       SendToICS(ics_prefix);
11503       SendToICS("refresh\n");
11504     }
11505 }
11506
11507 void
11508 ExitEvent(status)
11509      int status;
11510 {
11511     exiting++;
11512     if (exiting > 2) {
11513       /* Give up on clean exit */
11514       exit(status);
11515     }
11516     if (exiting > 1) {
11517       /* Keep trying for clean exit */
11518       return;
11519     }
11520
11521     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11522
11523     if (telnetISR != NULL) {
11524       RemoveInputSource(telnetISR);
11525     }
11526     if (icsPR != NoProc) {
11527       DestroyChildProcess(icsPR, TRUE);
11528     }
11529
11530     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11531     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11532
11533     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11534     /* make sure this other one finishes before killing it!                  */
11535     if(endingGame) { int count = 0;
11536         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11537         while(endingGame && count++ < 10) DoSleep(1);
11538         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11539     }
11540
11541     /* Kill off chess programs */
11542     if (first.pr != NoProc) {
11543         ExitAnalyzeMode();
11544
11545         DoSleep( appData.delayBeforeQuit );
11546         SendToProgram("quit\n", &first);
11547         DoSleep( appData.delayAfterQuit );
11548         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11549     }
11550     if (second.pr != NoProc) {
11551         DoSleep( appData.delayBeforeQuit );
11552         SendToProgram("quit\n", &second);
11553         DoSleep( appData.delayAfterQuit );
11554         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11555     }
11556     if (first.isr != NULL) {
11557         RemoveInputSource(first.isr);
11558     }
11559     if (second.isr != NULL) {
11560         RemoveInputSource(second.isr);
11561     }
11562
11563     ShutDownFrontEnd();
11564     exit(status);
11565 }
11566
11567 void
11568 PauseEvent()
11569 {
11570     if (appData.debugMode)
11571         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11572     if (pausing) {
11573         pausing = FALSE;
11574         ModeHighlight();
11575         if (gameMode == MachinePlaysWhite ||
11576             gameMode == MachinePlaysBlack) {
11577             StartClocks();
11578         } else {
11579             DisplayBothClocks();
11580         }
11581         if (gameMode == PlayFromGameFile) {
11582             if (appData.timeDelay >= 0)
11583                 AutoPlayGameLoop();
11584         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11585             Reset(FALSE, TRUE);
11586             SendToICS(ics_prefix);
11587             SendToICS("refresh\n");
11588         } else if (currentMove < forwardMostMove) {
11589             ForwardInner(forwardMostMove);
11590         }
11591         pauseExamInvalid = FALSE;
11592     } else {
11593         switch (gameMode) {
11594           default:
11595             return;
11596           case IcsExamining:
11597             pauseExamForwardMostMove = forwardMostMove;
11598             pauseExamInvalid = FALSE;
11599             /* fall through */
11600           case IcsObserving:
11601           case IcsPlayingWhite:
11602           case IcsPlayingBlack:
11603             pausing = TRUE;
11604             ModeHighlight();
11605             return;
11606           case PlayFromGameFile:
11607             (void) StopLoadGameTimer();
11608             pausing = TRUE;
11609             ModeHighlight();
11610             break;
11611           case BeginningOfGame:
11612             if (appData.icsActive) return;
11613             /* else fall through */
11614           case MachinePlaysWhite:
11615           case MachinePlaysBlack:
11616           case TwoMachinesPlay:
11617             if (forwardMostMove == 0)
11618               return;           /* don't pause if no one has moved */
11619             if ((gameMode == MachinePlaysWhite &&
11620                  !WhiteOnMove(forwardMostMove)) ||
11621                 (gameMode == MachinePlaysBlack &&
11622                  WhiteOnMove(forwardMostMove))) {
11623                 StopClocks();
11624             }
11625             pausing = TRUE;
11626             ModeHighlight();
11627             break;
11628         }
11629     }
11630 }
11631
11632 void
11633 EditCommentEvent()
11634 {
11635     char title[MSG_SIZ];
11636
11637     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11638       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11639     } else {
11640       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11641                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11642                parseList[currentMove - 1]);
11643     }
11644
11645     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11646 }
11647
11648
11649 void
11650 EditTagsEvent()
11651 {
11652     char *tags = PGNTags(&gameInfo);
11653     EditTagsPopUp(tags, NULL);
11654     free(tags);
11655 }
11656
11657 void
11658 AnalyzeModeEvent()
11659 {
11660     if (appData.noChessProgram || gameMode == AnalyzeMode)
11661       return;
11662
11663     if (gameMode != AnalyzeFile) {
11664         if (!appData.icsEngineAnalyze) {
11665                EditGameEvent();
11666                if (gameMode != EditGame) return;
11667         }
11668         ResurrectChessProgram();
11669         SendToProgram("analyze\n", &first);
11670         first.analyzing = TRUE;
11671         /*first.maybeThinking = TRUE;*/
11672         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11673         EngineOutputPopUp();
11674     }
11675     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11676     pausing = FALSE;
11677     ModeHighlight();
11678     SetGameInfo();
11679
11680     StartAnalysisClock();
11681     GetTimeMark(&lastNodeCountTime);
11682     lastNodeCount = 0;
11683 }
11684
11685 void
11686 AnalyzeFileEvent()
11687 {
11688     if (appData.noChessProgram || gameMode == AnalyzeFile)
11689       return;
11690
11691     if (gameMode != AnalyzeMode) {
11692         EditGameEvent();
11693         if (gameMode != EditGame) return;
11694         ResurrectChessProgram();
11695         SendToProgram("analyze\n", &first);
11696         first.analyzing = TRUE;
11697         /*first.maybeThinking = TRUE;*/
11698         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11699         EngineOutputPopUp();
11700     }
11701     gameMode = AnalyzeFile;
11702     pausing = FALSE;
11703     ModeHighlight();
11704     SetGameInfo();
11705
11706     StartAnalysisClock();
11707     GetTimeMark(&lastNodeCountTime);
11708     lastNodeCount = 0;
11709 }
11710
11711 void
11712 MachineWhiteEvent()
11713 {
11714     char buf[MSG_SIZ];
11715     char *bookHit = NULL;
11716
11717     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11718       return;
11719
11720
11721     if (gameMode == PlayFromGameFile ||
11722         gameMode == TwoMachinesPlay  ||
11723         gameMode == Training         ||
11724         gameMode == AnalyzeMode      ||
11725         gameMode == EndOfGame)
11726         EditGameEvent();
11727
11728     if (gameMode == EditPosition)
11729         EditPositionDone(TRUE);
11730
11731     if (!WhiteOnMove(currentMove)) {
11732         DisplayError(_("It is not White's turn"), 0);
11733         return;
11734     }
11735
11736     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11737       ExitAnalyzeMode();
11738
11739     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11740         gameMode == AnalyzeFile)
11741         TruncateGame();
11742
11743     ResurrectChessProgram();    /* in case it isn't running */
11744     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11745         gameMode = MachinePlaysWhite;
11746         ResetClocks();
11747     } else
11748     gameMode = MachinePlaysWhite;
11749     pausing = FALSE;
11750     ModeHighlight();
11751     SetGameInfo();
11752     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11753     DisplayTitle(buf);
11754     if (first.sendName) {
11755       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11756       SendToProgram(buf, &first);
11757     }
11758     if (first.sendTime) {
11759       if (first.useColors) {
11760         SendToProgram("black\n", &first); /*gnu kludge*/
11761       }
11762       SendTimeRemaining(&first, TRUE);
11763     }
11764     if (first.useColors) {
11765       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11766     }
11767     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11768     SetMachineThinkingEnables();
11769     first.maybeThinking = TRUE;
11770     StartClocks();
11771     firstMove = FALSE;
11772
11773     if (appData.autoFlipView && !flipView) {
11774       flipView = !flipView;
11775       DrawPosition(FALSE, NULL);
11776       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11777     }
11778
11779     if(bookHit) { // [HGM] book: simulate book reply
11780         static char bookMove[MSG_SIZ]; // a bit generous?
11781
11782         programStats.nodes = programStats.depth = programStats.time =
11783         programStats.score = programStats.got_only_move = 0;
11784         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11785
11786         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11787         strcat(bookMove, bookHit);
11788         HandleMachineMove(bookMove, &first);
11789     }
11790 }
11791
11792 void
11793 MachineBlackEvent()
11794 {
11795   char buf[MSG_SIZ];
11796   char *bookHit = NULL;
11797
11798     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11799         return;
11800
11801
11802     if (gameMode == PlayFromGameFile ||
11803         gameMode == TwoMachinesPlay  ||
11804         gameMode == Training         ||
11805         gameMode == AnalyzeMode      ||
11806         gameMode == EndOfGame)
11807         EditGameEvent();
11808
11809     if (gameMode == EditPosition)
11810         EditPositionDone(TRUE);
11811
11812     if (WhiteOnMove(currentMove)) {
11813         DisplayError(_("It is not Black's turn"), 0);
11814         return;
11815     }
11816
11817     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11818       ExitAnalyzeMode();
11819
11820     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11821         gameMode == AnalyzeFile)
11822         TruncateGame();
11823
11824     ResurrectChessProgram();    /* in case it isn't running */
11825     gameMode = MachinePlaysBlack;
11826     pausing = FALSE;
11827     ModeHighlight();
11828     SetGameInfo();
11829     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11830     DisplayTitle(buf);
11831     if (first.sendName) {
11832       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11833       SendToProgram(buf, &first);
11834     }
11835     if (first.sendTime) {
11836       if (first.useColors) {
11837         SendToProgram("white\n", &first); /*gnu kludge*/
11838       }
11839       SendTimeRemaining(&first, FALSE);
11840     }
11841     if (first.useColors) {
11842       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11843     }
11844     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11845     SetMachineThinkingEnables();
11846     first.maybeThinking = TRUE;
11847     StartClocks();
11848
11849     if (appData.autoFlipView && flipView) {
11850       flipView = !flipView;
11851       DrawPosition(FALSE, NULL);
11852       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11853     }
11854     if(bookHit) { // [HGM] book: simulate book reply
11855         static char bookMove[MSG_SIZ]; // a bit generous?
11856
11857         programStats.nodes = programStats.depth = programStats.time =
11858         programStats.score = programStats.got_only_move = 0;
11859         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11860
11861         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11862         strcat(bookMove, bookHit);
11863         HandleMachineMove(bookMove, &first);
11864     }
11865 }
11866
11867
11868 void
11869 DisplayTwoMachinesTitle()
11870 {
11871     char buf[MSG_SIZ];
11872     if (appData.matchGames > 0) {
11873         if (first.twoMachinesColor[0] == 'w') {
11874           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11875                    gameInfo.white, gameInfo.black,
11876                    first.matchWins, second.matchWins,
11877                    matchGame - 1 - (first.matchWins + second.matchWins));
11878         } else {
11879           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11880                    gameInfo.white, gameInfo.black,
11881                    second.matchWins, first.matchWins,
11882                    matchGame - 1 - (first.matchWins + second.matchWins));
11883         }
11884     } else {
11885       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11886     }
11887     DisplayTitle(buf);
11888 }
11889
11890 void
11891 SettingsMenuIfReady()
11892 {
11893   if (second.lastPing != second.lastPong) {
11894     DisplayMessage("", _("Waiting for second chess program"));
11895     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11896     return;
11897   }
11898   ThawUI();
11899   DisplayMessage("", "");
11900   SettingsPopUp(&second);
11901 }
11902
11903 int
11904 WaitForSecond(DelayedEventCallback retry)
11905 {
11906     if (second.pr == NULL) {
11907         StartChessProgram(&second);
11908         if (second.protocolVersion == 1) {
11909           retry();
11910         } else {
11911           /* kludge: allow timeout for initial "feature" command */
11912           FreezeUI();
11913           DisplayMessage("", _("Starting second chess program"));
11914           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11915         }
11916         return 1;
11917     }
11918     return 0;
11919 }
11920
11921 void
11922 TwoMachinesEvent P((void))
11923 {
11924     int i;
11925     char buf[MSG_SIZ];
11926     ChessProgramState *onmove;
11927     char *bookHit = NULL;
11928
11929     if (appData.noChessProgram) return;
11930
11931     switch (gameMode) {
11932       case TwoMachinesPlay:
11933         return;
11934       case MachinePlaysWhite:
11935       case MachinePlaysBlack:
11936         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11937             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11938             return;
11939         }
11940         /* fall through */
11941       case BeginningOfGame:
11942       case PlayFromGameFile:
11943       case EndOfGame:
11944         EditGameEvent();
11945         if (gameMode != EditGame) return;
11946         break;
11947       case EditPosition:
11948         EditPositionDone(TRUE);
11949         break;
11950       case AnalyzeMode:
11951       case AnalyzeFile:
11952         ExitAnalyzeMode();
11953         break;
11954       case EditGame:
11955       default:
11956         break;
11957     }
11958
11959 //    forwardMostMove = currentMove;
11960     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11961     ResurrectChessProgram();    /* in case first program isn't running */
11962
11963     if(WaitForSecond(TwoMachinesEventIfReady)) return;
11964     DisplayMessage("", "");
11965     InitChessProgram(&second, FALSE);
11966     SendToProgram("force\n", &second);
11967     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11968       ScheduleDelayedEvent(TwoMachinesEvent, 10);
11969       return;
11970     }
11971     if (startedFromSetupPosition) {
11972         SendBoard(&second, backwardMostMove);
11973     if (appData.debugMode) {
11974         fprintf(debugFP, "Two Machines\n");
11975     }
11976     }
11977     for (i = backwardMostMove; i < forwardMostMove; i++) {
11978         SendMoveToProgram(i, &second);
11979     }
11980
11981     gameMode = TwoMachinesPlay;
11982     pausing = FALSE;
11983     ModeHighlight();
11984     SetGameInfo();
11985     DisplayTwoMachinesTitle();
11986     firstMove = TRUE;
11987     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11988         onmove = &first;
11989     } else {
11990         onmove = &second;
11991     }
11992
11993     SendToProgram(first.computerString, &first);
11994     if (first.sendName) {
11995       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11996       SendToProgram(buf, &first);
11997     }
11998     SendToProgram(second.computerString, &second);
11999     if (second.sendName) {
12000       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12001       SendToProgram(buf, &second);
12002     }
12003
12004     ResetClocks();
12005     if (!first.sendTime || !second.sendTime) {
12006         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12007         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12008     }
12009     if (onmove->sendTime) {
12010       if (onmove->useColors) {
12011         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12012       }
12013       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12014     }
12015     if (onmove->useColors) {
12016       SendToProgram(onmove->twoMachinesColor, onmove);
12017     }
12018     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12019 //    SendToProgram("go\n", onmove);
12020     onmove->maybeThinking = TRUE;
12021     SetMachineThinkingEnables();
12022
12023     StartClocks();
12024
12025     if(bookHit) { // [HGM] book: simulate book reply
12026         static char bookMove[MSG_SIZ]; // a bit generous?
12027
12028         programStats.nodes = programStats.depth = programStats.time =
12029         programStats.score = programStats.got_only_move = 0;
12030         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12031
12032         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12033         strcat(bookMove, bookHit);
12034         savedMessage = bookMove; // args for deferred call
12035         savedState = onmove;
12036         ScheduleDelayedEvent(DeferredBookMove, 1);
12037     }
12038 }
12039
12040 void
12041 TrainingEvent()
12042 {
12043     if (gameMode == Training) {
12044       SetTrainingModeOff();
12045       gameMode = PlayFromGameFile;
12046       DisplayMessage("", _("Training mode off"));
12047     } else {
12048       gameMode = Training;
12049       animateTraining = appData.animate;
12050
12051       /* make sure we are not already at the end of the game */
12052       if (currentMove < forwardMostMove) {
12053         SetTrainingModeOn();
12054         DisplayMessage("", _("Training mode on"));
12055       } else {
12056         gameMode = PlayFromGameFile;
12057         DisplayError(_("Already at end of game"), 0);
12058       }
12059     }
12060     ModeHighlight();
12061 }
12062
12063 void
12064 IcsClientEvent()
12065 {
12066     if (!appData.icsActive) return;
12067     switch (gameMode) {
12068       case IcsPlayingWhite:
12069       case IcsPlayingBlack:
12070       case IcsObserving:
12071       case IcsIdle:
12072       case BeginningOfGame:
12073       case IcsExamining:
12074         return;
12075
12076       case EditGame:
12077         break;
12078
12079       case EditPosition:
12080         EditPositionDone(TRUE);
12081         break;
12082
12083       case AnalyzeMode:
12084       case AnalyzeFile:
12085         ExitAnalyzeMode();
12086         break;
12087
12088       default:
12089         EditGameEvent();
12090         break;
12091     }
12092
12093     gameMode = IcsIdle;
12094     ModeHighlight();
12095     return;
12096 }
12097
12098
12099 void
12100 EditGameEvent()
12101 {
12102     int i;
12103
12104     switch (gameMode) {
12105       case Training:
12106         SetTrainingModeOff();
12107         break;
12108       case MachinePlaysWhite:
12109       case MachinePlaysBlack:
12110       case BeginningOfGame:
12111         SendToProgram("force\n", &first);
12112         SetUserThinkingEnables();
12113         break;
12114       case PlayFromGameFile:
12115         (void) StopLoadGameTimer();
12116         if (gameFileFP != NULL) {
12117             gameFileFP = NULL;
12118         }
12119         break;
12120       case EditPosition:
12121         EditPositionDone(TRUE);
12122         break;
12123       case AnalyzeMode:
12124       case AnalyzeFile:
12125         ExitAnalyzeMode();
12126         SendToProgram("force\n", &first);
12127         break;
12128       case TwoMachinesPlay:
12129         GameEnds(EndOfFile, NULL, GE_PLAYER);
12130         ResurrectChessProgram();
12131         SetUserThinkingEnables();
12132         break;
12133       case EndOfGame:
12134         ResurrectChessProgram();
12135         break;
12136       case IcsPlayingBlack:
12137       case IcsPlayingWhite:
12138         DisplayError(_("Warning: You are still playing a game"), 0);
12139         break;
12140       case IcsObserving:
12141         DisplayError(_("Warning: You are still observing a game"), 0);
12142         break;
12143       case IcsExamining:
12144         DisplayError(_("Warning: You are still examining a game"), 0);
12145         break;
12146       case IcsIdle:
12147         break;
12148       case EditGame:
12149       default:
12150         return;
12151     }
12152
12153     pausing = FALSE;
12154     StopClocks();
12155     first.offeredDraw = second.offeredDraw = 0;
12156
12157     if (gameMode == PlayFromGameFile) {
12158         whiteTimeRemaining = timeRemaining[0][currentMove];
12159         blackTimeRemaining = timeRemaining[1][currentMove];
12160         DisplayTitle("");
12161     }
12162
12163     if (gameMode == MachinePlaysWhite ||
12164         gameMode == MachinePlaysBlack ||
12165         gameMode == TwoMachinesPlay ||
12166         gameMode == EndOfGame) {
12167         i = forwardMostMove;
12168         while (i > currentMove) {
12169             SendToProgram("undo\n", &first);
12170             i--;
12171         }
12172         whiteTimeRemaining = timeRemaining[0][currentMove];
12173         blackTimeRemaining = timeRemaining[1][currentMove];
12174         DisplayBothClocks();
12175         if (whiteFlag || blackFlag) {
12176             whiteFlag = blackFlag = 0;
12177         }
12178         DisplayTitle("");
12179     }
12180
12181     gameMode = EditGame;
12182     ModeHighlight();
12183     SetGameInfo();
12184 }
12185
12186
12187 void
12188 EditPositionEvent()
12189 {
12190     if (gameMode == EditPosition) {
12191         EditGameEvent();
12192         return;
12193     }
12194
12195     EditGameEvent();
12196     if (gameMode != EditGame) return;
12197
12198     gameMode = EditPosition;
12199     ModeHighlight();
12200     SetGameInfo();
12201     if (currentMove > 0)
12202       CopyBoard(boards[0], boards[currentMove]);
12203
12204     blackPlaysFirst = !WhiteOnMove(currentMove);
12205     ResetClocks();
12206     currentMove = forwardMostMove = backwardMostMove = 0;
12207     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12208     DisplayMove(-1);
12209 }
12210
12211 void
12212 ExitAnalyzeMode()
12213 {
12214     /* [DM] icsEngineAnalyze - possible call from other functions */
12215     if (appData.icsEngineAnalyze) {
12216         appData.icsEngineAnalyze = FALSE;
12217
12218         DisplayMessage("",_("Close ICS engine analyze..."));
12219     }
12220     if (first.analysisSupport && first.analyzing) {
12221       SendToProgram("exit\n", &first);
12222       first.analyzing = FALSE;
12223     }
12224     thinkOutput[0] = NULLCHAR;
12225 }
12226
12227 void
12228 EditPositionDone(Boolean fakeRights)
12229 {
12230     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12231
12232     startedFromSetupPosition = TRUE;
12233     InitChessProgram(&first, FALSE);
12234     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12235       boards[0][EP_STATUS] = EP_NONE;
12236       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12237     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12238         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12239         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12240       } else boards[0][CASTLING][2] = NoRights;
12241     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12242         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12243         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12244       } else boards[0][CASTLING][5] = NoRights;
12245     }
12246     SendToProgram("force\n", &first);
12247     if (blackPlaysFirst) {
12248         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12249         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12250         currentMove = forwardMostMove = backwardMostMove = 1;
12251         CopyBoard(boards[1], boards[0]);
12252     } else {
12253         currentMove = forwardMostMove = backwardMostMove = 0;
12254     }
12255     SendBoard(&first, forwardMostMove);
12256     if (appData.debugMode) {
12257         fprintf(debugFP, "EditPosDone\n");
12258     }
12259     DisplayTitle("");
12260     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12261     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12262     gameMode = EditGame;
12263     ModeHighlight();
12264     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12265     ClearHighlights(); /* [AS] */
12266 }
12267
12268 /* Pause for `ms' milliseconds */
12269 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12270 void
12271 TimeDelay(ms)
12272      long ms;
12273 {
12274     TimeMark m1, m2;
12275
12276     GetTimeMark(&m1);
12277     do {
12278         GetTimeMark(&m2);
12279     } while (SubtractTimeMarks(&m2, &m1) < ms);
12280 }
12281
12282 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12283 void
12284 SendMultiLineToICS(buf)
12285      char *buf;
12286 {
12287     char temp[MSG_SIZ+1], *p;
12288     int len;
12289
12290     len = strlen(buf);
12291     if (len > MSG_SIZ)
12292       len = MSG_SIZ;
12293
12294     strncpy(temp, buf, len);
12295     temp[len] = 0;
12296
12297     p = temp;
12298     while (*p) {
12299         if (*p == '\n' || *p == '\r')
12300           *p = ' ';
12301         ++p;
12302     }
12303
12304     strcat(temp, "\n");
12305     SendToICS(temp);
12306     SendToPlayer(temp, strlen(temp));
12307 }
12308
12309 void
12310 SetWhiteToPlayEvent()
12311 {
12312     if (gameMode == EditPosition) {
12313         blackPlaysFirst = FALSE;
12314         DisplayBothClocks();    /* works because currentMove is 0 */
12315     } else if (gameMode == IcsExamining) {
12316         SendToICS(ics_prefix);
12317         SendToICS("tomove white\n");
12318     }
12319 }
12320
12321 void
12322 SetBlackToPlayEvent()
12323 {
12324     if (gameMode == EditPosition) {
12325         blackPlaysFirst = TRUE;
12326         currentMove = 1;        /* kludge */
12327         DisplayBothClocks();
12328         currentMove = 0;
12329     } else if (gameMode == IcsExamining) {
12330         SendToICS(ics_prefix);
12331         SendToICS("tomove black\n");
12332     }
12333 }
12334
12335 void
12336 EditPositionMenuEvent(selection, x, y)
12337      ChessSquare selection;
12338      int x, y;
12339 {
12340     char buf[MSG_SIZ];
12341     ChessSquare piece = boards[0][y][x];
12342
12343     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12344
12345     switch (selection) {
12346       case ClearBoard:
12347         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12348             SendToICS(ics_prefix);
12349             SendToICS("bsetup clear\n");
12350         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12351             SendToICS(ics_prefix);
12352             SendToICS("clearboard\n");
12353         } else {
12354             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12355                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12356                 for (y = 0; y < BOARD_HEIGHT; y++) {
12357                     if (gameMode == IcsExamining) {
12358                         if (boards[currentMove][y][x] != EmptySquare) {
12359                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12360                                     AAA + x, ONE + y);
12361                             SendToICS(buf);
12362                         }
12363                     } else {
12364                         boards[0][y][x] = p;
12365                     }
12366                 }
12367             }
12368         }
12369         if (gameMode == EditPosition) {
12370             DrawPosition(FALSE, boards[0]);
12371         }
12372         break;
12373
12374       case WhitePlay:
12375         SetWhiteToPlayEvent();
12376         break;
12377
12378       case BlackPlay:
12379         SetBlackToPlayEvent();
12380         break;
12381
12382       case EmptySquare:
12383         if (gameMode == IcsExamining) {
12384             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12385             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12386             SendToICS(buf);
12387         } else {
12388             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12389                 if(x == BOARD_LEFT-2) {
12390                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12391                     boards[0][y][1] = 0;
12392                 } else
12393                 if(x == BOARD_RGHT+1) {
12394                     if(y >= gameInfo.holdingsSize) break;
12395                     boards[0][y][BOARD_WIDTH-2] = 0;
12396                 } else break;
12397             }
12398             boards[0][y][x] = EmptySquare;
12399             DrawPosition(FALSE, boards[0]);
12400         }
12401         break;
12402
12403       case PromotePiece:
12404         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12405            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12406             selection = (ChessSquare) (PROMOTED piece);
12407         } else if(piece == EmptySquare) selection = WhiteSilver;
12408         else selection = (ChessSquare)((int)piece - 1);
12409         goto defaultlabel;
12410
12411       case DemotePiece:
12412         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12413            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12414             selection = (ChessSquare) (DEMOTED piece);
12415         } else if(piece == EmptySquare) selection = BlackSilver;
12416         else selection = (ChessSquare)((int)piece + 1);
12417         goto defaultlabel;
12418
12419       case WhiteQueen:
12420       case BlackQueen:
12421         if(gameInfo.variant == VariantShatranj ||
12422            gameInfo.variant == VariantXiangqi  ||
12423            gameInfo.variant == VariantCourier  ||
12424            gameInfo.variant == VariantMakruk     )
12425             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12426         goto defaultlabel;
12427
12428       case WhiteKing:
12429       case BlackKing:
12430         if(gameInfo.variant == VariantXiangqi)
12431             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12432         if(gameInfo.variant == VariantKnightmate)
12433             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12434       default:
12435         defaultlabel:
12436         if (gameMode == IcsExamining) {
12437             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12438             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12439                      PieceToChar(selection), AAA + x, ONE + y);
12440             SendToICS(buf);
12441         } else {
12442             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12443                 int n;
12444                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12445                     n = PieceToNumber(selection - BlackPawn);
12446                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12447                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12448                     boards[0][BOARD_HEIGHT-1-n][1]++;
12449                 } else
12450                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12451                     n = PieceToNumber(selection);
12452                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12453                     boards[0][n][BOARD_WIDTH-1] = selection;
12454                     boards[0][n][BOARD_WIDTH-2]++;
12455                 }
12456             } else
12457             boards[0][y][x] = selection;
12458             DrawPosition(TRUE, boards[0]);
12459         }
12460         break;
12461     }
12462 }
12463
12464
12465 void
12466 DropMenuEvent(selection, x, y)
12467      ChessSquare selection;
12468      int x, y;
12469 {
12470     ChessMove moveType;
12471
12472     switch (gameMode) {
12473       case IcsPlayingWhite:
12474       case MachinePlaysBlack:
12475         if (!WhiteOnMove(currentMove)) {
12476             DisplayMoveError(_("It is Black's turn"));
12477             return;
12478         }
12479         moveType = WhiteDrop;
12480         break;
12481       case IcsPlayingBlack:
12482       case MachinePlaysWhite:
12483         if (WhiteOnMove(currentMove)) {
12484             DisplayMoveError(_("It is White's turn"));
12485             return;
12486         }
12487         moveType = BlackDrop;
12488         break;
12489       case EditGame:
12490         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12491         break;
12492       default:
12493         return;
12494     }
12495
12496     if (moveType == BlackDrop && selection < BlackPawn) {
12497       selection = (ChessSquare) ((int) selection
12498                                  + (int) BlackPawn - (int) WhitePawn);
12499     }
12500     if (boards[currentMove][y][x] != EmptySquare) {
12501         DisplayMoveError(_("That square is occupied"));
12502         return;
12503     }
12504
12505     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12506 }
12507
12508 void
12509 AcceptEvent()
12510 {
12511     /* Accept a pending offer of any kind from opponent */
12512
12513     if (appData.icsActive) {
12514         SendToICS(ics_prefix);
12515         SendToICS("accept\n");
12516     } else if (cmailMsgLoaded) {
12517         if (currentMove == cmailOldMove &&
12518             commentList[cmailOldMove] != NULL &&
12519             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12520                    "Black offers a draw" : "White offers a draw")) {
12521             TruncateGame();
12522             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12523             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12524         } else {
12525             DisplayError(_("There is no pending offer on this move"), 0);
12526             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12527         }
12528     } else {
12529         /* Not used for offers from chess program */
12530     }
12531 }
12532
12533 void
12534 DeclineEvent()
12535 {
12536     /* Decline a pending offer of any kind from opponent */
12537
12538     if (appData.icsActive) {
12539         SendToICS(ics_prefix);
12540         SendToICS("decline\n");
12541     } else if (cmailMsgLoaded) {
12542         if (currentMove == cmailOldMove &&
12543             commentList[cmailOldMove] != NULL &&
12544             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12545                    "Black offers a draw" : "White offers a draw")) {
12546 #ifdef NOTDEF
12547             AppendComment(cmailOldMove, "Draw declined", TRUE);
12548             DisplayComment(cmailOldMove - 1, "Draw declined");
12549 #endif /*NOTDEF*/
12550         } else {
12551             DisplayError(_("There is no pending offer on this move"), 0);
12552         }
12553     } else {
12554         /* Not used for offers from chess program */
12555     }
12556 }
12557
12558 void
12559 RematchEvent()
12560 {
12561     /* Issue ICS rematch command */
12562     if (appData.icsActive) {
12563         SendToICS(ics_prefix);
12564         SendToICS("rematch\n");
12565     }
12566 }
12567
12568 void
12569 CallFlagEvent()
12570 {
12571     /* Call your opponent's flag (claim a win on time) */
12572     if (appData.icsActive) {
12573         SendToICS(ics_prefix);
12574         SendToICS("flag\n");
12575     } else {
12576         switch (gameMode) {
12577           default:
12578             return;
12579           case MachinePlaysWhite:
12580             if (whiteFlag) {
12581                 if (blackFlag)
12582                   GameEnds(GameIsDrawn, "Both players ran out of time",
12583                            GE_PLAYER);
12584                 else
12585                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12586             } else {
12587                 DisplayError(_("Your opponent is not out of time"), 0);
12588             }
12589             break;
12590           case MachinePlaysBlack:
12591             if (blackFlag) {
12592                 if (whiteFlag)
12593                   GameEnds(GameIsDrawn, "Both players ran out of time",
12594                            GE_PLAYER);
12595                 else
12596                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12597             } else {
12598                 DisplayError(_("Your opponent is not out of time"), 0);
12599             }
12600             break;
12601         }
12602     }
12603 }
12604
12605 void
12606 DrawEvent()
12607 {
12608     /* Offer draw or accept pending draw offer from opponent */
12609
12610     if (appData.icsActive) {
12611         /* Note: tournament rules require draw offers to be
12612            made after you make your move but before you punch
12613            your clock.  Currently ICS doesn't let you do that;
12614            instead, you immediately punch your clock after making
12615            a move, but you can offer a draw at any time. */
12616
12617         SendToICS(ics_prefix);
12618         SendToICS("draw\n");
12619         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12620     } else if (cmailMsgLoaded) {
12621         if (currentMove == cmailOldMove &&
12622             commentList[cmailOldMove] != NULL &&
12623             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12624                    "Black offers a draw" : "White offers a draw")) {
12625             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12626             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12627         } else if (currentMove == cmailOldMove + 1) {
12628             char *offer = WhiteOnMove(cmailOldMove) ?
12629               "White offers a draw" : "Black offers a draw";
12630             AppendComment(currentMove, offer, TRUE);
12631             DisplayComment(currentMove - 1, offer);
12632             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12633         } else {
12634             DisplayError(_("You must make your move before offering a draw"), 0);
12635             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12636         }
12637     } else if (first.offeredDraw) {
12638         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12639     } else {
12640         if (first.sendDrawOffers) {
12641             SendToProgram("draw\n", &first);
12642             userOfferedDraw = TRUE;
12643         }
12644     }
12645 }
12646
12647 void
12648 AdjournEvent()
12649 {
12650     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12651
12652     if (appData.icsActive) {
12653         SendToICS(ics_prefix);
12654         SendToICS("adjourn\n");
12655     } else {
12656         /* Currently GNU Chess doesn't offer or accept Adjourns */
12657     }
12658 }
12659
12660
12661 void
12662 AbortEvent()
12663 {
12664     /* Offer Abort or accept pending Abort offer from opponent */
12665
12666     if (appData.icsActive) {
12667         SendToICS(ics_prefix);
12668         SendToICS("abort\n");
12669     } else {
12670         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12671     }
12672 }
12673
12674 void
12675 ResignEvent()
12676 {
12677     /* Resign.  You can do this even if it's not your turn. */
12678
12679     if (appData.icsActive) {
12680         SendToICS(ics_prefix);
12681         SendToICS("resign\n");
12682     } else {
12683         switch (gameMode) {
12684           case MachinePlaysWhite:
12685             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12686             break;
12687           case MachinePlaysBlack:
12688             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12689             break;
12690           case EditGame:
12691             if (cmailMsgLoaded) {
12692                 TruncateGame();
12693                 if (WhiteOnMove(cmailOldMove)) {
12694                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12695                 } else {
12696                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12697                 }
12698                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12699             }
12700             break;
12701           default:
12702             break;
12703         }
12704     }
12705 }
12706
12707
12708 void
12709 StopObservingEvent()
12710 {
12711     /* Stop observing current games */
12712     SendToICS(ics_prefix);
12713     SendToICS("unobserve\n");
12714 }
12715
12716 void
12717 StopExaminingEvent()
12718 {
12719     /* Stop observing current game */
12720     SendToICS(ics_prefix);
12721     SendToICS("unexamine\n");
12722 }
12723
12724 void
12725 ForwardInner(target)
12726      int target;
12727 {
12728     int limit;
12729
12730     if (appData.debugMode)
12731         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12732                 target, currentMove, forwardMostMove);
12733
12734     if (gameMode == EditPosition)
12735       return;
12736
12737     if (gameMode == PlayFromGameFile && !pausing)
12738       PauseEvent();
12739
12740     if (gameMode == IcsExamining && pausing)
12741       limit = pauseExamForwardMostMove;
12742     else
12743       limit = forwardMostMove;
12744
12745     if (target > limit) target = limit;
12746
12747     if (target > 0 && moveList[target - 1][0]) {
12748         int fromX, fromY, toX, toY;
12749         toX = moveList[target - 1][2] - AAA;
12750         toY = moveList[target - 1][3] - ONE;
12751         if (moveList[target - 1][1] == '@') {
12752             if (appData.highlightLastMove) {
12753                 SetHighlights(-1, -1, toX, toY);
12754             }
12755         } else {
12756             fromX = moveList[target - 1][0] - AAA;
12757             fromY = moveList[target - 1][1] - ONE;
12758             if (target == currentMove + 1) {
12759                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12760             }
12761             if (appData.highlightLastMove) {
12762                 SetHighlights(fromX, fromY, toX, toY);
12763             }
12764         }
12765     }
12766     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12767         gameMode == Training || gameMode == PlayFromGameFile ||
12768         gameMode == AnalyzeFile) {
12769         while (currentMove < target) {
12770             SendMoveToProgram(currentMove++, &first);
12771         }
12772     } else {
12773         currentMove = target;
12774     }
12775
12776     if (gameMode == EditGame || gameMode == EndOfGame) {
12777         whiteTimeRemaining = timeRemaining[0][currentMove];
12778         blackTimeRemaining = timeRemaining[1][currentMove];
12779     }
12780     DisplayBothClocks();
12781     DisplayMove(currentMove - 1);
12782     DrawPosition(FALSE, boards[currentMove]);
12783     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12784     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12785         DisplayComment(currentMove - 1, commentList[currentMove]);
12786     }
12787 }
12788
12789
12790 void
12791 ForwardEvent()
12792 {
12793     if (gameMode == IcsExamining && !pausing) {
12794         SendToICS(ics_prefix);
12795         SendToICS("forward\n");
12796     } else {
12797         ForwardInner(currentMove + 1);
12798     }
12799 }
12800
12801 void
12802 ToEndEvent()
12803 {
12804     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12805         /* to optimze, we temporarily turn off analysis mode while we feed
12806          * the remaining moves to the engine. Otherwise we get analysis output
12807          * after each move.
12808          */
12809         if (first.analysisSupport) {
12810           SendToProgram("exit\nforce\n", &first);
12811           first.analyzing = FALSE;
12812         }
12813     }
12814
12815     if (gameMode == IcsExamining && !pausing) {
12816         SendToICS(ics_prefix);
12817         SendToICS("forward 999999\n");
12818     } else {
12819         ForwardInner(forwardMostMove);
12820     }
12821
12822     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12823         /* we have fed all the moves, so reactivate analysis mode */
12824         SendToProgram("analyze\n", &first);
12825         first.analyzing = TRUE;
12826         /*first.maybeThinking = TRUE;*/
12827         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12828     }
12829 }
12830
12831 void
12832 BackwardInner(target)
12833      int target;
12834 {
12835     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12836
12837     if (appData.debugMode)
12838         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12839                 target, currentMove, forwardMostMove);
12840
12841     if (gameMode == EditPosition) return;
12842     if (currentMove <= backwardMostMove) {
12843         ClearHighlights();
12844         DrawPosition(full_redraw, boards[currentMove]);
12845         return;
12846     }
12847     if (gameMode == PlayFromGameFile && !pausing)
12848       PauseEvent();
12849
12850     if (moveList[target][0]) {
12851         int fromX, fromY, toX, toY;
12852         toX = moveList[target][2] - AAA;
12853         toY = moveList[target][3] - ONE;
12854         if (moveList[target][1] == '@') {
12855             if (appData.highlightLastMove) {
12856                 SetHighlights(-1, -1, toX, toY);
12857             }
12858         } else {
12859             fromX = moveList[target][0] - AAA;
12860             fromY = moveList[target][1] - ONE;
12861             if (target == currentMove - 1) {
12862                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12863             }
12864             if (appData.highlightLastMove) {
12865                 SetHighlights(fromX, fromY, toX, toY);
12866             }
12867         }
12868     }
12869     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12870         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12871         while (currentMove > target) {
12872             SendToProgram("undo\n", &first);
12873             currentMove--;
12874         }
12875     } else {
12876         currentMove = target;
12877     }
12878
12879     if (gameMode == EditGame || gameMode == EndOfGame) {
12880         whiteTimeRemaining = timeRemaining[0][currentMove];
12881         blackTimeRemaining = timeRemaining[1][currentMove];
12882     }
12883     DisplayBothClocks();
12884     DisplayMove(currentMove - 1);
12885     DrawPosition(full_redraw, boards[currentMove]);
12886     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12887     // [HGM] PV info: routine tests if comment empty
12888     DisplayComment(currentMove - 1, commentList[currentMove]);
12889 }
12890
12891 void
12892 BackwardEvent()
12893 {
12894     if (gameMode == IcsExamining && !pausing) {
12895         SendToICS(ics_prefix);
12896         SendToICS("backward\n");
12897     } else {
12898         BackwardInner(currentMove - 1);
12899     }
12900 }
12901
12902 void
12903 ToStartEvent()
12904 {
12905     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12906         /* to optimize, we temporarily turn off analysis mode while we undo
12907          * all the moves. Otherwise we get analysis output after each undo.
12908          */
12909         if (first.analysisSupport) {
12910           SendToProgram("exit\nforce\n", &first);
12911           first.analyzing = FALSE;
12912         }
12913     }
12914
12915     if (gameMode == IcsExamining && !pausing) {
12916         SendToICS(ics_prefix);
12917         SendToICS("backward 999999\n");
12918     } else {
12919         BackwardInner(backwardMostMove);
12920     }
12921
12922     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12923         /* we have fed all the moves, so reactivate analysis mode */
12924         SendToProgram("analyze\n", &first);
12925         first.analyzing = TRUE;
12926         /*first.maybeThinking = TRUE;*/
12927         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12928     }
12929 }
12930
12931 void
12932 ToNrEvent(int to)
12933 {
12934   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12935   if (to >= forwardMostMove) to = forwardMostMove;
12936   if (to <= backwardMostMove) to = backwardMostMove;
12937   if (to < currentMove) {
12938     BackwardInner(to);
12939   } else {
12940     ForwardInner(to);
12941   }
12942 }
12943
12944 void
12945 RevertEvent(Boolean annotate)
12946 {
12947     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12948         return;
12949     }
12950     if (gameMode != IcsExamining) {
12951         DisplayError(_("You are not examining a game"), 0);
12952         return;
12953     }
12954     if (pausing) {
12955         DisplayError(_("You can't revert while pausing"), 0);
12956         return;
12957     }
12958     SendToICS(ics_prefix);
12959     SendToICS("revert\n");
12960 }
12961
12962 void
12963 RetractMoveEvent()
12964 {
12965     switch (gameMode) {
12966       case MachinePlaysWhite:
12967       case MachinePlaysBlack:
12968         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12969             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12970             return;
12971         }
12972         if (forwardMostMove < 2) return;
12973         currentMove = forwardMostMove = forwardMostMove - 2;
12974         whiteTimeRemaining = timeRemaining[0][currentMove];
12975         blackTimeRemaining = timeRemaining[1][currentMove];
12976         DisplayBothClocks();
12977         DisplayMove(currentMove - 1);
12978         ClearHighlights();/*!! could figure this out*/
12979         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12980         SendToProgram("remove\n", &first);
12981         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12982         break;
12983
12984       case BeginningOfGame:
12985       default:
12986         break;
12987
12988       case IcsPlayingWhite:
12989       case IcsPlayingBlack:
12990         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12991             SendToICS(ics_prefix);
12992             SendToICS("takeback 2\n");
12993         } else {
12994             SendToICS(ics_prefix);
12995             SendToICS("takeback 1\n");
12996         }
12997         break;
12998     }
12999 }
13000
13001 void
13002 MoveNowEvent()
13003 {
13004     ChessProgramState *cps;
13005
13006     switch (gameMode) {
13007       case MachinePlaysWhite:
13008         if (!WhiteOnMove(forwardMostMove)) {
13009             DisplayError(_("It is your turn"), 0);
13010             return;
13011         }
13012         cps = &first;
13013         break;
13014       case MachinePlaysBlack:
13015         if (WhiteOnMove(forwardMostMove)) {
13016             DisplayError(_("It is your turn"), 0);
13017             return;
13018         }
13019         cps = &first;
13020         break;
13021       case TwoMachinesPlay:
13022         if (WhiteOnMove(forwardMostMove) ==
13023             (first.twoMachinesColor[0] == 'w')) {
13024             cps = &first;
13025         } else {
13026             cps = &second;
13027         }
13028         break;
13029       case BeginningOfGame:
13030       default:
13031         return;
13032     }
13033     SendToProgram("?\n", cps);
13034 }
13035
13036 void
13037 TruncateGameEvent()
13038 {
13039     EditGameEvent();
13040     if (gameMode != EditGame) return;
13041     TruncateGame();
13042 }
13043
13044 void
13045 TruncateGame()
13046 {
13047     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13048     if (forwardMostMove > currentMove) {
13049         if (gameInfo.resultDetails != NULL) {
13050             free(gameInfo.resultDetails);
13051             gameInfo.resultDetails = NULL;
13052             gameInfo.result = GameUnfinished;
13053         }
13054         forwardMostMove = currentMove;
13055         HistorySet(parseList, backwardMostMove, forwardMostMove,
13056                    currentMove-1);
13057     }
13058 }
13059
13060 void
13061 HintEvent()
13062 {
13063     if (appData.noChessProgram) return;
13064     switch (gameMode) {
13065       case MachinePlaysWhite:
13066         if (WhiteOnMove(forwardMostMove)) {
13067             DisplayError(_("Wait until your turn"), 0);
13068             return;
13069         }
13070         break;
13071       case BeginningOfGame:
13072       case MachinePlaysBlack:
13073         if (!WhiteOnMove(forwardMostMove)) {
13074             DisplayError(_("Wait until your turn"), 0);
13075             return;
13076         }
13077         break;
13078       default:
13079         DisplayError(_("No hint available"), 0);
13080         return;
13081     }
13082     SendToProgram("hint\n", &first);
13083     hintRequested = TRUE;
13084 }
13085
13086 void
13087 BookEvent()
13088 {
13089     if (appData.noChessProgram) return;
13090     switch (gameMode) {
13091       case MachinePlaysWhite:
13092         if (WhiteOnMove(forwardMostMove)) {
13093             DisplayError(_("Wait until your turn"), 0);
13094             return;
13095         }
13096         break;
13097       case BeginningOfGame:
13098       case MachinePlaysBlack:
13099         if (!WhiteOnMove(forwardMostMove)) {
13100             DisplayError(_("Wait until your turn"), 0);
13101             return;
13102         }
13103         break;
13104       case EditPosition:
13105         EditPositionDone(TRUE);
13106         break;
13107       case TwoMachinesPlay:
13108         return;
13109       default:
13110         break;
13111     }
13112     SendToProgram("bk\n", &first);
13113     bookOutput[0] = NULLCHAR;
13114     bookRequested = TRUE;
13115 }
13116
13117 void
13118 AboutGameEvent()
13119 {
13120     char *tags = PGNTags(&gameInfo);
13121     TagsPopUp(tags, CmailMsg());
13122     free(tags);
13123 }
13124
13125 /* end button procedures */
13126
13127 void
13128 PrintPosition(fp, move)
13129      FILE *fp;
13130      int move;
13131 {
13132     int i, j;
13133
13134     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13135         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13136             char c = PieceToChar(boards[move][i][j]);
13137             fputc(c == 'x' ? '.' : c, fp);
13138             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13139         }
13140     }
13141     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13142       fprintf(fp, "white to play\n");
13143     else
13144       fprintf(fp, "black to play\n");
13145 }
13146
13147 void
13148 PrintOpponents(fp)
13149      FILE *fp;
13150 {
13151     if (gameInfo.white != NULL) {
13152         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13153     } else {
13154         fprintf(fp, "\n");
13155     }
13156 }
13157
13158 /* Find last component of program's own name, using some heuristics */
13159 void
13160 TidyProgramName(prog, host, buf)
13161      char *prog, *host, buf[MSG_SIZ];
13162 {
13163     char *p, *q;
13164     int local = (strcmp(host, "localhost") == 0);
13165     while (!local && (p = strchr(prog, ';')) != NULL) {
13166         p++;
13167         while (*p == ' ') p++;
13168         prog = p;
13169     }
13170     if (*prog == '"' || *prog == '\'') {
13171         q = strchr(prog + 1, *prog);
13172     } else {
13173         q = strchr(prog, ' ');
13174     }
13175     if (q == NULL) q = prog + strlen(prog);
13176     p = q;
13177     while (p >= prog && *p != '/' && *p != '\\') p--;
13178     p++;
13179     if(p == prog && *p == '"') p++;
13180     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13181     memcpy(buf, p, q - p);
13182     buf[q - p] = NULLCHAR;
13183     if (!local) {
13184         strcat(buf, "@");
13185         strcat(buf, host);
13186     }
13187 }
13188
13189 char *
13190 TimeControlTagValue()
13191 {
13192     char buf[MSG_SIZ];
13193     if (!appData.clockMode) {
13194       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13195     } else if (movesPerSession > 0) {
13196       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13197     } else if (timeIncrement == 0) {
13198       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13199     } else {
13200       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13201     }
13202     return StrSave(buf);
13203 }
13204
13205 void
13206 SetGameInfo()
13207 {
13208     /* This routine is used only for certain modes */
13209     VariantClass v = gameInfo.variant;
13210     ChessMove r = GameUnfinished;
13211     char *p = NULL;
13212
13213     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13214         r = gameInfo.result;
13215         p = gameInfo.resultDetails;
13216         gameInfo.resultDetails = NULL;
13217     }
13218     ClearGameInfo(&gameInfo);
13219     gameInfo.variant = v;
13220
13221     switch (gameMode) {
13222       case MachinePlaysWhite:
13223         gameInfo.event = StrSave( appData.pgnEventHeader );
13224         gameInfo.site = StrSave(HostName());
13225         gameInfo.date = PGNDate();
13226         gameInfo.round = StrSave("-");
13227         gameInfo.white = StrSave(first.tidy);
13228         gameInfo.black = StrSave(UserName());
13229         gameInfo.timeControl = TimeControlTagValue();
13230         break;
13231
13232       case MachinePlaysBlack:
13233         gameInfo.event = StrSave( appData.pgnEventHeader );
13234         gameInfo.site = StrSave(HostName());
13235         gameInfo.date = PGNDate();
13236         gameInfo.round = StrSave("-");
13237         gameInfo.white = StrSave(UserName());
13238         gameInfo.black = StrSave(first.tidy);
13239         gameInfo.timeControl = TimeControlTagValue();
13240         break;
13241
13242       case TwoMachinesPlay:
13243         gameInfo.event = StrSave( appData.pgnEventHeader );
13244         gameInfo.site = StrSave(HostName());
13245         gameInfo.date = PGNDate();
13246         if (matchGame > 0) {
13247             char buf[MSG_SIZ];
13248             snprintf(buf, MSG_SIZ, "%d", matchGame);
13249             gameInfo.round = StrSave(buf);
13250         } else {
13251             gameInfo.round = StrSave("-");
13252         }
13253         if (first.twoMachinesColor[0] == 'w') {
13254             gameInfo.white = StrSave(first.tidy);
13255             gameInfo.black = StrSave(second.tidy);
13256         } else {
13257             gameInfo.white = StrSave(second.tidy);
13258             gameInfo.black = StrSave(first.tidy);
13259         }
13260         gameInfo.timeControl = TimeControlTagValue();
13261         break;
13262
13263       case EditGame:
13264         gameInfo.event = StrSave("Edited game");
13265         gameInfo.site = StrSave(HostName());
13266         gameInfo.date = PGNDate();
13267         gameInfo.round = StrSave("-");
13268         gameInfo.white = StrSave("-");
13269         gameInfo.black = StrSave("-");
13270         gameInfo.result = r;
13271         gameInfo.resultDetails = p;
13272         break;
13273
13274       case EditPosition:
13275         gameInfo.event = StrSave("Edited position");
13276         gameInfo.site = StrSave(HostName());
13277         gameInfo.date = PGNDate();
13278         gameInfo.round = StrSave("-");
13279         gameInfo.white = StrSave("-");
13280         gameInfo.black = StrSave("-");
13281         break;
13282
13283       case IcsPlayingWhite:
13284       case IcsPlayingBlack:
13285       case IcsObserving:
13286       case IcsExamining:
13287         break;
13288
13289       case PlayFromGameFile:
13290         gameInfo.event = StrSave("Game from non-PGN file");
13291         gameInfo.site = StrSave(HostName());
13292         gameInfo.date = PGNDate();
13293         gameInfo.round = StrSave("-");
13294         gameInfo.white = StrSave("?");
13295         gameInfo.black = StrSave("?");
13296         break;
13297
13298       default:
13299         break;
13300     }
13301 }
13302
13303 void
13304 ReplaceComment(index, text)
13305      int index;
13306      char *text;
13307 {
13308     int len;
13309     char *p;
13310     float score;
13311
13312     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13313        pvInfoList[index-1].depth == len &&
13314        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13315        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13316     while (*text == '\n') text++;
13317     len = strlen(text);
13318     while (len > 0 && text[len - 1] == '\n') len--;
13319
13320     if (commentList[index] != NULL)
13321       free(commentList[index]);
13322
13323     if (len == 0) {
13324         commentList[index] = NULL;
13325         return;
13326     }
13327   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13328       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13329       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13330     commentList[index] = (char *) malloc(len + 2);
13331     strncpy(commentList[index], text, len);
13332     commentList[index][len] = '\n';
13333     commentList[index][len + 1] = NULLCHAR;
13334   } else {
13335     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13336     char *p;
13337     commentList[index] = (char *) malloc(len + 7);
13338     safeStrCpy(commentList[index], "{\n", 3);
13339     safeStrCpy(commentList[index]+2, text, len+1);
13340     commentList[index][len+2] = NULLCHAR;
13341     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13342     strcat(commentList[index], "\n}\n");
13343   }
13344 }
13345
13346 void
13347 CrushCRs(text)
13348      char *text;
13349 {
13350   char *p = text;
13351   char *q = text;
13352   char ch;
13353
13354   do {
13355     ch = *p++;
13356     if (ch == '\r') continue;
13357     *q++ = ch;
13358   } while (ch != '\0');
13359 }
13360
13361 void
13362 AppendComment(index, text, addBraces)
13363      int index;
13364      char *text;
13365      Boolean addBraces; // [HGM] braces: tells if we should add {}
13366 {
13367     int oldlen, len;
13368     char *old;
13369
13370 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13371     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13372
13373     CrushCRs(text);
13374     while (*text == '\n') text++;
13375     len = strlen(text);
13376     while (len > 0 && text[len - 1] == '\n') len--;
13377
13378     if (len == 0) return;
13379
13380     if (commentList[index] != NULL) {
13381         old = commentList[index];
13382         oldlen = strlen(old);
13383         while(commentList[index][oldlen-1] ==  '\n')
13384           commentList[index][--oldlen] = NULLCHAR;
13385         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13386         safeStrCpy(commentList[index], old, oldlen + len + 6);
13387         free(old);
13388         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13389         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13390           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13391           while (*text == '\n') { text++; len--; }
13392           commentList[index][--oldlen] = NULLCHAR;
13393       }
13394         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13395         else          strcat(commentList[index], "\n");
13396         strcat(commentList[index], text);
13397         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13398         else          strcat(commentList[index], "\n");
13399     } else {
13400         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13401         if(addBraces)
13402           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13403         else commentList[index][0] = NULLCHAR;
13404         strcat(commentList[index], text);
13405         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13406         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13407     }
13408 }
13409
13410 static char * FindStr( char * text, char * sub_text )
13411 {
13412     char * result = strstr( text, sub_text );
13413
13414     if( result != NULL ) {
13415         result += strlen( sub_text );
13416     }
13417
13418     return result;
13419 }
13420
13421 /* [AS] Try to extract PV info from PGN comment */
13422 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13423 char *GetInfoFromComment( int index, char * text )
13424 {
13425     char * sep = text, *p;
13426
13427     if( text != NULL && index > 0 ) {
13428         int score = 0;
13429         int depth = 0;
13430         int time = -1, sec = 0, deci;
13431         char * s_eval = FindStr( text, "[%eval " );
13432         char * s_emt = FindStr( text, "[%emt " );
13433
13434         if( s_eval != NULL || s_emt != NULL ) {
13435             /* New style */
13436             char delim;
13437
13438             if( s_eval != NULL ) {
13439                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13440                     return text;
13441                 }
13442
13443                 if( delim != ']' ) {
13444                     return text;
13445                 }
13446             }
13447
13448             if( s_emt != NULL ) {
13449             }
13450                 return text;
13451         }
13452         else {
13453             /* We expect something like: [+|-]nnn.nn/dd */
13454             int score_lo = 0;
13455
13456             if(*text != '{') return text; // [HGM] braces: must be normal comment
13457
13458             sep = strchr( text, '/' );
13459             if( sep == NULL || sep < (text+4) ) {
13460                 return text;
13461             }
13462
13463             p = text;
13464             if(p[1] == '(') { // comment starts with PV
13465                p = strchr(p, ')'); // locate end of PV
13466                if(p == NULL || sep < p+5) return text;
13467                // at this point we have something like "{(.*) +0.23/6 ..."
13468                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13469                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13470                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13471             }
13472             time = -1; sec = -1; deci = -1;
13473             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13474                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13475                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13476                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13477                 return text;
13478             }
13479
13480             if( score_lo < 0 || score_lo >= 100 ) {
13481                 return text;
13482             }
13483
13484             if(sec >= 0) time = 600*time + 10*sec; else
13485             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13486
13487             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13488
13489             /* [HGM] PV time: now locate end of PV info */
13490             while( *++sep >= '0' && *sep <= '9'); // strip depth
13491             if(time >= 0)
13492             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13493             if(sec >= 0)
13494             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13495             if(deci >= 0)
13496             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13497             while(*sep == ' ') sep++;
13498         }
13499
13500         if( depth <= 0 ) {
13501             return text;
13502         }
13503
13504         if( time < 0 ) {
13505             time = -1;
13506         }
13507
13508         pvInfoList[index-1].depth = depth;
13509         pvInfoList[index-1].score = score;
13510         pvInfoList[index-1].time  = 10*time; // centi-sec
13511         if(*sep == '}') *sep = 0; else *--sep = '{';
13512         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13513     }
13514     return sep;
13515 }
13516
13517 void
13518 SendToProgram(message, cps)
13519      char *message;
13520      ChessProgramState *cps;
13521 {
13522     int count, outCount, error;
13523     char buf[MSG_SIZ];
13524
13525     if (cps->pr == NULL) return;
13526     Attention(cps);
13527
13528     if (appData.debugMode) {
13529         TimeMark now;
13530         GetTimeMark(&now);
13531         fprintf(debugFP, "%ld >%-6s: %s",
13532                 SubtractTimeMarks(&now, &programStartTime),
13533                 cps->which, message);
13534     }
13535
13536     count = strlen(message);
13537     outCount = OutputToProcess(cps->pr, message, count, &error);
13538     if (outCount < count && !exiting
13539                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13540       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13541         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13542             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13543                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13544                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13545             } else {
13546                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13547             }
13548             gameInfo.resultDetails = StrSave(buf);
13549         }
13550         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13551     }
13552 }
13553
13554 void
13555 ReceiveFromProgram(isr, closure, message, count, error)
13556      InputSourceRef isr;
13557      VOIDSTAR closure;
13558      char *message;
13559      int count;
13560      int error;
13561 {
13562     char *end_str;
13563     char buf[MSG_SIZ];
13564     ChessProgramState *cps = (ChessProgramState *)closure;
13565
13566     if (isr != cps->isr) return; /* Killed intentionally */
13567     if (count <= 0) {
13568         if (count == 0) {
13569             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13570                     cps->which, cps->program);
13571         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13572                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13573                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13574                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13575                 } else {
13576                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13577                 }
13578                 gameInfo.resultDetails = StrSave(buf);
13579             }
13580             RemoveInputSource(cps->isr);
13581             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13582         } else {
13583             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13584                     cps->which, cps->program);
13585             RemoveInputSource(cps->isr);
13586
13587             /* [AS] Program is misbehaving badly... kill it */
13588             if( count == -2 ) {
13589                 DestroyChildProcess( cps->pr, 9 );
13590                 cps->pr = NoProc;
13591             }
13592
13593             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13594         }
13595         return;
13596     }
13597
13598     if ((end_str = strchr(message, '\r')) != NULL)
13599       *end_str = NULLCHAR;
13600     if ((end_str = strchr(message, '\n')) != NULL)
13601       *end_str = NULLCHAR;
13602
13603     if (appData.debugMode) {
13604         TimeMark now; int print = 1;
13605         char *quote = ""; char c; int i;
13606
13607         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13608                 char start = message[0];
13609                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13610                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13611                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13612                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13613                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13614                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13615                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13616                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13617                    sscanf(message, "hint: %c", &c)!=1 && 
13618                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13619                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13620                     print = (appData.engineComments >= 2);
13621                 }
13622                 message[0] = start; // restore original message
13623         }
13624         if(print) {
13625                 GetTimeMark(&now);
13626                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13627                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13628                         quote,
13629                         message);
13630         }
13631     }
13632
13633     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13634     if (appData.icsEngineAnalyze) {
13635         if (strstr(message, "whisper") != NULL ||
13636              strstr(message, "kibitz") != NULL ||
13637             strstr(message, "tellics") != NULL) return;
13638     }
13639
13640     HandleMachineMove(message, cps);
13641 }
13642
13643
13644 void
13645 SendTimeControl(cps, mps, tc, inc, sd, st)
13646      ChessProgramState *cps;
13647      int mps, inc, sd, st;
13648      long tc;
13649 {
13650     char buf[MSG_SIZ];
13651     int seconds;
13652
13653     if( timeControl_2 > 0 ) {
13654         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13655             tc = timeControl_2;
13656         }
13657     }
13658     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13659     inc /= cps->timeOdds;
13660     st  /= cps->timeOdds;
13661
13662     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13663
13664     if (st > 0) {
13665       /* Set exact time per move, normally using st command */
13666       if (cps->stKludge) {
13667         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13668         seconds = st % 60;
13669         if (seconds == 0) {
13670           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13671         } else {
13672           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13673         }
13674       } else {
13675         snprintf(buf, MSG_SIZ, "st %d\n", st);
13676       }
13677     } else {
13678       /* Set conventional or incremental time control, using level command */
13679       if (seconds == 0) {
13680         /* Note old gnuchess bug -- minutes:seconds used to not work.
13681            Fixed in later versions, but still avoid :seconds
13682            when seconds is 0. */
13683         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13684       } else {
13685         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13686                  seconds, inc/1000.);
13687       }
13688     }
13689     SendToProgram(buf, cps);
13690
13691     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13692     /* Orthogonally, limit search to given depth */
13693     if (sd > 0) {
13694       if (cps->sdKludge) {
13695         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13696       } else {
13697         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13698       }
13699       SendToProgram(buf, cps);
13700     }
13701
13702     if(cps->nps > 0) { /* [HGM] nps */
13703         if(cps->supportsNPS == FALSE)
13704           cps->nps = -1; // don't use if engine explicitly says not supported!
13705         else {
13706           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13707           SendToProgram(buf, cps);
13708         }
13709     }
13710 }
13711
13712 ChessProgramState *WhitePlayer()
13713 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13714 {
13715     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13716        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13717         return &second;
13718     return &first;
13719 }
13720
13721 void
13722 SendTimeRemaining(cps, machineWhite)
13723      ChessProgramState *cps;
13724      int /*boolean*/ machineWhite;
13725 {
13726     char message[MSG_SIZ];
13727     long time, otime;
13728
13729     /* Note: this routine must be called when the clocks are stopped
13730        or when they have *just* been set or switched; otherwise
13731        it will be off by the time since the current tick started.
13732     */
13733     if (machineWhite) {
13734         time = whiteTimeRemaining / 10;
13735         otime = blackTimeRemaining / 10;
13736     } else {
13737         time = blackTimeRemaining / 10;
13738         otime = whiteTimeRemaining / 10;
13739     }
13740     /* [HGM] translate opponent's time by time-odds factor */
13741     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13742     if (appData.debugMode) {
13743         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13744     }
13745
13746     if (time <= 0) time = 1;
13747     if (otime <= 0) otime = 1;
13748
13749     snprintf(message, MSG_SIZ, "time %ld\n", time);
13750     SendToProgram(message, cps);
13751
13752     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13753     SendToProgram(message, cps);
13754 }
13755
13756 int
13757 BoolFeature(p, name, loc, cps)
13758      char **p;
13759      char *name;
13760      int *loc;
13761      ChessProgramState *cps;
13762 {
13763   char buf[MSG_SIZ];
13764   int len = strlen(name);
13765   int val;
13766
13767   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13768     (*p) += len + 1;
13769     sscanf(*p, "%d", &val);
13770     *loc = (val != 0);
13771     while (**p && **p != ' ')
13772       (*p)++;
13773     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13774     SendToProgram(buf, cps);
13775     return TRUE;
13776   }
13777   return FALSE;
13778 }
13779
13780 int
13781 IntFeature(p, name, loc, cps)
13782      char **p;
13783      char *name;
13784      int *loc;
13785      ChessProgramState *cps;
13786 {
13787   char buf[MSG_SIZ];
13788   int len = strlen(name);
13789   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13790     (*p) += len + 1;
13791     sscanf(*p, "%d", loc);
13792     while (**p && **p != ' ') (*p)++;
13793     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13794     SendToProgram(buf, cps);
13795     return TRUE;
13796   }
13797   return FALSE;
13798 }
13799
13800 int
13801 StringFeature(p, name, loc, cps)
13802      char **p;
13803      char *name;
13804      char loc[];
13805      ChessProgramState *cps;
13806 {
13807   char buf[MSG_SIZ];
13808   int len = strlen(name);
13809   if (strncmp((*p), name, len) == 0
13810       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13811     (*p) += len + 2;
13812     sscanf(*p, "%[^\"]", loc);
13813     while (**p && **p != '\"') (*p)++;
13814     if (**p == '\"') (*p)++;
13815     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13816     SendToProgram(buf, cps);
13817     return TRUE;
13818   }
13819   return FALSE;
13820 }
13821
13822 int
13823 ParseOption(Option *opt, ChessProgramState *cps)
13824 // [HGM] options: process the string that defines an engine option, and determine
13825 // name, type, default value, and allowed value range
13826 {
13827         char *p, *q, buf[MSG_SIZ];
13828         int n, min = (-1)<<31, max = 1<<31, def;
13829
13830         if(p = strstr(opt->name, " -spin ")) {
13831             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13832             if(max < min) max = min; // enforce consistency
13833             if(def < min) def = min;
13834             if(def > max) def = max;
13835             opt->value = def;
13836             opt->min = min;
13837             opt->max = max;
13838             opt->type = Spin;
13839         } else if((p = strstr(opt->name, " -slider "))) {
13840             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13841             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13842             if(max < min) max = min; // enforce consistency
13843             if(def < min) def = min;
13844             if(def > max) def = max;
13845             opt->value = def;
13846             opt->min = min;
13847             opt->max = max;
13848             opt->type = Spin; // Slider;
13849         } else if((p = strstr(opt->name, " -string "))) {
13850             opt->textValue = p+9;
13851             opt->type = TextBox;
13852         } else if((p = strstr(opt->name, " -file "))) {
13853             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13854             opt->textValue = p+7;
13855             opt->type = TextBox; // FileName;
13856         } else if((p = strstr(opt->name, " -path "))) {
13857             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13858             opt->textValue = p+7;
13859             opt->type = TextBox; // PathName;
13860         } else if(p = strstr(opt->name, " -check ")) {
13861             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13862             opt->value = (def != 0);
13863             opt->type = CheckBox;
13864         } else if(p = strstr(opt->name, " -combo ")) {
13865             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13866             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13867             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13868             opt->value = n = 0;
13869             while(q = StrStr(q, " /// ")) {
13870                 n++; *q = 0;    // count choices, and null-terminate each of them
13871                 q += 5;
13872                 if(*q == '*') { // remember default, which is marked with * prefix
13873                     q++;
13874                     opt->value = n;
13875                 }
13876                 cps->comboList[cps->comboCnt++] = q;
13877             }
13878             cps->comboList[cps->comboCnt++] = NULL;
13879             opt->max = n + 1;
13880             opt->type = ComboBox;
13881         } else if(p = strstr(opt->name, " -button")) {
13882             opt->type = Button;
13883         } else if(p = strstr(opt->name, " -save")) {
13884             opt->type = SaveButton;
13885         } else return FALSE;
13886         *p = 0; // terminate option name
13887         // now look if the command-line options define a setting for this engine option.
13888         if(cps->optionSettings && cps->optionSettings[0])
13889             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13890         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13891           snprintf(buf, MSG_SIZ, "option %s", p);
13892                 if(p = strstr(buf, ",")) *p = 0;
13893                 if(q = strchr(buf, '=')) switch(opt->type) {
13894                     case ComboBox:
13895                         for(n=0; n<opt->max; n++)
13896                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13897                         break;
13898                     case TextBox:
13899                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13900                         break;
13901                     case Spin:
13902                     case CheckBox:
13903                         opt->value = atoi(q+1);
13904                     default:
13905                         break;
13906                 }
13907                 strcat(buf, "\n");
13908                 SendToProgram(buf, cps);
13909         }
13910         return TRUE;
13911 }
13912
13913 void
13914 FeatureDone(cps, val)
13915      ChessProgramState* cps;
13916      int val;
13917 {
13918   DelayedEventCallback cb = GetDelayedEvent();
13919   if ((cb == InitBackEnd3 && cps == &first) ||
13920       (cb == SettingsMenuIfReady && cps == &second) ||
13921       (cb == TwoMachinesEventIfReady && cps == &second)) {
13922     CancelDelayedEvent();
13923     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13924   }
13925   cps->initDone = val;
13926 }
13927
13928 /* Parse feature command from engine */
13929 void
13930 ParseFeatures(args, cps)
13931      char* args;
13932      ChessProgramState *cps;
13933 {
13934   char *p = args;
13935   char *q;
13936   int val;
13937   char buf[MSG_SIZ];
13938
13939   for (;;) {
13940     while (*p == ' ') p++;
13941     if (*p == NULLCHAR) return;
13942
13943     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13944     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13945     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13946     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13947     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13948     if (BoolFeature(&p, "reuse", &val, cps)) {
13949       /* Engine can disable reuse, but can't enable it if user said no */
13950       if (!val) cps->reuse = FALSE;
13951       continue;
13952     }
13953     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13954     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13955       if (gameMode == TwoMachinesPlay) {
13956         DisplayTwoMachinesTitle();
13957       } else {
13958         DisplayTitle("");
13959       }
13960       continue;
13961     }
13962     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13963     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13964     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13965     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13966     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13967     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13968     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13969     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13970     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13971     if (IntFeature(&p, "done", &val, cps)) {
13972       FeatureDone(cps, val);
13973       continue;
13974     }
13975     /* Added by Tord: */
13976     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13977     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13978     /* End of additions by Tord */
13979
13980     /* [HGM] added features: */
13981     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13982     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13983     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13984     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13985     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13986     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13987     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13988         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13989           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13990             SendToProgram(buf, cps);
13991             continue;
13992         }
13993         if(cps->nrOptions >= MAX_OPTIONS) {
13994             cps->nrOptions--;
13995             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13996             DisplayError(buf, 0);
13997         }
13998         continue;
13999     }
14000     /* End of additions by HGM */
14001
14002     /* unknown feature: complain and skip */
14003     q = p;
14004     while (*q && *q != '=') q++;
14005     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14006     SendToProgram(buf, cps);
14007     p = q;
14008     if (*p == '=') {
14009       p++;
14010       if (*p == '\"') {
14011         p++;
14012         while (*p && *p != '\"') p++;
14013         if (*p == '\"') p++;
14014       } else {
14015         while (*p && *p != ' ') p++;
14016       }
14017     }
14018   }
14019
14020 }
14021
14022 void
14023 PeriodicUpdatesEvent(newState)
14024      int newState;
14025 {
14026     if (newState == appData.periodicUpdates)
14027       return;
14028
14029     appData.periodicUpdates=newState;
14030
14031     /* Display type changes, so update it now */
14032 //    DisplayAnalysis();
14033
14034     /* Get the ball rolling again... */
14035     if (newState) {
14036         AnalysisPeriodicEvent(1);
14037         StartAnalysisClock();
14038     }
14039 }
14040
14041 void
14042 PonderNextMoveEvent(newState)
14043      int newState;
14044 {
14045     if (newState == appData.ponderNextMove) return;
14046     if (gameMode == EditPosition) EditPositionDone(TRUE);
14047     if (newState) {
14048         SendToProgram("hard\n", &first);
14049         if (gameMode == TwoMachinesPlay) {
14050             SendToProgram("hard\n", &second);
14051         }
14052     } else {
14053         SendToProgram("easy\n", &first);
14054         thinkOutput[0] = NULLCHAR;
14055         if (gameMode == TwoMachinesPlay) {
14056             SendToProgram("easy\n", &second);
14057         }
14058     }
14059     appData.ponderNextMove = newState;
14060 }
14061
14062 void
14063 NewSettingEvent(option, feature, command, value)
14064      char *command;
14065      int option, value, *feature;
14066 {
14067     char buf[MSG_SIZ];
14068
14069     if (gameMode == EditPosition) EditPositionDone(TRUE);
14070     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14071     if(feature == NULL || *feature) SendToProgram(buf, &first);
14072     if (gameMode == TwoMachinesPlay) {
14073         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14074     }
14075 }
14076
14077 void
14078 ShowThinkingEvent()
14079 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14080 {
14081     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14082     int newState = appData.showThinking
14083         // [HGM] thinking: other features now need thinking output as well
14084         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14085
14086     if (oldState == newState) return;
14087     oldState = newState;
14088     if (gameMode == EditPosition) EditPositionDone(TRUE);
14089     if (oldState) {
14090         SendToProgram("post\n", &first);
14091         if (gameMode == TwoMachinesPlay) {
14092             SendToProgram("post\n", &second);
14093         }
14094     } else {
14095         SendToProgram("nopost\n", &first);
14096         thinkOutput[0] = NULLCHAR;
14097         if (gameMode == TwoMachinesPlay) {
14098             SendToProgram("nopost\n", &second);
14099         }
14100     }
14101 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14102 }
14103
14104 void
14105 AskQuestionEvent(title, question, replyPrefix, which)
14106      char *title; char *question; char *replyPrefix; char *which;
14107 {
14108   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14109   if (pr == NoProc) return;
14110   AskQuestion(title, question, replyPrefix, pr);
14111 }
14112
14113 void
14114 DisplayMove(moveNumber)
14115      int moveNumber;
14116 {
14117     char message[MSG_SIZ];
14118     char res[MSG_SIZ];
14119     char cpThinkOutput[MSG_SIZ];
14120
14121     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14122
14123     if (moveNumber == forwardMostMove - 1 ||
14124         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14125
14126         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14127
14128         if (strchr(cpThinkOutput, '\n')) {
14129             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14130         }
14131     } else {
14132         *cpThinkOutput = NULLCHAR;
14133     }
14134
14135     /* [AS] Hide thinking from human user */
14136     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14137         *cpThinkOutput = NULLCHAR;
14138         if( thinkOutput[0] != NULLCHAR ) {
14139             int i;
14140
14141             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14142                 cpThinkOutput[i] = '.';
14143             }
14144             cpThinkOutput[i] = NULLCHAR;
14145             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14146         }
14147     }
14148
14149     if (moveNumber == forwardMostMove - 1 &&
14150         gameInfo.resultDetails != NULL) {
14151         if (gameInfo.resultDetails[0] == NULLCHAR) {
14152           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14153         } else {
14154           snprintf(res, MSG_SIZ, " {%s} %s",
14155                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14156         }
14157     } else {
14158         res[0] = NULLCHAR;
14159     }
14160
14161     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14162         DisplayMessage(res, cpThinkOutput);
14163     } else {
14164       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14165                 WhiteOnMove(moveNumber) ? " " : ".. ",
14166                 parseList[moveNumber], res);
14167         DisplayMessage(message, cpThinkOutput);
14168     }
14169 }
14170
14171 void
14172 DisplayComment(moveNumber, text)
14173      int moveNumber;
14174      char *text;
14175 {
14176     char title[MSG_SIZ];
14177     char buf[8000]; // comment can be long!
14178     int score, depth;
14179
14180     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14181       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14182     } else {
14183       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14184               WhiteOnMove(moveNumber) ? " " : ".. ",
14185               parseList[moveNumber]);
14186     }
14187     // [HGM] PV info: display PV info together with (or as) comment
14188     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14189       if(text == NULL) text = "";
14190       score = pvInfoList[moveNumber].score;
14191       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14192               depth, (pvInfoList[moveNumber].time+50)/100, text);
14193       text = buf;
14194     }
14195     if (text != NULL && (appData.autoDisplayComment || commentUp))
14196         CommentPopUp(title, text);
14197 }
14198
14199 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14200  * might be busy thinking or pondering.  It can be omitted if your
14201  * gnuchess is configured to stop thinking immediately on any user
14202  * input.  However, that gnuchess feature depends on the FIONREAD
14203  * ioctl, which does not work properly on some flavors of Unix.
14204  */
14205 void
14206 Attention(cps)
14207      ChessProgramState *cps;
14208 {
14209 #if ATTENTION
14210     if (!cps->useSigint) return;
14211     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14212     switch (gameMode) {
14213       case MachinePlaysWhite:
14214       case MachinePlaysBlack:
14215       case TwoMachinesPlay:
14216       case IcsPlayingWhite:
14217       case IcsPlayingBlack:
14218       case AnalyzeMode:
14219       case AnalyzeFile:
14220         /* Skip if we know it isn't thinking */
14221         if (!cps->maybeThinking) return;
14222         if (appData.debugMode)
14223           fprintf(debugFP, "Interrupting %s\n", cps->which);
14224         InterruptChildProcess(cps->pr);
14225         cps->maybeThinking = FALSE;
14226         break;
14227       default:
14228         break;
14229     }
14230 #endif /*ATTENTION*/
14231 }
14232
14233 int
14234 CheckFlags()
14235 {
14236     if (whiteTimeRemaining <= 0) {
14237         if (!whiteFlag) {
14238             whiteFlag = TRUE;
14239             if (appData.icsActive) {
14240                 if (appData.autoCallFlag &&
14241                     gameMode == IcsPlayingBlack && !blackFlag) {
14242                   SendToICS(ics_prefix);
14243                   SendToICS("flag\n");
14244                 }
14245             } else {
14246                 if (blackFlag) {
14247                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14248                 } else {
14249                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14250                     if (appData.autoCallFlag) {
14251                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14252                         return TRUE;
14253                     }
14254                 }
14255             }
14256         }
14257     }
14258     if (blackTimeRemaining <= 0) {
14259         if (!blackFlag) {
14260             blackFlag = TRUE;
14261             if (appData.icsActive) {
14262                 if (appData.autoCallFlag &&
14263                     gameMode == IcsPlayingWhite && !whiteFlag) {
14264                   SendToICS(ics_prefix);
14265                   SendToICS("flag\n");
14266                 }
14267             } else {
14268                 if (whiteFlag) {
14269                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14270                 } else {
14271                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14272                     if (appData.autoCallFlag) {
14273                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14274                         return TRUE;
14275                     }
14276                 }
14277             }
14278         }
14279     }
14280     return FALSE;
14281 }
14282
14283 void
14284 CheckTimeControl()
14285 {
14286     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14287         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14288
14289     /*
14290      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14291      */
14292     if ( !WhiteOnMove(forwardMostMove) ) {
14293         /* White made time control */
14294         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14295         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14296         /* [HGM] time odds: correct new time quota for time odds! */
14297                                             / WhitePlayer()->timeOdds;
14298         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14299     } else {
14300         lastBlack -= blackTimeRemaining;
14301         /* Black made time control */
14302         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14303                                             / WhitePlayer()->other->timeOdds;
14304         lastWhite = whiteTimeRemaining;
14305     }
14306 }
14307
14308 void
14309 DisplayBothClocks()
14310 {
14311     int wom = gameMode == EditPosition ?
14312       !blackPlaysFirst : WhiteOnMove(currentMove);
14313     DisplayWhiteClock(whiteTimeRemaining, wom);
14314     DisplayBlackClock(blackTimeRemaining, !wom);
14315 }
14316
14317
14318 /* Timekeeping seems to be a portability nightmare.  I think everyone
14319    has ftime(), but I'm really not sure, so I'm including some ifdefs
14320    to use other calls if you don't.  Clocks will be less accurate if
14321    you have neither ftime nor gettimeofday.
14322 */
14323
14324 /* VS 2008 requires the #include outside of the function */
14325 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14326 #include <sys/timeb.h>
14327 #endif
14328
14329 /* Get the current time as a TimeMark */
14330 void
14331 GetTimeMark(tm)
14332      TimeMark *tm;
14333 {
14334 #if HAVE_GETTIMEOFDAY
14335
14336     struct timeval timeVal;
14337     struct timezone timeZone;
14338
14339     gettimeofday(&timeVal, &timeZone);
14340     tm->sec = (long) timeVal.tv_sec;
14341     tm->ms = (int) (timeVal.tv_usec / 1000L);
14342
14343 #else /*!HAVE_GETTIMEOFDAY*/
14344 #if HAVE_FTIME
14345
14346 // include <sys/timeb.h> / moved to just above start of function
14347     struct timeb timeB;
14348
14349     ftime(&timeB);
14350     tm->sec = (long) timeB.time;
14351     tm->ms = (int) timeB.millitm;
14352
14353 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14354     tm->sec = (long) time(NULL);
14355     tm->ms = 0;
14356 #endif
14357 #endif
14358 }
14359
14360 /* Return the difference in milliseconds between two
14361    time marks.  We assume the difference will fit in a long!
14362 */
14363 long
14364 SubtractTimeMarks(tm2, tm1)
14365      TimeMark *tm2, *tm1;
14366 {
14367     return 1000L*(tm2->sec - tm1->sec) +
14368            (long) (tm2->ms - tm1->ms);
14369 }
14370
14371
14372 /*
14373  * Code to manage the game clocks.
14374  *
14375  * In tournament play, black starts the clock and then white makes a move.
14376  * We give the human user a slight advantage if he is playing white---the
14377  * clocks don't run until he makes his first move, so it takes zero time.
14378  * Also, we don't account for network lag, so we could get out of sync
14379  * with GNU Chess's clock -- but then, referees are always right.
14380  */
14381
14382 static TimeMark tickStartTM;
14383 static long intendedTickLength;
14384
14385 long
14386 NextTickLength(timeRemaining)
14387      long timeRemaining;
14388 {
14389     long nominalTickLength, nextTickLength;
14390
14391     if (timeRemaining > 0L && timeRemaining <= 10000L)
14392       nominalTickLength = 100L;
14393     else
14394       nominalTickLength = 1000L;
14395     nextTickLength = timeRemaining % nominalTickLength;
14396     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14397
14398     return nextTickLength;
14399 }
14400
14401 /* Adjust clock one minute up or down */
14402 void
14403 AdjustClock(Boolean which, int dir)
14404 {
14405     if(which) blackTimeRemaining += 60000*dir;
14406     else      whiteTimeRemaining += 60000*dir;
14407     DisplayBothClocks();
14408 }
14409
14410 /* Stop clocks and reset to a fresh time control */
14411 void
14412 ResetClocks()
14413 {
14414     (void) StopClockTimer();
14415     if (appData.icsActive) {
14416         whiteTimeRemaining = blackTimeRemaining = 0;
14417     } else if (searchTime) {
14418         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14419         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14420     } else { /* [HGM] correct new time quote for time odds */
14421         whiteTC = blackTC = fullTimeControlString;
14422         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14423         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14424     }
14425     if (whiteFlag || blackFlag) {
14426         DisplayTitle("");
14427         whiteFlag = blackFlag = FALSE;
14428     }
14429     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14430     DisplayBothClocks();
14431 }
14432
14433 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14434
14435 /* Decrement running clock by amount of time that has passed */
14436 void
14437 DecrementClocks()
14438 {
14439     long timeRemaining;
14440     long lastTickLength, fudge;
14441     TimeMark now;
14442
14443     if (!appData.clockMode) return;
14444     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14445
14446     GetTimeMark(&now);
14447
14448     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14449
14450     /* Fudge if we woke up a little too soon */
14451     fudge = intendedTickLength - lastTickLength;
14452     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14453
14454     if (WhiteOnMove(forwardMostMove)) {
14455         if(whiteNPS >= 0) lastTickLength = 0;
14456         timeRemaining = whiteTimeRemaining -= lastTickLength;
14457         if(timeRemaining < 0 && !appData.icsActive) {
14458             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14459             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14460                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14461                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14462             }
14463         }
14464         DisplayWhiteClock(whiteTimeRemaining - fudge,
14465                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14466     } else {
14467         if(blackNPS >= 0) lastTickLength = 0;
14468         timeRemaining = blackTimeRemaining -= lastTickLength;
14469         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14470             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14471             if(suddenDeath) {
14472                 blackStartMove = forwardMostMove;
14473                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14474             }
14475         }
14476         DisplayBlackClock(blackTimeRemaining - fudge,
14477                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14478     }
14479     if (CheckFlags()) return;
14480
14481     tickStartTM = now;
14482     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14483     StartClockTimer(intendedTickLength);
14484
14485     /* if the time remaining has fallen below the alarm threshold, sound the
14486      * alarm. if the alarm has sounded and (due to a takeback or time control
14487      * with increment) the time remaining has increased to a level above the
14488      * threshold, reset the alarm so it can sound again.
14489      */
14490
14491     if (appData.icsActive && appData.icsAlarm) {
14492
14493         /* make sure we are dealing with the user's clock */
14494         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14495                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14496            )) return;
14497
14498         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14499             alarmSounded = FALSE;
14500         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14501             PlayAlarmSound();
14502             alarmSounded = TRUE;
14503         }
14504     }
14505 }
14506
14507
14508 /* A player has just moved, so stop the previously running
14509    clock and (if in clock mode) start the other one.
14510    We redisplay both clocks in case we're in ICS mode, because
14511    ICS gives us an update to both clocks after every move.
14512    Note that this routine is called *after* forwardMostMove
14513    is updated, so the last fractional tick must be subtracted
14514    from the color that is *not* on move now.
14515 */
14516 void
14517 SwitchClocks(int newMoveNr)
14518 {
14519     long lastTickLength;
14520     TimeMark now;
14521     int flagged = FALSE;
14522
14523     GetTimeMark(&now);
14524
14525     if (StopClockTimer() && appData.clockMode) {
14526         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14527         if (!WhiteOnMove(forwardMostMove)) {
14528             if(blackNPS >= 0) lastTickLength = 0;
14529             blackTimeRemaining -= lastTickLength;
14530            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14531 //         if(pvInfoList[forwardMostMove-1].time == -1)
14532                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14533                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14534         } else {
14535            if(whiteNPS >= 0) lastTickLength = 0;
14536            whiteTimeRemaining -= lastTickLength;
14537            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14538 //         if(pvInfoList[forwardMostMove-1].time == -1)
14539                  pvInfoList[forwardMostMove-1].time =
14540                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14541         }
14542         flagged = CheckFlags();
14543     }
14544     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14545     CheckTimeControl();
14546
14547     if (flagged || !appData.clockMode) return;
14548
14549     switch (gameMode) {
14550       case MachinePlaysBlack:
14551       case MachinePlaysWhite:
14552       case BeginningOfGame:
14553         if (pausing) return;
14554         break;
14555
14556       case EditGame:
14557       case PlayFromGameFile:
14558       case IcsExamining:
14559         return;
14560
14561       default:
14562         break;
14563     }
14564
14565     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14566         if(WhiteOnMove(forwardMostMove))
14567              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14568         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14569     }
14570
14571     tickStartTM = now;
14572     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14573       whiteTimeRemaining : blackTimeRemaining);
14574     StartClockTimer(intendedTickLength);
14575 }
14576
14577
14578 /* Stop both clocks */
14579 void
14580 StopClocks()
14581 {
14582     long lastTickLength;
14583     TimeMark now;
14584
14585     if (!StopClockTimer()) return;
14586     if (!appData.clockMode) return;
14587
14588     GetTimeMark(&now);
14589
14590     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14591     if (WhiteOnMove(forwardMostMove)) {
14592         if(whiteNPS >= 0) lastTickLength = 0;
14593         whiteTimeRemaining -= lastTickLength;
14594         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14595     } else {
14596         if(blackNPS >= 0) lastTickLength = 0;
14597         blackTimeRemaining -= lastTickLength;
14598         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14599     }
14600     CheckFlags();
14601 }
14602
14603 /* Start clock of player on move.  Time may have been reset, so
14604    if clock is already running, stop and restart it. */
14605 void
14606 StartClocks()
14607 {
14608     (void) StopClockTimer(); /* in case it was running already */
14609     DisplayBothClocks();
14610     if (CheckFlags()) return;
14611
14612     if (!appData.clockMode) return;
14613     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14614
14615     GetTimeMark(&tickStartTM);
14616     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14617       whiteTimeRemaining : blackTimeRemaining);
14618
14619    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14620     whiteNPS = blackNPS = -1;
14621     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14622        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14623         whiteNPS = first.nps;
14624     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14625        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14626         blackNPS = first.nps;
14627     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14628         whiteNPS = second.nps;
14629     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14630         blackNPS = second.nps;
14631     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14632
14633     StartClockTimer(intendedTickLength);
14634 }
14635
14636 char *
14637 TimeString(ms)
14638      long ms;
14639 {
14640     long second, minute, hour, day;
14641     char *sign = "";
14642     static char buf[32];
14643
14644     if (ms > 0 && ms <= 9900) {
14645       /* convert milliseconds to tenths, rounding up */
14646       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14647
14648       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14649       return buf;
14650     }
14651
14652     /* convert milliseconds to seconds, rounding up */
14653     /* use floating point to avoid strangeness of integer division
14654        with negative dividends on many machines */
14655     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14656
14657     if (second < 0) {
14658         sign = "-";
14659         second = -second;
14660     }
14661
14662     day = second / (60 * 60 * 24);
14663     second = second % (60 * 60 * 24);
14664     hour = second / (60 * 60);
14665     second = second % (60 * 60);
14666     minute = second / 60;
14667     second = second % 60;
14668
14669     if (day > 0)
14670       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14671               sign, day, hour, minute, second);
14672     else if (hour > 0)
14673       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14674     else
14675       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14676
14677     return buf;
14678 }
14679
14680
14681 /*
14682  * This is necessary because some C libraries aren't ANSI C compliant yet.
14683  */
14684 char *
14685 StrStr(string, match)
14686      char *string, *match;
14687 {
14688     int i, length;
14689
14690     length = strlen(match);
14691
14692     for (i = strlen(string) - length; i >= 0; i--, string++)
14693       if (!strncmp(match, string, length))
14694         return string;
14695
14696     return NULL;
14697 }
14698
14699 char *
14700 StrCaseStr(string, match)
14701      char *string, *match;
14702 {
14703     int i, j, length;
14704
14705     length = strlen(match);
14706
14707     for (i = strlen(string) - length; i >= 0; i--, string++) {
14708         for (j = 0; j < length; j++) {
14709             if (ToLower(match[j]) != ToLower(string[j]))
14710               break;
14711         }
14712         if (j == length) return string;
14713     }
14714
14715     return NULL;
14716 }
14717
14718 #ifndef _amigados
14719 int
14720 StrCaseCmp(s1, s2)
14721      char *s1, *s2;
14722 {
14723     char c1, c2;
14724
14725     for (;;) {
14726         c1 = ToLower(*s1++);
14727         c2 = ToLower(*s2++);
14728         if (c1 > c2) return 1;
14729         if (c1 < c2) return -1;
14730         if (c1 == NULLCHAR) return 0;
14731     }
14732 }
14733
14734
14735 int
14736 ToLower(c)
14737      int c;
14738 {
14739     return isupper(c) ? tolower(c) : c;
14740 }
14741
14742
14743 int
14744 ToUpper(c)
14745      int c;
14746 {
14747     return islower(c) ? toupper(c) : c;
14748 }
14749 #endif /* !_amigados    */
14750
14751 char *
14752 StrSave(s)
14753      char *s;
14754 {
14755   char *ret;
14756
14757   if ((ret = (char *) malloc(strlen(s) + 1)))
14758     {
14759       safeStrCpy(ret, s, strlen(s)+1);
14760     }
14761   return ret;
14762 }
14763
14764 char *
14765 StrSavePtr(s, savePtr)
14766      char *s, **savePtr;
14767 {
14768     if (*savePtr) {
14769         free(*savePtr);
14770     }
14771     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14772       safeStrCpy(*savePtr, s, strlen(s)+1);
14773     }
14774     return(*savePtr);
14775 }
14776
14777 char *
14778 PGNDate()
14779 {
14780     time_t clock;
14781     struct tm *tm;
14782     char buf[MSG_SIZ];
14783
14784     clock = time((time_t *)NULL);
14785     tm = localtime(&clock);
14786     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14787             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14788     return StrSave(buf);
14789 }
14790
14791
14792 char *
14793 PositionToFEN(move, overrideCastling)
14794      int move;
14795      char *overrideCastling;
14796 {
14797     int i, j, fromX, fromY, toX, toY;
14798     int whiteToPlay;
14799     char buf[128];
14800     char *p, *q;
14801     int emptycount;
14802     ChessSquare piece;
14803
14804     whiteToPlay = (gameMode == EditPosition) ?
14805       !blackPlaysFirst : (move % 2 == 0);
14806     p = buf;
14807
14808     /* Piece placement data */
14809     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14810         emptycount = 0;
14811         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14812             if (boards[move][i][j] == EmptySquare) {
14813                 emptycount++;
14814             } else { ChessSquare piece = boards[move][i][j];
14815                 if (emptycount > 0) {
14816                     if(emptycount<10) /* [HGM] can be >= 10 */
14817                         *p++ = '0' + emptycount;
14818                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14819                     emptycount = 0;
14820                 }
14821                 if(PieceToChar(piece) == '+') {
14822                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14823                     *p++ = '+';
14824                     piece = (ChessSquare)(DEMOTED piece);
14825                 }
14826                 *p++ = PieceToChar(piece);
14827                 if(p[-1] == '~') {
14828                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14829                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14830                     *p++ = '~';
14831                 }
14832             }
14833         }
14834         if (emptycount > 0) {
14835             if(emptycount<10) /* [HGM] can be >= 10 */
14836                 *p++ = '0' + emptycount;
14837             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14838             emptycount = 0;
14839         }
14840         *p++ = '/';
14841     }
14842     *(p - 1) = ' ';
14843
14844     /* [HGM] print Crazyhouse or Shogi holdings */
14845     if( gameInfo.holdingsWidth ) {
14846         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14847         q = p;
14848         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14849             piece = boards[move][i][BOARD_WIDTH-1];
14850             if( piece != EmptySquare )
14851               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14852                   *p++ = PieceToChar(piece);
14853         }
14854         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14855             piece = boards[move][BOARD_HEIGHT-i-1][0];
14856             if( piece != EmptySquare )
14857               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14858                   *p++ = PieceToChar(piece);
14859         }
14860
14861         if( q == p ) *p++ = '-';
14862         *p++ = ']';
14863         *p++ = ' ';
14864     }
14865
14866     /* Active color */
14867     *p++ = whiteToPlay ? 'w' : 'b';
14868     *p++ = ' ';
14869
14870   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14871     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14872   } else {
14873   if(nrCastlingRights) {
14874      q = p;
14875      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14876        /* [HGM] write directly from rights */
14877            if(boards[move][CASTLING][2] != NoRights &&
14878               boards[move][CASTLING][0] != NoRights   )
14879                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14880            if(boards[move][CASTLING][2] != NoRights &&
14881               boards[move][CASTLING][1] != NoRights   )
14882                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14883            if(boards[move][CASTLING][5] != NoRights &&
14884               boards[move][CASTLING][3] != NoRights   )
14885                 *p++ = boards[move][CASTLING][3] + AAA;
14886            if(boards[move][CASTLING][5] != NoRights &&
14887               boards[move][CASTLING][4] != NoRights   )
14888                 *p++ = boards[move][CASTLING][4] + AAA;
14889      } else {
14890
14891         /* [HGM] write true castling rights */
14892         if( nrCastlingRights == 6 ) {
14893             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14894                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14895             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14896                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14897             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14898                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14899             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14900                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14901         }
14902      }
14903      if (q == p) *p++ = '-'; /* No castling rights */
14904      *p++ = ' ';
14905   }
14906
14907   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14908      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14909     /* En passant target square */
14910     if (move > backwardMostMove) {
14911         fromX = moveList[move - 1][0] - AAA;
14912         fromY = moveList[move - 1][1] - ONE;
14913         toX = moveList[move - 1][2] - AAA;
14914         toY = moveList[move - 1][3] - ONE;
14915         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14916             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14917             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14918             fromX == toX) {
14919             /* 2-square pawn move just happened */
14920             *p++ = toX + AAA;
14921             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14922         } else {
14923             *p++ = '-';
14924         }
14925     } else if(move == backwardMostMove) {
14926         // [HGM] perhaps we should always do it like this, and forget the above?
14927         if((signed char)boards[move][EP_STATUS] >= 0) {
14928             *p++ = boards[move][EP_STATUS] + AAA;
14929             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14930         } else {
14931             *p++ = '-';
14932         }
14933     } else {
14934         *p++ = '-';
14935     }
14936     *p++ = ' ';
14937   }
14938   }
14939
14940     /* [HGM] find reversible plies */
14941     {   int i = 0, j=move;
14942
14943         if (appData.debugMode) { int k;
14944             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14945             for(k=backwardMostMove; k<=forwardMostMove; k++)
14946                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14947
14948         }
14949
14950         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14951         if( j == backwardMostMove ) i += initialRulePlies;
14952         sprintf(p, "%d ", i);
14953         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14954     }
14955     /* Fullmove number */
14956     sprintf(p, "%d", (move / 2) + 1);
14957
14958     return StrSave(buf);
14959 }
14960
14961 Boolean
14962 ParseFEN(board, blackPlaysFirst, fen)
14963     Board board;
14964      int *blackPlaysFirst;
14965      char *fen;
14966 {
14967     int i, j;
14968     char *p, c;
14969     int emptycount;
14970     ChessSquare piece;
14971
14972     p = fen;
14973
14974     /* [HGM] by default clear Crazyhouse holdings, if present */
14975     if(gameInfo.holdingsWidth) {
14976        for(i=0; i<BOARD_HEIGHT; i++) {
14977            board[i][0]             = EmptySquare; /* black holdings */
14978            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14979            board[i][1]             = (ChessSquare) 0; /* black counts */
14980            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14981        }
14982     }
14983
14984     /* Piece placement data */
14985     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14986         j = 0;
14987         for (;;) {
14988             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14989                 if (*p == '/') p++;
14990                 emptycount = gameInfo.boardWidth - j;
14991                 while (emptycount--)
14992                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14993                 break;
14994 #if(BOARD_FILES >= 10)
14995             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14996                 p++; emptycount=10;
14997                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14998                 while (emptycount--)
14999                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15000 #endif
15001             } else if (isdigit(*p)) {
15002                 emptycount = *p++ - '0';
15003                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15004                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15005                 while (emptycount--)
15006                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15007             } else if (*p == '+' || isalpha(*p)) {
15008                 if (j >= gameInfo.boardWidth) return FALSE;
15009                 if(*p=='+') {
15010                     piece = CharToPiece(*++p);
15011                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15012                     piece = (ChessSquare) (PROMOTED piece ); p++;
15013                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15014                 } else piece = CharToPiece(*p++);
15015
15016                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15017                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15018                     piece = (ChessSquare) (PROMOTED piece);
15019                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15020                     p++;
15021                 }
15022                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15023             } else {
15024                 return FALSE;
15025             }
15026         }
15027     }
15028     while (*p == '/' || *p == ' ') p++;
15029
15030     /* [HGM] look for Crazyhouse holdings here */
15031     while(*p==' ') p++;
15032     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15033         if(*p == '[') p++;
15034         if(*p == '-' ) p++; /* empty holdings */ else {
15035             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15036             /* if we would allow FEN reading to set board size, we would   */
15037             /* have to add holdings and shift the board read so far here   */
15038             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15039                 p++;
15040                 if((int) piece >= (int) BlackPawn ) {
15041                     i = (int)piece - (int)BlackPawn;
15042                     i = PieceToNumber((ChessSquare)i);
15043                     if( i >= gameInfo.holdingsSize ) return FALSE;
15044                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15045                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15046                 } else {
15047                     i = (int)piece - (int)WhitePawn;
15048                     i = PieceToNumber((ChessSquare)i);
15049                     if( i >= gameInfo.holdingsSize ) return FALSE;
15050                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15051                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15052                 }
15053             }
15054         }
15055         if(*p == ']') p++;
15056     }
15057
15058     while(*p == ' ') p++;
15059
15060     /* Active color */
15061     c = *p++;
15062     if(appData.colorNickNames) {
15063       if( c == appData.colorNickNames[0] ) c = 'w'; else
15064       if( c == appData.colorNickNames[1] ) c = 'b';
15065     }
15066     switch (c) {
15067       case 'w':
15068         *blackPlaysFirst = FALSE;
15069         break;
15070       case 'b':
15071         *blackPlaysFirst = TRUE;
15072         break;
15073       default:
15074         return FALSE;
15075     }
15076
15077     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15078     /* return the extra info in global variiables             */
15079
15080     /* set defaults in case FEN is incomplete */
15081     board[EP_STATUS] = EP_UNKNOWN;
15082     for(i=0; i<nrCastlingRights; i++ ) {
15083         board[CASTLING][i] =
15084             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15085     }   /* assume possible unless obviously impossible */
15086     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15087     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15088     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15089                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15090     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15091     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15092     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15093                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15094     FENrulePlies = 0;
15095
15096     while(*p==' ') p++;
15097     if(nrCastlingRights) {
15098       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15099           /* castling indicator present, so default becomes no castlings */
15100           for(i=0; i<nrCastlingRights; i++ ) {
15101                  board[CASTLING][i] = NoRights;
15102           }
15103       }
15104       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15105              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15106              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15107              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15108         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15109
15110         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15111             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15112             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15113         }
15114         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15115             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15116         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15117                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15118         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15119                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15120         switch(c) {
15121           case'K':
15122               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15123               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15124               board[CASTLING][2] = whiteKingFile;
15125               break;
15126           case'Q':
15127               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15128               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15129               board[CASTLING][2] = whiteKingFile;
15130               break;
15131           case'k':
15132               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15133               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15134               board[CASTLING][5] = blackKingFile;
15135               break;
15136           case'q':
15137               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15138               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15139               board[CASTLING][5] = blackKingFile;
15140           case '-':
15141               break;
15142           default: /* FRC castlings */
15143               if(c >= 'a') { /* black rights */
15144                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15145                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15146                   if(i == BOARD_RGHT) break;
15147                   board[CASTLING][5] = i;
15148                   c -= AAA;
15149                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15150                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15151                   if(c > i)
15152                       board[CASTLING][3] = c;
15153                   else
15154                       board[CASTLING][4] = c;
15155               } else { /* white rights */
15156                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15157                     if(board[0][i] == WhiteKing) break;
15158                   if(i == BOARD_RGHT) break;
15159                   board[CASTLING][2] = i;
15160                   c -= AAA - 'a' + 'A';
15161                   if(board[0][c] >= WhiteKing) break;
15162                   if(c > i)
15163                       board[CASTLING][0] = c;
15164                   else
15165                       board[CASTLING][1] = c;
15166               }
15167         }
15168       }
15169       for(i=0; i<nrCastlingRights; i++)
15170         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15171     if (appData.debugMode) {
15172         fprintf(debugFP, "FEN castling rights:");
15173         for(i=0; i<nrCastlingRights; i++)
15174         fprintf(debugFP, " %d", board[CASTLING][i]);
15175         fprintf(debugFP, "\n");
15176     }
15177
15178       while(*p==' ') p++;
15179     }
15180
15181     /* read e.p. field in games that know e.p. capture */
15182     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15183        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15184       if(*p=='-') {
15185         p++; board[EP_STATUS] = EP_NONE;
15186       } else {
15187          char c = *p++ - AAA;
15188
15189          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15190          if(*p >= '0' && *p <='9') p++;
15191          board[EP_STATUS] = c;
15192       }
15193     }
15194
15195
15196     if(sscanf(p, "%d", &i) == 1) {
15197         FENrulePlies = i; /* 50-move ply counter */
15198         /* (The move number is still ignored)    */
15199     }
15200
15201     return TRUE;
15202 }
15203
15204 void
15205 EditPositionPasteFEN(char *fen)
15206 {
15207   if (fen != NULL) {
15208     Board initial_position;
15209
15210     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15211       DisplayError(_("Bad FEN position in clipboard"), 0);
15212       return ;
15213     } else {
15214       int savedBlackPlaysFirst = blackPlaysFirst;
15215       EditPositionEvent();
15216       blackPlaysFirst = savedBlackPlaysFirst;
15217       CopyBoard(boards[0], initial_position);
15218       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15219       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15220       DisplayBothClocks();
15221       DrawPosition(FALSE, boards[currentMove]);
15222     }
15223   }
15224 }
15225
15226 static char cseq[12] = "\\   ";
15227
15228 Boolean set_cont_sequence(char *new_seq)
15229 {
15230     int len;
15231     Boolean ret;
15232
15233     // handle bad attempts to set the sequence
15234         if (!new_seq)
15235                 return 0; // acceptable error - no debug
15236
15237     len = strlen(new_seq);
15238     ret = (len > 0) && (len < sizeof(cseq));
15239     if (ret)
15240       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15241     else if (appData.debugMode)
15242       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15243     return ret;
15244 }
15245
15246 /*
15247     reformat a source message so words don't cross the width boundary.  internal
15248     newlines are not removed.  returns the wrapped size (no null character unless
15249     included in source message).  If dest is NULL, only calculate the size required
15250     for the dest buffer.  lp argument indicats line position upon entry, and it's
15251     passed back upon exit.
15252 */
15253 int wrap(char *dest, char *src, int count, int width, int *lp)
15254 {
15255     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15256
15257     cseq_len = strlen(cseq);
15258     old_line = line = *lp;
15259     ansi = len = clen = 0;
15260
15261     for (i=0; i < count; i++)
15262     {
15263         if (src[i] == '\033')
15264             ansi = 1;
15265
15266         // if we hit the width, back up
15267         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15268         {
15269             // store i & len in case the word is too long
15270             old_i = i, old_len = len;
15271
15272             // find the end of the last word
15273             while (i && src[i] != ' ' && src[i] != '\n')
15274             {
15275                 i--;
15276                 len--;
15277             }
15278
15279             // word too long?  restore i & len before splitting it
15280             if ((old_i-i+clen) >= width)
15281             {
15282                 i = old_i;
15283                 len = old_len;
15284             }
15285
15286             // extra space?
15287             if (i && src[i-1] == ' ')
15288                 len--;
15289
15290             if (src[i] != ' ' && src[i] != '\n')
15291             {
15292                 i--;
15293                 if (len)
15294                     len--;
15295             }
15296
15297             // now append the newline and continuation sequence
15298             if (dest)
15299                 dest[len] = '\n';
15300             len++;
15301             if (dest)
15302                 strncpy(dest+len, cseq, cseq_len);
15303             len += cseq_len;
15304             line = cseq_len;
15305             clen = cseq_len;
15306             continue;
15307         }
15308
15309         if (dest)
15310             dest[len] = src[i];
15311         len++;
15312         if (!ansi)
15313             line++;
15314         if (src[i] == '\n')
15315             line = 0;
15316         if (src[i] == 'm')
15317             ansi = 0;
15318     }
15319     if (dest && appData.debugMode)
15320     {
15321         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15322             count, width, line, len, *lp);
15323         show_bytes(debugFP, src, count);
15324         fprintf(debugFP, "\ndest: ");
15325         show_bytes(debugFP, dest, len);
15326         fprintf(debugFP, "\n");
15327     }
15328     *lp = dest ? line : old_line;
15329
15330     return len;
15331 }
15332
15333 // [HGM] vari: routines for shelving variations
15334
15335 void
15336 PushTail(int firstMove, int lastMove)
15337 {
15338         int i, j, nrMoves = lastMove - firstMove;
15339
15340         if(appData.icsActive) { // only in local mode
15341                 forwardMostMove = currentMove; // mimic old ICS behavior
15342                 return;
15343         }
15344         if(storedGames >= MAX_VARIATIONS-1) return;
15345
15346         // push current tail of game on stack
15347         savedResult[storedGames] = gameInfo.result;
15348         savedDetails[storedGames] = gameInfo.resultDetails;
15349         gameInfo.resultDetails = NULL;
15350         savedFirst[storedGames] = firstMove;
15351         savedLast [storedGames] = lastMove;
15352         savedFramePtr[storedGames] = framePtr;
15353         framePtr -= nrMoves; // reserve space for the boards
15354         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15355             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15356             for(j=0; j<MOVE_LEN; j++)
15357                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15358             for(j=0; j<2*MOVE_LEN; j++)
15359                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15360             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15361             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15362             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15363             pvInfoList[firstMove+i-1].depth = 0;
15364             commentList[framePtr+i] = commentList[firstMove+i];
15365             commentList[firstMove+i] = NULL;
15366         }
15367
15368         storedGames++;
15369         forwardMostMove = firstMove; // truncate game so we can start variation
15370         if(storedGames == 1) GreyRevert(FALSE);
15371 }
15372
15373 Boolean
15374 PopTail(Boolean annotate)
15375 {
15376         int i, j, nrMoves;
15377         char buf[8000], moveBuf[20];
15378
15379         if(appData.icsActive) return FALSE; // only in local mode
15380         if(!storedGames) return FALSE; // sanity
15381         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15382
15383         storedGames--;
15384         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15385         nrMoves = savedLast[storedGames] - currentMove;
15386         if(annotate) {
15387                 int cnt = 10;
15388                 if(!WhiteOnMove(currentMove))
15389                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15390                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15391                 for(i=currentMove; i<forwardMostMove; i++) {
15392                         if(WhiteOnMove(i))
15393                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15394                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15395                         strcat(buf, moveBuf);
15396                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15397                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15398                 }
15399                 strcat(buf, ")");
15400         }
15401         for(i=1; i<=nrMoves; i++) { // copy last variation back
15402             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15403             for(j=0; j<MOVE_LEN; j++)
15404                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15405             for(j=0; j<2*MOVE_LEN; j++)
15406                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15407             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15408             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15409             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15410             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15411             commentList[currentMove+i] = commentList[framePtr+i];
15412             commentList[framePtr+i] = NULL;
15413         }
15414         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15415         framePtr = savedFramePtr[storedGames];
15416         gameInfo.result = savedResult[storedGames];
15417         if(gameInfo.resultDetails != NULL) {
15418             free(gameInfo.resultDetails);
15419       }
15420         gameInfo.resultDetails = savedDetails[storedGames];
15421         forwardMostMove = currentMove + nrMoves;
15422         if(storedGames == 0) GreyRevert(TRUE);
15423         return TRUE;
15424 }
15425
15426 void
15427 CleanupTail()
15428 {       // remove all shelved variations
15429         int i;
15430         for(i=0; i<storedGames; i++) {
15431             if(savedDetails[i])
15432                 free(savedDetails[i]);
15433             savedDetails[i] = NULL;
15434         }
15435         for(i=framePtr; i<MAX_MOVES; i++) {
15436                 if(commentList[i]) free(commentList[i]);
15437                 commentList[i] = NULL;
15438         }
15439         framePtr = MAX_MOVES-1;
15440         storedGames = 0;
15441 }
15442
15443 void
15444 LoadVariation(int index, char *text)
15445 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15446         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15447         int level = 0, move;
15448
15449         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15450         // first find outermost bracketing variation
15451         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15452             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15453                 if(*p == '{') wait = '}'; else
15454                 if(*p == '[') wait = ']'; else
15455                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15456                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15457             }
15458             if(*p == wait) wait = NULLCHAR; // closing ]} found
15459             p++;
15460         }
15461         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15462         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15463         end[1] = NULLCHAR; // clip off comment beyond variation
15464         ToNrEvent(currentMove-1);
15465         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15466         // kludge: use ParsePV() to append variation to game
15467         move = currentMove;
15468         ParsePV(start, TRUE);
15469         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15470         ClearPremoveHighlights();
15471         CommentPopDown();
15472         ToNrEvent(currentMove+1);
15473 }
15474