75fc9a15573b2b823c502b6b20408dbddb590c37
[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 SpartanArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
521         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
522     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
523         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
524 };
525
526 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
530         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
531 };
532
533 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
534     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
535         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
536     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
537         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
538 };
539
540 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
542         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackMan, BlackFerz,
544         BlackKing, BlackMan, BlackKnight, BlackRook }
545 };
546
547
548 #if (BOARD_FILES>=10)
549 ChessSquare ShogiArray[2][BOARD_FILES] = {
550     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
551         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
552     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
553         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
554 };
555
556 ChessSquare XiangqiArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
558         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
560         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
561 };
562
563 ChessSquare CapablancaArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
567         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
568 };
569
570 ChessSquare GreatArray[2][BOARD_FILES] = {
571     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
572         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
573     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
574         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
575 };
576
577 ChessSquare JanusArray[2][BOARD_FILES] = {
578     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
579         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
580     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
581         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
582 };
583
584 #ifdef GOTHIC
585 ChessSquare GothicArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
587         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
589         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
590 };
591 #else // !GOTHIC
592 #define GothicArray CapablancaArray
593 #endif // !GOTHIC
594
595 #ifdef FALCON
596 ChessSquare FalconArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
598         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
600         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
601 };
602 #else // !FALCON
603 #define FalconArray CapablancaArray
604 #endif // !FALCON
605
606 #else // !(BOARD_FILES>=10)
607 #define XiangqiPosition FIDEArray
608 #define CapablancaArray FIDEArray
609 #define GothicArray FIDEArray
610 #define GreatArray FIDEArray
611 #endif // !(BOARD_FILES>=10)
612
613 #if (BOARD_FILES>=12)
614 ChessSquare CourierArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
616         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
618         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
619 };
620 #else // !(BOARD_FILES>=12)
621 #define CourierArray CapablancaArray
622 #endif // !(BOARD_FILES>=12)
623
624
625 Board initialPosition;
626
627
628 /* Convert str to a rating. Checks for special cases of "----",
629
630    "++++", etc. Also strips ()'s */
631 int
632 string_to_rating(str)
633   char *str;
634 {
635   while(*str && !isdigit(*str)) ++str;
636   if (!*str)
637     return 0;   /* One of the special "no rating" cases */
638   else
639     return atoi(str);
640 }
641
642 void
643 ClearProgramStats()
644 {
645     /* Init programStats */
646     programStats.movelist[0] = 0;
647     programStats.depth = 0;
648     programStats.nr_moves = 0;
649     programStats.moves_left = 0;
650     programStats.nodes = 0;
651     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
652     programStats.score = 0;
653     programStats.got_only_move = 0;
654     programStats.got_fail = 0;
655     programStats.line_is_book = 0;
656 }
657
658 void
659 InitBackEnd1()
660 {
661     int matched, min, sec;
662
663     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
664     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
665
666     GetTimeMark(&programStartTime);
667     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
668
669     ClearProgramStats();
670     programStats.ok_to_send = 1;
671     programStats.seen_stat = 0;
672
673     /*
674      * Initialize game list
675      */
676     ListNew(&gameList);
677
678
679     /*
680      * Internet chess server status
681      */
682     if (appData.icsActive) {
683         appData.matchMode = FALSE;
684         appData.matchGames = 0;
685 #if ZIPPY
686         appData.noChessProgram = !appData.zippyPlay;
687 #else
688         appData.zippyPlay = FALSE;
689         appData.zippyTalk = FALSE;
690         appData.noChessProgram = TRUE;
691 #endif
692         if (*appData.icsHelper != NULLCHAR) {
693             appData.useTelnet = TRUE;
694             appData.telnetProgram = appData.icsHelper;
695         }
696     } else {
697         appData.zippyTalk = appData.zippyPlay = FALSE;
698     }
699
700     /* [AS] Initialize pv info list [HGM] and game state */
701     {
702         int i, j;
703
704         for( i=0; i<=framePtr; i++ ) {
705             pvInfoList[i].depth = -1;
706             boards[i][EP_STATUS] = EP_NONE;
707             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
708         }
709     }
710
711     /*
712      * Parse timeControl resource
713      */
714     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
715                           appData.movesPerSession)) {
716         char buf[MSG_SIZ];
717         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
718         DisplayFatalError(buf, 0, 2);
719     }
720
721     /*
722      * Parse searchTime resource
723      */
724     if (*appData.searchTime != NULLCHAR) {
725         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
726         if (matched == 1) {
727             searchTime = min * 60;
728         } else if (matched == 2) {
729             searchTime = min * 60 + sec;
730         } else {
731             char buf[MSG_SIZ];
732             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
733             DisplayFatalError(buf, 0, 2);
734         }
735     }
736
737     /* [AS] Adjudication threshold */
738     adjudicateLossThreshold = appData.adjudicateLossThreshold;
739
740     first.which = _("first");
741     second.which = _("second");
742     first.maybeThinking = second.maybeThinking = FALSE;
743     first.pr = second.pr = NoProc;
744     first.isr = second.isr = NULL;
745     first.sendTime = second.sendTime = 2;
746     first.sendDrawOffers = 1;
747     if (appData.firstPlaysBlack) {
748         first.twoMachinesColor = "black\n";
749         second.twoMachinesColor = "white\n";
750     } else {
751         first.twoMachinesColor = "white\n";
752         second.twoMachinesColor = "black\n";
753     }
754     first.program = appData.firstChessProgram;
755     second.program = appData.secondChessProgram;
756     first.host = appData.firstHost;
757     second.host = appData.secondHost;
758     first.dir = appData.firstDirectory;
759     second.dir = appData.secondDirectory;
760     first.other = &second;
761     second.other = &first;
762     first.initString = appData.initString;
763     second.initString = appData.secondInitString;
764     first.computerString = appData.firstComputerString;
765     second.computerString = appData.secondComputerString;
766     first.useSigint = second.useSigint = TRUE;
767     first.useSigterm = second.useSigterm = TRUE;
768     first.reuse = appData.reuseFirst;
769     second.reuse = appData.reuseSecond;
770     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
771     second.nps = appData.secondNPS;
772     first.useSetboard = second.useSetboard = FALSE;
773     first.useSAN = second.useSAN = FALSE;
774     first.usePing = second.usePing = FALSE;
775     first.lastPing = second.lastPing = 0;
776     first.lastPong = second.lastPong = 0;
777     first.usePlayother = second.usePlayother = FALSE;
778     first.useColors = second.useColors = TRUE;
779     first.useUsermove = second.useUsermove = FALSE;
780     first.sendICS = second.sendICS = FALSE;
781     first.sendName = second.sendName = appData.icsActive;
782     first.sdKludge = second.sdKludge = FALSE;
783     first.stKludge = second.stKludge = FALSE;
784     TidyProgramName(first.program, first.host, first.tidy);
785     TidyProgramName(second.program, second.host, second.tidy);
786     first.matchWins = second.matchWins = 0;
787     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
788     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
789     first.analysisSupport = second.analysisSupport = 2; /* detect */
790     first.analyzing = second.analyzing = FALSE;
791     first.initDone = second.initDone = FALSE;
792
793     /* New features added by Tord: */
794     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
795     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
796     /* End of new features added by Tord. */
797     first.fenOverride  = appData.fenOverride1;
798     second.fenOverride = appData.fenOverride2;
799
800     /* [HGM] time odds: set factor for each machine */
801     first.timeOdds  = appData.firstTimeOdds;
802     second.timeOdds = appData.secondTimeOdds;
803     { float norm = 1;
804         if(appData.timeOddsMode) {
805             norm = first.timeOdds;
806             if(norm > second.timeOdds) norm = second.timeOdds;
807         }
808         first.timeOdds /= norm;
809         second.timeOdds /= norm;
810     }
811
812     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
813     first.accumulateTC = appData.firstAccumulateTC;
814     second.accumulateTC = appData.secondAccumulateTC;
815     first.maxNrOfSessions = second.maxNrOfSessions = 1;
816
817     /* [HGM] debug */
818     first.debug = second.debug = FALSE;
819     first.supportsNPS = second.supportsNPS = UNKNOWN;
820
821     /* [HGM] options */
822     first.optionSettings  = appData.firstOptions;
823     second.optionSettings = appData.secondOptions;
824
825     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
826     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
827     first.isUCI = appData.firstIsUCI; /* [AS] */
828     second.isUCI = appData.secondIsUCI; /* [AS] */
829     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
830     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
831
832     if (appData.firstProtocolVersion > PROTOVER
833         || appData.firstProtocolVersion < 1)
834       {
835         char buf[MSG_SIZ];
836         int len;
837
838         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
839                        appData.firstProtocolVersion);
840         if( (len > MSG_SIZ) && appData.debugMode )
841           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
842
843         DisplayFatalError(buf, 0, 2);
844       }
845     else
846       {
847         first.protocolVersion = appData.firstProtocolVersion;
848       }
849
850     if (appData.secondProtocolVersion > PROTOVER
851         || appData.secondProtocolVersion < 1)
852       {
853         char buf[MSG_SIZ];
854         int len;
855
856         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
857                        appData.secondProtocolVersion);
858         if( (len > MSG_SIZ) && appData.debugMode )
859           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
860
861         DisplayFatalError(buf, 0, 2);
862       }
863     else
864       {
865         second.protocolVersion = appData.secondProtocolVersion;
866       }
867
868     if (appData.icsActive) {
869         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
870 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
871     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
872         appData.clockMode = FALSE;
873         first.sendTime = second.sendTime = 0;
874     }
875
876 #if ZIPPY
877     /* Override some settings from environment variables, for backward
878        compatibility.  Unfortunately it's not feasible to have the env
879        vars just set defaults, at least in xboard.  Ugh.
880     */
881     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
882       ZippyInit();
883     }
884 #endif
885
886     if (appData.noChessProgram) {
887         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
888         sprintf(programVersion, "%s", PACKAGE_STRING);
889     } else {
890       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
891       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
892       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
893     }
894
895     if (!appData.icsActive) {
896       char buf[MSG_SIZ];
897       int len;
898
899       /* Check for variants that are supported only in ICS mode,
900          or not at all.  Some that are accepted here nevertheless
901          have bugs; see comments below.
902       */
903       VariantClass variant = StringToVariant(appData.variant);
904       switch (variant) {
905       case VariantBughouse:     /* need four players and two boards */
906       case VariantKriegspiel:   /* need to hide pieces and move details */
907         /* case VariantFischeRandom: (Fabien: moved below) */
908         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
909         if( (len > MSG_SIZ) && appData.debugMode )
910           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
911
912         DisplayFatalError(buf, 0, 2);
913         return;
914
915       case VariantUnknown:
916       case VariantLoadable:
917       case Variant29:
918       case Variant30:
919       case Variant31:
920       case Variant32:
921       case Variant33:
922       case Variant34:
923       case Variant35:
924       case Variant36:
925       default:
926         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
927         if( (len > MSG_SIZ) && appData.debugMode )
928           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
929
930         DisplayFatalError(buf, 0, 2);
931         return;
932
933       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
934       case VariantFairy:      /* [HGM] TestLegality definitely off! */
935       case VariantGothic:     /* [HGM] should work */
936       case VariantCapablanca: /* [HGM] should work */
937       case VariantCourier:    /* [HGM] initial forced moves not implemented */
938       case VariantShogi:      /* [HGM] could still mate with pawn drop */
939       case VariantKnightmate: /* [HGM] should work */
940       case VariantCylinder:   /* [HGM] untested */
941       case VariantFalcon:     /* [HGM] untested */
942       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
943                                  offboard interposition not understood */
944       case VariantNormal:     /* definitely works! */
945       case VariantWildCastle: /* pieces not automatically shuffled */
946       case VariantNoCastle:   /* pieces not automatically shuffled */
947       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
948       case VariantLosers:     /* should work except for win condition,
949                                  and doesn't know captures are mandatory */
950       case VariantSuicide:    /* should work except for win condition,
951                                  and doesn't know captures are mandatory */
952       case VariantGiveaway:   /* should work except for win condition,
953                                  and doesn't know captures are mandatory */
954       case VariantTwoKings:   /* should work */
955       case VariantAtomic:     /* should work except for win condition */
956       case Variant3Check:     /* should work except for win condition */
957       case VariantShatranj:   /* should work except for all win conditions */
958       case VariantMakruk:     /* should work except for daw countdown */
959       case VariantBerolina:   /* might work if TestLegality is off */
960       case VariantCapaRandom: /* should work */
961       case VariantJanus:      /* should work */
962       case VariantSuper:      /* experimental */
963       case VariantGreat:      /* experimental, requires legality testing to be off */
964       case VariantSChess:     /* S-Chess, should work */
965       case VariantSpartan:    /* should work */
966         break;
967       }
968     }
969
970     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
971     InitEngineUCI( installDir, &second );
972 }
973
974 int NextIntegerFromString( char ** str, long * value )
975 {
976     int result = -1;
977     char * s = *str;
978
979     while( *s == ' ' || *s == '\t' ) {
980         s++;
981     }
982
983     *value = 0;
984
985     if( *s >= '0' && *s <= '9' ) {
986         while( *s >= '0' && *s <= '9' ) {
987             *value = *value * 10 + (*s - '0');
988             s++;
989         }
990
991         result = 0;
992     }
993
994     *str = s;
995
996     return result;
997 }
998
999 int NextTimeControlFromString( char ** str, long * value )
1000 {
1001     long temp;
1002     int result = NextIntegerFromString( str, &temp );
1003
1004     if( result == 0 ) {
1005         *value = temp * 60; /* Minutes */
1006         if( **str == ':' ) {
1007             (*str)++;
1008             result = NextIntegerFromString( str, &temp );
1009             *value += temp; /* Seconds */
1010         }
1011     }
1012
1013     return result;
1014 }
1015
1016 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1017 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1018     int result = -1, type = 0; long temp, temp2;
1019
1020     if(**str != ':') return -1; // old params remain in force!
1021     (*str)++;
1022     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1023     if( NextIntegerFromString( str, &temp ) ) return -1;
1024     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1025
1026     if(**str != '/') {
1027         /* time only: incremental or sudden-death time control */
1028         if(**str == '+') { /* increment follows; read it */
1029             (*str)++;
1030             if(**str == '!') type = *(*str)++; // Bronstein TC
1031             if(result = NextIntegerFromString( str, &temp2)) return -1;
1032             *inc = temp2 * 1000;
1033             if(**str == '.') { // read fraction of increment
1034                 char *start = ++(*str);
1035                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1036                 temp2 *= 1000;
1037                 while(start++ < *str) temp2 /= 10;
1038                 *inc += temp2;
1039             }
1040         } else *inc = 0;
1041         *moves = 0; *tc = temp * 1000; *incType = type;
1042         return 0;
1043     }
1044
1045     (*str)++; /* classical time control */
1046     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1047
1048     if(result == 0) {
1049         *moves = temp;
1050         *tc    = temp2 * 1000;
1051         *inc   = 0;
1052         *incType = type;
1053     }
1054     return result;
1055 }
1056
1057 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1058 {   /* [HGM] get time to add from the multi-session time-control string */
1059     int incType, moves=1; /* kludge to force reading of first session */
1060     long time, increment;
1061     char *s = tcString;
1062
1063     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1064     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1065     do {
1066         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1067         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1068         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1069         if(movenr == -1) return time;    /* last move before new session     */
1070         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1071         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1072         if(!moves) return increment;     /* current session is incremental   */
1073         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1074     } while(movenr >= -1);               /* try again for next session       */
1075
1076     return 0; // no new time quota on this move
1077 }
1078
1079 int
1080 ParseTimeControl(tc, ti, mps)
1081      char *tc;
1082      float ti;
1083      int mps;
1084 {
1085   long tc1;
1086   long tc2;
1087   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1088   int min, sec=0;
1089
1090   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1091   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1092       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1093   if(ti > 0) {
1094
1095     if(mps)
1096       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1097     else 
1098       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1099   } else {
1100     if(mps)
1101       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1102     else 
1103       snprintf(buf, MSG_SIZ, ":%s", mytc);
1104   }
1105   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1106   
1107   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1108     return FALSE;
1109   }
1110
1111   if( *tc == '/' ) {
1112     /* Parse second time control */
1113     tc++;
1114
1115     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1116       return FALSE;
1117     }
1118
1119     if( tc2 == 0 ) {
1120       return FALSE;
1121     }
1122
1123     timeControl_2 = tc2 * 1000;
1124   }
1125   else {
1126     timeControl_2 = 0;
1127   }
1128
1129   if( tc1 == 0 ) {
1130     return FALSE;
1131   }
1132
1133   timeControl = tc1 * 1000;
1134
1135   if (ti >= 0) {
1136     timeIncrement = ti * 1000;  /* convert to ms */
1137     movesPerSession = 0;
1138   } else {
1139     timeIncrement = 0;
1140     movesPerSession = mps;
1141   }
1142   return TRUE;
1143 }
1144
1145 void
1146 InitBackEnd2()
1147 {
1148     if (appData.debugMode) {
1149         fprintf(debugFP, "%s\n", programVersion);
1150     }
1151
1152     set_cont_sequence(appData.wrapContSeq);
1153     if (appData.matchGames > 0) {
1154         appData.matchMode = TRUE;
1155     } else if (appData.matchMode) {
1156         appData.matchGames = 1;
1157     }
1158     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1159         appData.matchGames = appData.sameColorGames;
1160     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1161         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1162         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1163     }
1164     Reset(TRUE, FALSE);
1165     if (appData.noChessProgram || first.protocolVersion == 1) {
1166       InitBackEnd3();
1167     } else {
1168       /* kludge: allow timeout for initial "feature" commands */
1169       FreezeUI();
1170       DisplayMessage("", _("Starting chess program"));
1171       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1172     }
1173 }
1174
1175 void
1176 InitBackEnd3 P((void))
1177 {
1178     GameMode initialMode;
1179     char buf[MSG_SIZ];
1180     int err, len;
1181
1182     InitChessProgram(&first, startedFromSetupPosition);
1183
1184     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1185         free(programVersion);
1186         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1187         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1188     }
1189
1190     if (appData.icsActive) {
1191 #ifdef WIN32
1192         /* [DM] Make a console window if needed [HGM] merged ifs */
1193         ConsoleCreate();
1194 #endif
1195         err = establish();
1196         if (err != 0)
1197           {
1198             if (*appData.icsCommPort != NULLCHAR)
1199               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1200                              appData.icsCommPort);
1201             else
1202               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1203                         appData.icsHost, appData.icsPort);
1204
1205             if( (len > MSG_SIZ) && appData.debugMode )
1206               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1207
1208             DisplayFatalError(buf, err, 1);
1209             return;
1210         }
1211         SetICSMode();
1212         telnetISR =
1213           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1214         fromUserISR =
1215           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1216         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1217             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1218     } else if (appData.noChessProgram) {
1219         SetNCPMode();
1220     } else {
1221         SetGNUMode();
1222     }
1223
1224     if (*appData.cmailGameName != NULLCHAR) {
1225         SetCmailMode();
1226         OpenLoopback(&cmailPR);
1227         cmailISR =
1228           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1229     }
1230
1231     ThawUI();
1232     DisplayMessage("", "");
1233     if (StrCaseCmp(appData.initialMode, "") == 0) {
1234       initialMode = BeginningOfGame;
1235     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1236       initialMode = TwoMachinesPlay;
1237     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1238       initialMode = AnalyzeFile;
1239     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1240       initialMode = AnalyzeMode;
1241     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1242       initialMode = MachinePlaysWhite;
1243     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1244       initialMode = MachinePlaysBlack;
1245     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1246       initialMode = EditGame;
1247     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1248       initialMode = EditPosition;
1249     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1250       initialMode = Training;
1251     } else {
1252       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1253       if( (len > MSG_SIZ) && appData.debugMode )
1254         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1255
1256       DisplayFatalError(buf, 0, 2);
1257       return;
1258     }
1259
1260     if (appData.matchMode) {
1261         /* Set up machine vs. machine match */
1262         if (appData.noChessProgram) {
1263             DisplayFatalError(_("Can't have a match with no chess programs"),
1264                               0, 2);
1265             return;
1266         }
1267         matchMode = TRUE;
1268         matchGame = 1;
1269         if (*appData.loadGameFile != NULLCHAR) {
1270             int index = appData.loadGameIndex; // [HGM] autoinc
1271             if(index<0) lastIndex = index = 1;
1272             if (!LoadGameFromFile(appData.loadGameFile,
1273                                   index,
1274                                   appData.loadGameFile, FALSE)) {
1275                 DisplayFatalError(_("Bad game file"), 0, 1);
1276                 return;
1277             }
1278         } else if (*appData.loadPositionFile != NULLCHAR) {
1279             int index = appData.loadPositionIndex; // [HGM] autoinc
1280             if(index<0) lastIndex = index = 1;
1281             if (!LoadPositionFromFile(appData.loadPositionFile,
1282                                       index,
1283                                       appData.loadPositionFile)) {
1284                 DisplayFatalError(_("Bad position file"), 0, 1);
1285                 return;
1286             }
1287         }
1288         TwoMachinesEvent();
1289     } else if (*appData.cmailGameName != NULLCHAR) {
1290         /* Set up cmail mode */
1291         ReloadCmailMsgEvent(TRUE);
1292     } else {
1293         /* Set up other modes */
1294         if (initialMode == AnalyzeFile) {
1295           if (*appData.loadGameFile == NULLCHAR) {
1296             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1297             return;
1298           }
1299         }
1300         if (*appData.loadGameFile != NULLCHAR) {
1301             (void) LoadGameFromFile(appData.loadGameFile,
1302                                     appData.loadGameIndex,
1303                                     appData.loadGameFile, TRUE);
1304         } else if (*appData.loadPositionFile != NULLCHAR) {
1305             (void) LoadPositionFromFile(appData.loadPositionFile,
1306                                         appData.loadPositionIndex,
1307                                         appData.loadPositionFile);
1308             /* [HGM] try to make self-starting even after FEN load */
1309             /* to allow automatic setup of fairy variants with wtm */
1310             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1311                 gameMode = BeginningOfGame;
1312                 setboardSpoiledMachineBlack = 1;
1313             }
1314             /* [HGM] loadPos: make that every new game uses the setup */
1315             /* from file as long as we do not switch variant          */
1316             if(!blackPlaysFirst) {
1317                 startedFromPositionFile = TRUE;
1318                 CopyBoard(filePosition, boards[0]);
1319             }
1320         }
1321         if (initialMode == AnalyzeMode) {
1322           if (appData.noChessProgram) {
1323             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1324             return;
1325           }
1326           if (appData.icsActive) {
1327             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1328             return;
1329           }
1330           AnalyzeModeEvent();
1331         } else if (initialMode == AnalyzeFile) {
1332           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1333           ShowThinkingEvent();
1334           AnalyzeFileEvent();
1335           AnalysisPeriodicEvent(1);
1336         } else if (initialMode == MachinePlaysWhite) {
1337           if (appData.noChessProgram) {
1338             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1339                               0, 2);
1340             return;
1341           }
1342           if (appData.icsActive) {
1343             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1344                               0, 2);
1345             return;
1346           }
1347           MachineWhiteEvent();
1348         } else if (initialMode == MachinePlaysBlack) {
1349           if (appData.noChessProgram) {
1350             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1351                               0, 2);
1352             return;
1353           }
1354           if (appData.icsActive) {
1355             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1356                               0, 2);
1357             return;
1358           }
1359           MachineBlackEvent();
1360         } else if (initialMode == TwoMachinesPlay) {
1361           if (appData.noChessProgram) {
1362             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1363                               0, 2);
1364             return;
1365           }
1366           if (appData.icsActive) {
1367             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1368                               0, 2);
1369             return;
1370           }
1371           TwoMachinesEvent();
1372         } else if (initialMode == EditGame) {
1373           EditGameEvent();
1374         } else if (initialMode == EditPosition) {
1375           EditPositionEvent();
1376         } else if (initialMode == Training) {
1377           if (*appData.loadGameFile == NULLCHAR) {
1378             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1379             return;
1380           }
1381           TrainingEvent();
1382         }
1383     }
1384 }
1385
1386 /*
1387  * Establish will establish a contact to a remote host.port.
1388  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1389  *  used to talk to the host.
1390  * Returns 0 if okay, error code if not.
1391  */
1392 int
1393 establish()
1394 {
1395     char buf[MSG_SIZ];
1396
1397     if (*appData.icsCommPort != NULLCHAR) {
1398         /* Talk to the host through a serial comm port */
1399         return OpenCommPort(appData.icsCommPort, &icsPR);
1400
1401     } else if (*appData.gateway != NULLCHAR) {
1402         if (*appData.remoteShell == NULLCHAR) {
1403             /* Use the rcmd protocol to run telnet program on a gateway host */
1404             snprintf(buf, sizeof(buf), "%s %s %s",
1405                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1406             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1407
1408         } else {
1409             /* Use the rsh program to run telnet program on a gateway host */
1410             if (*appData.remoteUser == NULLCHAR) {
1411                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1412                         appData.gateway, appData.telnetProgram,
1413                         appData.icsHost, appData.icsPort);
1414             } else {
1415                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1416                         appData.remoteShell, appData.gateway,
1417                         appData.remoteUser, appData.telnetProgram,
1418                         appData.icsHost, appData.icsPort);
1419             }
1420             return StartChildProcess(buf, "", &icsPR);
1421
1422         }
1423     } else if (appData.useTelnet) {
1424         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1425
1426     } else {
1427         /* TCP socket interface differs somewhat between
1428            Unix and NT; handle details in the front end.
1429            */
1430         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1431     }
1432 }
1433
1434 void EscapeExpand(char *p, char *q)
1435 {       // [HGM] initstring: routine to shape up string arguments
1436         while(*p++ = *q++) if(p[-1] == '\\')
1437             switch(*q++) {
1438                 case 'n': p[-1] = '\n'; break;
1439                 case 'r': p[-1] = '\r'; break;
1440                 case 't': p[-1] = '\t'; break;
1441                 case '\\': p[-1] = '\\'; break;
1442                 case 0: *p = 0; return;
1443                 default: p[-1] = q[-1]; break;
1444             }
1445 }
1446
1447 void
1448 show_bytes(fp, buf, count)
1449      FILE *fp;
1450      char *buf;
1451      int count;
1452 {
1453     while (count--) {
1454         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1455             fprintf(fp, "\\%03o", *buf & 0xff);
1456         } else {
1457             putc(*buf, fp);
1458         }
1459         buf++;
1460     }
1461     fflush(fp);
1462 }
1463
1464 /* Returns an errno value */
1465 int
1466 OutputMaybeTelnet(pr, message, count, outError)
1467      ProcRef pr;
1468      char *message;
1469      int count;
1470      int *outError;
1471 {
1472     char buf[8192], *p, *q, *buflim;
1473     int left, newcount, outcount;
1474
1475     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1476         *appData.gateway != NULLCHAR) {
1477         if (appData.debugMode) {
1478             fprintf(debugFP, ">ICS: ");
1479             show_bytes(debugFP, message, count);
1480             fprintf(debugFP, "\n");
1481         }
1482         return OutputToProcess(pr, message, count, outError);
1483     }
1484
1485     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1486     p = message;
1487     q = buf;
1488     left = count;
1489     newcount = 0;
1490     while (left) {
1491         if (q >= buflim) {
1492             if (appData.debugMode) {
1493                 fprintf(debugFP, ">ICS: ");
1494                 show_bytes(debugFP, buf, newcount);
1495                 fprintf(debugFP, "\n");
1496             }
1497             outcount = OutputToProcess(pr, buf, newcount, outError);
1498             if (outcount < newcount) return -1; /* to be sure */
1499             q = buf;
1500             newcount = 0;
1501         }
1502         if (*p == '\n') {
1503             *q++ = '\r';
1504             newcount++;
1505         } else if (((unsigned char) *p) == TN_IAC) {
1506             *q++ = (char) TN_IAC;
1507             newcount ++;
1508         }
1509         *q++ = *p++;
1510         newcount++;
1511         left--;
1512     }
1513     if (appData.debugMode) {
1514         fprintf(debugFP, ">ICS: ");
1515         show_bytes(debugFP, buf, newcount);
1516         fprintf(debugFP, "\n");
1517     }
1518     outcount = OutputToProcess(pr, buf, newcount, outError);
1519     if (outcount < newcount) return -1; /* to be sure */
1520     return count;
1521 }
1522
1523 void
1524 read_from_player(isr, closure, message, count, error)
1525      InputSourceRef isr;
1526      VOIDSTAR closure;
1527      char *message;
1528      int count;
1529      int error;
1530 {
1531     int outError, outCount;
1532     static int gotEof = 0;
1533
1534     /* Pass data read from player on to ICS */
1535     if (count > 0) {
1536         gotEof = 0;
1537         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1538         if (outCount < count) {
1539             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1540         }
1541     } else if (count < 0) {
1542         RemoveInputSource(isr);
1543         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1544     } else if (gotEof++ > 0) {
1545         RemoveInputSource(isr);
1546         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1547     }
1548 }
1549
1550 void
1551 KeepAlive()
1552 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1553     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1554     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1555     SendToICS("date\n");
1556     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1557 }
1558
1559 /* added routine for printf style output to ics */
1560 void ics_printf(char *format, ...)
1561 {
1562     char buffer[MSG_SIZ];
1563     va_list args;
1564
1565     va_start(args, format);
1566     vsnprintf(buffer, sizeof(buffer), format, args);
1567     buffer[sizeof(buffer)-1] = '\0';
1568     SendToICS(buffer);
1569     va_end(args);
1570 }
1571
1572 void
1573 SendToICS(s)
1574      char *s;
1575 {
1576     int count, outCount, outError;
1577
1578     if (icsPR == NULL) return;
1579
1580     count = strlen(s);
1581     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1582     if (outCount < count) {
1583         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1584     }
1585 }
1586
1587 /* This is used for sending logon scripts to the ICS. Sending
1588    without a delay causes problems when using timestamp on ICC
1589    (at least on my machine). */
1590 void
1591 SendToICSDelayed(s,msdelay)
1592      char *s;
1593      long msdelay;
1594 {
1595     int count, outCount, outError;
1596
1597     if (icsPR == NULL) return;
1598
1599     count = strlen(s);
1600     if (appData.debugMode) {
1601         fprintf(debugFP, ">ICS: ");
1602         show_bytes(debugFP, s, count);
1603         fprintf(debugFP, "\n");
1604     }
1605     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1606                                       msdelay);
1607     if (outCount < count) {
1608         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1609     }
1610 }
1611
1612
1613 /* Remove all highlighting escape sequences in s
1614    Also deletes any suffix starting with '('
1615    */
1616 char *
1617 StripHighlightAndTitle(s)
1618      char *s;
1619 {
1620     static char retbuf[MSG_SIZ];
1621     char *p = retbuf;
1622
1623     while (*s != NULLCHAR) {
1624         while (*s == '\033') {
1625             while (*s != NULLCHAR && !isalpha(*s)) s++;
1626             if (*s != NULLCHAR) s++;
1627         }
1628         while (*s != NULLCHAR && *s != '\033') {
1629             if (*s == '(' || *s == '[') {
1630                 *p = NULLCHAR;
1631                 return retbuf;
1632             }
1633             *p++ = *s++;
1634         }
1635     }
1636     *p = NULLCHAR;
1637     return retbuf;
1638 }
1639
1640 /* Remove all highlighting escape sequences in s */
1641 char *
1642 StripHighlight(s)
1643      char *s;
1644 {
1645     static char retbuf[MSG_SIZ];
1646     char *p = retbuf;
1647
1648     while (*s != NULLCHAR) {
1649         while (*s == '\033') {
1650             while (*s != NULLCHAR && !isalpha(*s)) s++;
1651             if (*s != NULLCHAR) s++;
1652         }
1653         while (*s != NULLCHAR && *s != '\033') {
1654             *p++ = *s++;
1655         }
1656     }
1657     *p = NULLCHAR;
1658     return retbuf;
1659 }
1660
1661 char *variantNames[] = VARIANT_NAMES;
1662 char *
1663 VariantName(v)
1664      VariantClass v;
1665 {
1666     return variantNames[v];
1667 }
1668
1669
1670 /* Identify a variant from the strings the chess servers use or the
1671    PGN Variant tag names we use. */
1672 VariantClass
1673 StringToVariant(e)
1674      char *e;
1675 {
1676     char *p;
1677     int wnum = -1;
1678     VariantClass v = VariantNormal;
1679     int i, found = FALSE;
1680     char buf[MSG_SIZ];
1681     int len;
1682
1683     if (!e) return v;
1684
1685     /* [HGM] skip over optional board-size prefixes */
1686     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1687         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1688         while( *e++ != '_');
1689     }
1690
1691     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1692         v = VariantNormal;
1693         found = TRUE;
1694     } else
1695     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1696       if (StrCaseStr(e, variantNames[i])) {
1697         v = (VariantClass) i;
1698         found = TRUE;
1699         break;
1700       }
1701     }
1702
1703     if (!found) {
1704       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1705           || StrCaseStr(e, "wild/fr")
1706           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1707         v = VariantFischeRandom;
1708       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1709                  (i = 1, p = StrCaseStr(e, "w"))) {
1710         p += i;
1711         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1712         if (isdigit(*p)) {
1713           wnum = atoi(p);
1714         } else {
1715           wnum = -1;
1716         }
1717         switch (wnum) {
1718         case 0: /* FICS only, actually */
1719         case 1:
1720           /* Castling legal even if K starts on d-file */
1721           v = VariantWildCastle;
1722           break;
1723         case 2:
1724         case 3:
1725         case 4:
1726           /* Castling illegal even if K & R happen to start in
1727              normal positions. */
1728           v = VariantNoCastle;
1729           break;
1730         case 5:
1731         case 7:
1732         case 8:
1733         case 10:
1734         case 11:
1735         case 12:
1736         case 13:
1737         case 14:
1738         case 15:
1739         case 18:
1740         case 19:
1741           /* Castling legal iff K & R start in normal positions */
1742           v = VariantNormal;
1743           break;
1744         case 6:
1745         case 20:
1746         case 21:
1747           /* Special wilds for position setup; unclear what to do here */
1748           v = VariantLoadable;
1749           break;
1750         case 9:
1751           /* Bizarre ICC game */
1752           v = VariantTwoKings;
1753           break;
1754         case 16:
1755           v = VariantKriegspiel;
1756           break;
1757         case 17:
1758           v = VariantLosers;
1759           break;
1760         case 22:
1761           v = VariantFischeRandom;
1762           break;
1763         case 23:
1764           v = VariantCrazyhouse;
1765           break;
1766         case 24:
1767           v = VariantBughouse;
1768           break;
1769         case 25:
1770           v = Variant3Check;
1771           break;
1772         case 26:
1773           /* Not quite the same as FICS suicide! */
1774           v = VariantGiveaway;
1775           break;
1776         case 27:
1777           v = VariantAtomic;
1778           break;
1779         case 28:
1780           v = VariantShatranj;
1781           break;
1782
1783         /* Temporary names for future ICC types.  The name *will* change in
1784            the next xboard/WinBoard release after ICC defines it. */
1785         case 29:
1786           v = Variant29;
1787           break;
1788         case 30:
1789           v = Variant30;
1790           break;
1791         case 31:
1792           v = Variant31;
1793           break;
1794         case 32:
1795           v = Variant32;
1796           break;
1797         case 33:
1798           v = Variant33;
1799           break;
1800         case 34:
1801           v = Variant34;
1802           break;
1803         case 35:
1804           v = Variant35;
1805           break;
1806         case 36:
1807           v = Variant36;
1808           break;
1809         case 37:
1810           v = VariantShogi;
1811           break;
1812         case 38:
1813           v = VariantXiangqi;
1814           break;
1815         case 39:
1816           v = VariantCourier;
1817           break;
1818         case 40:
1819           v = VariantGothic;
1820           break;
1821         case 41:
1822           v = VariantCapablanca;
1823           break;
1824         case 42:
1825           v = VariantKnightmate;
1826           break;
1827         case 43:
1828           v = VariantFairy;
1829           break;
1830         case 44:
1831           v = VariantCylinder;
1832           break;
1833         case 45:
1834           v = VariantFalcon;
1835           break;
1836         case 46:
1837           v = VariantCapaRandom;
1838           break;
1839         case 47:
1840           v = VariantBerolina;
1841           break;
1842         case 48:
1843           v = VariantJanus;
1844           break;
1845         case 49:
1846           v = VariantSuper;
1847           break;
1848         case 50:
1849           v = VariantGreat;
1850           break;
1851         case -1:
1852           /* Found "wild" or "w" in the string but no number;
1853              must assume it's normal chess. */
1854           v = VariantNormal;
1855           break;
1856         default:
1857           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1858           if( (len > MSG_SIZ) && appData.debugMode )
1859             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1860
1861           DisplayError(buf, 0);
1862           v = VariantUnknown;
1863           break;
1864         }
1865       }
1866     }
1867     if (appData.debugMode) {
1868       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1869               e, wnum, VariantName(v));
1870     }
1871     return v;
1872 }
1873
1874 static int leftover_start = 0, leftover_len = 0;
1875 char star_match[STAR_MATCH_N][MSG_SIZ];
1876
1877 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1878    advance *index beyond it, and set leftover_start to the new value of
1879    *index; else return FALSE.  If pattern contains the character '*', it
1880    matches any sequence of characters not containing '\r', '\n', or the
1881    character following the '*' (if any), and the matched sequence(s) are
1882    copied into star_match.
1883    */
1884 int
1885 looking_at(buf, index, pattern)
1886      char *buf;
1887      int *index;
1888      char *pattern;
1889 {
1890     char *bufp = &buf[*index], *patternp = pattern;
1891     int star_count = 0;
1892     char *matchp = star_match[0];
1893
1894     for (;;) {
1895         if (*patternp == NULLCHAR) {
1896             *index = leftover_start = bufp - buf;
1897             *matchp = NULLCHAR;
1898             return TRUE;
1899         }
1900         if (*bufp == NULLCHAR) return FALSE;
1901         if (*patternp == '*') {
1902             if (*bufp == *(patternp + 1)) {
1903                 *matchp = NULLCHAR;
1904                 matchp = star_match[++star_count];
1905                 patternp += 2;
1906                 bufp++;
1907                 continue;
1908             } else if (*bufp == '\n' || *bufp == '\r') {
1909                 patternp++;
1910                 if (*patternp == NULLCHAR)
1911                   continue;
1912                 else
1913                   return FALSE;
1914             } else {
1915                 *matchp++ = *bufp++;
1916                 continue;
1917             }
1918         }
1919         if (*patternp != *bufp) return FALSE;
1920         patternp++;
1921         bufp++;
1922     }
1923 }
1924
1925 void
1926 SendToPlayer(data, length)
1927      char *data;
1928      int length;
1929 {
1930     int error, outCount;
1931     outCount = OutputToProcess(NoProc, data, length, &error);
1932     if (outCount < length) {
1933         DisplayFatalError(_("Error writing to display"), error, 1);
1934     }
1935 }
1936
1937 void
1938 PackHolding(packed, holding)
1939      char packed[];
1940      char *holding;
1941 {
1942     char *p = holding;
1943     char *q = packed;
1944     int runlength = 0;
1945     int curr = 9999;
1946     do {
1947         if (*p == curr) {
1948             runlength++;
1949         } else {
1950             switch (runlength) {
1951               case 0:
1952                 break;
1953               case 1:
1954                 *q++ = curr;
1955                 break;
1956               case 2:
1957                 *q++ = curr;
1958                 *q++ = curr;
1959                 break;
1960               default:
1961                 sprintf(q, "%d", runlength);
1962                 while (*q) q++;
1963                 *q++ = curr;
1964                 break;
1965             }
1966             runlength = 1;
1967             curr = *p;
1968         }
1969     } while (*p++);
1970     *q = NULLCHAR;
1971 }
1972
1973 /* Telnet protocol requests from the front end */
1974 void
1975 TelnetRequest(ddww, option)
1976      unsigned char ddww, option;
1977 {
1978     unsigned char msg[3];
1979     int outCount, outError;
1980
1981     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1982
1983     if (appData.debugMode) {
1984         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1985         switch (ddww) {
1986           case TN_DO:
1987             ddwwStr = "DO";
1988             break;
1989           case TN_DONT:
1990             ddwwStr = "DONT";
1991             break;
1992           case TN_WILL:
1993             ddwwStr = "WILL";
1994             break;
1995           case TN_WONT:
1996             ddwwStr = "WONT";
1997             break;
1998           default:
1999             ddwwStr = buf1;
2000             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2001             break;
2002         }
2003         switch (option) {
2004           case TN_ECHO:
2005             optionStr = "ECHO";
2006             break;
2007           default:
2008             optionStr = buf2;
2009             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2010             break;
2011         }
2012         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2013     }
2014     msg[0] = TN_IAC;
2015     msg[1] = ddww;
2016     msg[2] = option;
2017     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2018     if (outCount < 3) {
2019         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2020     }
2021 }
2022
2023 void
2024 DoEcho()
2025 {
2026     if (!appData.icsActive) return;
2027     TelnetRequest(TN_DO, TN_ECHO);
2028 }
2029
2030 void
2031 DontEcho()
2032 {
2033     if (!appData.icsActive) return;
2034     TelnetRequest(TN_DONT, TN_ECHO);
2035 }
2036
2037 void
2038 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2039 {
2040     /* put the holdings sent to us by the server on the board holdings area */
2041     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2042     char p;
2043     ChessSquare piece;
2044
2045     if(gameInfo.holdingsWidth < 2)  return;
2046     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2047         return; // prevent overwriting by pre-board holdings
2048
2049     if( (int)lowestPiece >= BlackPawn ) {
2050         holdingsColumn = 0;
2051         countsColumn = 1;
2052         holdingsStartRow = BOARD_HEIGHT-1;
2053         direction = -1;
2054     } else {
2055         holdingsColumn = BOARD_WIDTH-1;
2056         countsColumn = BOARD_WIDTH-2;
2057         holdingsStartRow = 0;
2058         direction = 1;
2059     }
2060
2061     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2062         board[i][holdingsColumn] = EmptySquare;
2063         board[i][countsColumn]   = (ChessSquare) 0;
2064     }
2065     while( (p=*holdings++) != NULLCHAR ) {
2066         piece = CharToPiece( ToUpper(p) );
2067         if(piece == EmptySquare) continue;
2068         /*j = (int) piece - (int) WhitePawn;*/
2069         j = PieceToNumber(piece);
2070         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2071         if(j < 0) continue;               /* should not happen */
2072         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2073         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2074         board[holdingsStartRow+j*direction][countsColumn]++;
2075     }
2076 }
2077
2078
2079 void
2080 VariantSwitch(Board board, VariantClass newVariant)
2081 {
2082    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2083    static Board oldBoard;
2084
2085    startedFromPositionFile = FALSE;
2086    if(gameInfo.variant == newVariant) return;
2087
2088    /* [HGM] This routine is called each time an assignment is made to
2089     * gameInfo.variant during a game, to make sure the board sizes
2090     * are set to match the new variant. If that means adding or deleting
2091     * holdings, we shift the playing board accordingly
2092     * This kludge is needed because in ICS observe mode, we get boards
2093     * of an ongoing game without knowing the variant, and learn about the
2094     * latter only later. This can be because of the move list we requested,
2095     * in which case the game history is refilled from the beginning anyway,
2096     * but also when receiving holdings of a crazyhouse game. In the latter
2097     * case we want to add those holdings to the already received position.
2098     */
2099
2100
2101    if (appData.debugMode) {
2102      fprintf(debugFP, "Switch board from %s to %s\n",
2103              VariantName(gameInfo.variant), VariantName(newVariant));
2104      setbuf(debugFP, NULL);
2105    }
2106    shuffleOpenings = 0;       /* [HGM] shuffle */
2107    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2108    switch(newVariant)
2109      {
2110      case VariantShogi:
2111        newWidth = 9;  newHeight = 9;
2112        gameInfo.holdingsSize = 7;
2113      case VariantBughouse:
2114      case VariantCrazyhouse:
2115        newHoldingsWidth = 2; break;
2116      case VariantGreat:
2117        newWidth = 10;
2118      case VariantSuper:
2119        newHoldingsWidth = 2;
2120        gameInfo.holdingsSize = 8;
2121        break;
2122      case VariantGothic:
2123      case VariantCapablanca:
2124      case VariantCapaRandom:
2125        newWidth = 10;
2126      default:
2127        newHoldingsWidth = gameInfo.holdingsSize = 0;
2128      };
2129
2130    if(newWidth  != gameInfo.boardWidth  ||
2131       newHeight != gameInfo.boardHeight ||
2132       newHoldingsWidth != gameInfo.holdingsWidth ) {
2133
2134      /* shift position to new playing area, if needed */
2135      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2136        for(i=0; i<BOARD_HEIGHT; i++)
2137          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2138            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2139              board[i][j];
2140        for(i=0; i<newHeight; i++) {
2141          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2142          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2143        }
2144      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2145        for(i=0; i<BOARD_HEIGHT; i++)
2146          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2147            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2148              board[i][j];
2149      }
2150      gameInfo.boardWidth  = newWidth;
2151      gameInfo.boardHeight = newHeight;
2152      gameInfo.holdingsWidth = newHoldingsWidth;
2153      gameInfo.variant = newVariant;
2154      InitDrawingSizes(-2, 0);
2155    } else gameInfo.variant = newVariant;
2156    CopyBoard(oldBoard, board);   // remember correctly formatted board
2157      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2158    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2159 }
2160
2161 static int loggedOn = FALSE;
2162
2163 /*-- Game start info cache: --*/
2164 int gs_gamenum;
2165 char gs_kind[MSG_SIZ];
2166 static char player1Name[128] = "";
2167 static char player2Name[128] = "";
2168 static char cont_seq[] = "\n\\   ";
2169 static int player1Rating = -1;
2170 static int player2Rating = -1;
2171 /*----------------------------*/
2172
2173 ColorClass curColor = ColorNormal;
2174 int suppressKibitz = 0;
2175
2176 // [HGM] seekgraph
2177 Boolean soughtPending = FALSE;
2178 Boolean seekGraphUp;
2179 #define MAX_SEEK_ADS 200
2180 #define SQUARE 0x80
2181 char *seekAdList[MAX_SEEK_ADS];
2182 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2183 float tcList[MAX_SEEK_ADS];
2184 char colorList[MAX_SEEK_ADS];
2185 int nrOfSeekAds = 0;
2186 int minRating = 1010, maxRating = 2800;
2187 int hMargin = 10, vMargin = 20, h, w;
2188 extern int squareSize, lineGap;
2189
2190 void
2191 PlotSeekAd(int i)
2192 {
2193         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2194         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2195         if(r < minRating+100 && r >=0 ) r = minRating+100;
2196         if(r > maxRating) r = maxRating;
2197         if(tc < 1.) tc = 1.;
2198         if(tc > 95.) tc = 95.;
2199         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2200         y = ((double)r - minRating)/(maxRating - minRating)
2201             * (h-vMargin-squareSize/8-1) + vMargin;
2202         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2203         if(strstr(seekAdList[i], " u ")) color = 1;
2204         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2205            !strstr(seekAdList[i], "bullet") &&
2206            !strstr(seekAdList[i], "blitz") &&
2207            !strstr(seekAdList[i], "standard") ) color = 2;
2208         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2209         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2210 }
2211
2212 void
2213 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2214 {
2215         char buf[MSG_SIZ], *ext = "";
2216         VariantClass v = StringToVariant(type);
2217         if(strstr(type, "wild")) {
2218             ext = type + 4; // append wild number
2219             if(v == VariantFischeRandom) type = "chess960"; else
2220             if(v == VariantLoadable) type = "setup"; else
2221             type = VariantName(v);
2222         }
2223         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2224         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2225             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2226             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2227             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2228             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2229             seekNrList[nrOfSeekAds] = nr;
2230             zList[nrOfSeekAds] = 0;
2231             seekAdList[nrOfSeekAds++] = StrSave(buf);
2232             if(plot) PlotSeekAd(nrOfSeekAds-1);
2233         }
2234 }
2235
2236 void
2237 EraseSeekDot(int i)
2238 {
2239     int x = xList[i], y = yList[i], d=squareSize/4, k;
2240     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2241     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2242     // now replot every dot that overlapped
2243     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2244         int xx = xList[k], yy = yList[k];
2245         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2246             DrawSeekDot(xx, yy, colorList[k]);
2247     }
2248 }
2249
2250 void
2251 RemoveSeekAd(int nr)
2252 {
2253         int i;
2254         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2255             EraseSeekDot(i);
2256             if(seekAdList[i]) free(seekAdList[i]);
2257             seekAdList[i] = seekAdList[--nrOfSeekAds];
2258             seekNrList[i] = seekNrList[nrOfSeekAds];
2259             ratingList[i] = ratingList[nrOfSeekAds];
2260             colorList[i]  = colorList[nrOfSeekAds];
2261             tcList[i] = tcList[nrOfSeekAds];
2262             xList[i]  = xList[nrOfSeekAds];
2263             yList[i]  = yList[nrOfSeekAds];
2264             zList[i]  = zList[nrOfSeekAds];
2265             seekAdList[nrOfSeekAds] = NULL;
2266             break;
2267         }
2268 }
2269
2270 Boolean
2271 MatchSoughtLine(char *line)
2272 {
2273     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2274     int nr, base, inc, u=0; char dummy;
2275
2276     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2277        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2278        (u=1) &&
2279        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2280         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2281         // match: compact and save the line
2282         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2283         return TRUE;
2284     }
2285     return FALSE;
2286 }
2287
2288 int
2289 DrawSeekGraph()
2290 {
2291     int i;
2292     if(!seekGraphUp) return FALSE;
2293     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2294     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2295
2296     DrawSeekBackground(0, 0, w, h);
2297     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2298     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2299     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2300         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2301         yy = h-1-yy;
2302         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2303         if(i%500 == 0) {
2304             char buf[MSG_SIZ];
2305             snprintf(buf, MSG_SIZ, "%d", i);
2306             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2307         }
2308     }
2309     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2310     for(i=1; i<100; i+=(i<10?1:5)) {
2311         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2312         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2313         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2314             char buf[MSG_SIZ];
2315             snprintf(buf, MSG_SIZ, "%d", i);
2316             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2317         }
2318     }
2319     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2320     return TRUE;
2321 }
2322
2323 int SeekGraphClick(ClickType click, int x, int y, int moving)
2324 {
2325     static int lastDown = 0, displayed = 0, lastSecond;
2326     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2327         if(click == Release || moving) return FALSE;
2328         nrOfSeekAds = 0;
2329         soughtPending = TRUE;
2330         SendToICS(ics_prefix);
2331         SendToICS("sought\n"); // should this be "sought all"?
2332     } else { // issue challenge based on clicked ad
2333         int dist = 10000; int i, closest = 0, second = 0;
2334         for(i=0; i<nrOfSeekAds; i++) {
2335             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2336             if(d < dist) { dist = d; closest = i; }
2337             second += (d - zList[i] < 120); // count in-range ads
2338             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2339         }
2340         if(dist < 120) {
2341             char buf[MSG_SIZ];
2342             second = (second > 1);
2343             if(displayed != closest || second != lastSecond) {
2344                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2345                 lastSecond = second; displayed = closest;
2346             }
2347             if(click == Press) {
2348                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2349                 lastDown = closest;
2350                 return TRUE;
2351             } // on press 'hit', only show info
2352             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2353             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2354             SendToICS(ics_prefix);
2355             SendToICS(buf);
2356             return TRUE; // let incoming board of started game pop down the graph
2357         } else if(click == Release) { // release 'miss' is ignored
2358             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2359             if(moving == 2) { // right up-click
2360                 nrOfSeekAds = 0; // refresh graph
2361                 soughtPending = TRUE;
2362                 SendToICS(ics_prefix);
2363                 SendToICS("sought\n"); // should this be "sought all"?
2364             }
2365             return TRUE;
2366         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2367         // press miss or release hit 'pop down' seek graph
2368         seekGraphUp = FALSE;
2369         DrawPosition(TRUE, NULL);
2370     }
2371     return TRUE;
2372 }
2373
2374 void
2375 read_from_ics(isr, closure, data, count, error)
2376      InputSourceRef isr;
2377      VOIDSTAR closure;
2378      char *data;
2379      int count;
2380      int error;
2381 {
2382 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2383 #define STARTED_NONE 0
2384 #define STARTED_MOVES 1
2385 #define STARTED_BOARD 2
2386 #define STARTED_OBSERVE 3
2387 #define STARTED_HOLDINGS 4
2388 #define STARTED_CHATTER 5
2389 #define STARTED_COMMENT 6
2390 #define STARTED_MOVES_NOHIDE 7
2391
2392     static int started = STARTED_NONE;
2393     static char parse[20000];
2394     static int parse_pos = 0;
2395     static char buf[BUF_SIZE + 1];
2396     static int firstTime = TRUE, intfSet = FALSE;
2397     static ColorClass prevColor = ColorNormal;
2398     static int savingComment = FALSE;
2399     static int cmatch = 0; // continuation sequence match
2400     char *bp;
2401     char str[MSG_SIZ];
2402     int i, oldi;
2403     int buf_len;
2404     int next_out;
2405     int tkind;
2406     int backup;    /* [DM] For zippy color lines */
2407     char *p;
2408     char talker[MSG_SIZ]; // [HGM] chat
2409     int channel;
2410
2411     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2412
2413     if (appData.debugMode) {
2414       if (!error) {
2415         fprintf(debugFP, "<ICS: ");
2416         show_bytes(debugFP, data, count);
2417         fprintf(debugFP, "\n");
2418       }
2419     }
2420
2421     if (appData.debugMode) { int f = forwardMostMove;
2422         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2423                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2424                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2425     }
2426     if (count > 0) {
2427         /* If last read ended with a partial line that we couldn't parse,
2428            prepend it to the new read and try again. */
2429         if (leftover_len > 0) {
2430             for (i=0; i<leftover_len; i++)
2431               buf[i] = buf[leftover_start + i];
2432         }
2433
2434     /* copy new characters into the buffer */
2435     bp = buf + leftover_len;
2436     buf_len=leftover_len;
2437     for (i=0; i<count; i++)
2438     {
2439         // ignore these
2440         if (data[i] == '\r')
2441             continue;
2442
2443         // join lines split by ICS?
2444         if (!appData.noJoin)
2445         {
2446             /*
2447                 Joining just consists of finding matches against the
2448                 continuation sequence, and discarding that sequence
2449                 if found instead of copying it.  So, until a match
2450                 fails, there's nothing to do since it might be the
2451                 complete sequence, and thus, something we don't want
2452                 copied.
2453             */
2454             if (data[i] == cont_seq[cmatch])
2455             {
2456                 cmatch++;
2457                 if (cmatch == strlen(cont_seq))
2458                 {
2459                     cmatch = 0; // complete match.  just reset the counter
2460
2461                     /*
2462                         it's possible for the ICS to not include the space
2463                         at the end of the last word, making our [correct]
2464                         join operation fuse two separate words.  the server
2465                         does this when the space occurs at the width setting.
2466                     */
2467                     if (!buf_len || buf[buf_len-1] != ' ')
2468                     {
2469                         *bp++ = ' ';
2470                         buf_len++;
2471                     }
2472                 }
2473                 continue;
2474             }
2475             else if (cmatch)
2476             {
2477                 /*
2478                     match failed, so we have to copy what matched before
2479                     falling through and copying this character.  In reality,
2480                     this will only ever be just the newline character, but
2481                     it doesn't hurt to be precise.
2482                 */
2483                 strncpy(bp, cont_seq, cmatch);
2484                 bp += cmatch;
2485                 buf_len += cmatch;
2486                 cmatch = 0;
2487             }
2488         }
2489
2490         // copy this char
2491         *bp++ = data[i];
2492         buf_len++;
2493     }
2494
2495         buf[buf_len] = NULLCHAR;
2496 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2497         next_out = 0;
2498         leftover_start = 0;
2499
2500         i = 0;
2501         while (i < buf_len) {
2502             /* Deal with part of the TELNET option negotiation
2503                protocol.  We refuse to do anything beyond the
2504                defaults, except that we allow the WILL ECHO option,
2505                which ICS uses to turn off password echoing when we are
2506                directly connected to it.  We reject this option
2507                if localLineEditing mode is on (always on in xboard)
2508                and we are talking to port 23, which might be a real
2509                telnet server that will try to keep WILL ECHO on permanently.
2510              */
2511             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2512                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2513                 unsigned char option;
2514                 oldi = i;
2515                 switch ((unsigned char) buf[++i]) {
2516                   case TN_WILL:
2517                     if (appData.debugMode)
2518                       fprintf(debugFP, "\n<WILL ");
2519                     switch (option = (unsigned char) buf[++i]) {
2520                       case TN_ECHO:
2521                         if (appData.debugMode)
2522                           fprintf(debugFP, "ECHO ");
2523                         /* Reply only if this is a change, according
2524                            to the protocol rules. */
2525                         if (remoteEchoOption) break;
2526                         if (appData.localLineEditing &&
2527                             atoi(appData.icsPort) == TN_PORT) {
2528                             TelnetRequest(TN_DONT, TN_ECHO);
2529                         } else {
2530                             EchoOff();
2531                             TelnetRequest(TN_DO, TN_ECHO);
2532                             remoteEchoOption = TRUE;
2533                         }
2534                         break;
2535                       default:
2536                         if (appData.debugMode)
2537                           fprintf(debugFP, "%d ", option);
2538                         /* Whatever this is, we don't want it. */
2539                         TelnetRequest(TN_DONT, option);
2540                         break;
2541                     }
2542                     break;
2543                   case TN_WONT:
2544                     if (appData.debugMode)
2545                       fprintf(debugFP, "\n<WONT ");
2546                     switch (option = (unsigned char) buf[++i]) {
2547                       case TN_ECHO:
2548                         if (appData.debugMode)
2549                           fprintf(debugFP, "ECHO ");
2550                         /* Reply only if this is a change, according
2551                            to the protocol rules. */
2552                         if (!remoteEchoOption) break;
2553                         EchoOn();
2554                         TelnetRequest(TN_DONT, TN_ECHO);
2555                         remoteEchoOption = FALSE;
2556                         break;
2557                       default:
2558                         if (appData.debugMode)
2559                           fprintf(debugFP, "%d ", (unsigned char) option);
2560                         /* Whatever this is, it must already be turned
2561                            off, because we never agree to turn on
2562                            anything non-default, so according to the
2563                            protocol rules, we don't reply. */
2564                         break;
2565                     }
2566                     break;
2567                   case TN_DO:
2568                     if (appData.debugMode)
2569                       fprintf(debugFP, "\n<DO ");
2570                     switch (option = (unsigned char) buf[++i]) {
2571                       default:
2572                         /* Whatever this is, we refuse to do it. */
2573                         if (appData.debugMode)
2574                           fprintf(debugFP, "%d ", option);
2575                         TelnetRequest(TN_WONT, option);
2576                         break;
2577                     }
2578                     break;
2579                   case TN_DONT:
2580                     if (appData.debugMode)
2581                       fprintf(debugFP, "\n<DONT ");
2582                     switch (option = (unsigned char) buf[++i]) {
2583                       default:
2584                         if (appData.debugMode)
2585                           fprintf(debugFP, "%d ", option);
2586                         /* Whatever this is, we are already not doing
2587                            it, because we never agree to do anything
2588                            non-default, so according to the protocol
2589                            rules, we don't reply. */
2590                         break;
2591                     }
2592                     break;
2593                   case TN_IAC:
2594                     if (appData.debugMode)
2595                       fprintf(debugFP, "\n<IAC ");
2596                     /* Doubled IAC; pass it through */
2597                     i--;
2598                     break;
2599                   default:
2600                     if (appData.debugMode)
2601                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2602                     /* Drop all other telnet commands on the floor */
2603                     break;
2604                 }
2605                 if (oldi > next_out)
2606                   SendToPlayer(&buf[next_out], oldi - next_out);
2607                 if (++i > next_out)
2608                   next_out = i;
2609                 continue;
2610             }
2611
2612             /* OK, this at least will *usually* work */
2613             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2614                 loggedOn = TRUE;
2615             }
2616
2617             if (loggedOn && !intfSet) {
2618                 if (ics_type == ICS_ICC) {
2619                   snprintf(str, MSG_SIZ,
2620                           "/set-quietly interface %s\n/set-quietly style 12\n",
2621                           programVersion);
2622                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2623                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2624                 } else if (ics_type == ICS_CHESSNET) {
2625                   snprintf(str, MSG_SIZ, "/style 12\n");
2626                 } else {
2627                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2628                   strcat(str, programVersion);
2629                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2630                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2631                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2632 #ifdef WIN32
2633                   strcat(str, "$iset nohighlight 1\n");
2634 #endif
2635                   strcat(str, "$iset lock 1\n$style 12\n");
2636                 }
2637                 SendToICS(str);
2638                 NotifyFrontendLogin();
2639                 intfSet = TRUE;
2640             }
2641
2642             if (started == STARTED_COMMENT) {
2643                 /* Accumulate characters in comment */
2644                 parse[parse_pos++] = buf[i];
2645                 if (buf[i] == '\n') {
2646                     parse[parse_pos] = NULLCHAR;
2647                     if(chattingPartner>=0) {
2648                         char mess[MSG_SIZ];
2649                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2650                         OutputChatMessage(chattingPartner, mess);
2651                         chattingPartner = -1;
2652                         next_out = i+1; // [HGM] suppress printing in ICS window
2653                     } else
2654                     if(!suppressKibitz) // [HGM] kibitz
2655                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2656                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2657                         int nrDigit = 0, nrAlph = 0, j;
2658                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2659                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2660                         parse[parse_pos] = NULLCHAR;
2661                         // try to be smart: if it does not look like search info, it should go to
2662                         // ICS interaction window after all, not to engine-output window.
2663                         for(j=0; j<parse_pos; j++) { // count letters and digits
2664                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2665                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2666                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2667                         }
2668                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2669                             int depth=0; float score;
2670                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2671                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2672                                 pvInfoList[forwardMostMove-1].depth = depth;
2673                                 pvInfoList[forwardMostMove-1].score = 100*score;
2674                             }
2675                             OutputKibitz(suppressKibitz, parse);
2676                         } else {
2677                             char tmp[MSG_SIZ];
2678                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2679                             SendToPlayer(tmp, strlen(tmp));
2680                         }
2681                         next_out = i+1; // [HGM] suppress printing in ICS window
2682                     }
2683                     started = STARTED_NONE;
2684                 } else {
2685                     /* Don't match patterns against characters in comment */
2686                     i++;
2687                     continue;
2688                 }
2689             }
2690             if (started == STARTED_CHATTER) {
2691                 if (buf[i] != '\n') {
2692                     /* Don't match patterns against characters in chatter */
2693                     i++;
2694                     continue;
2695                 }
2696                 started = STARTED_NONE;
2697                 if(suppressKibitz) next_out = i+1;
2698             }
2699
2700             /* Kludge to deal with rcmd protocol */
2701             if (firstTime && looking_at(buf, &i, "\001*")) {
2702                 DisplayFatalError(&buf[1], 0, 1);
2703                 continue;
2704             } else {
2705                 firstTime = FALSE;
2706             }
2707
2708             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2709                 ics_type = ICS_ICC;
2710                 ics_prefix = "/";
2711                 if (appData.debugMode)
2712                   fprintf(debugFP, "ics_type %d\n", ics_type);
2713                 continue;
2714             }
2715             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2716                 ics_type = ICS_FICS;
2717                 ics_prefix = "$";
2718                 if (appData.debugMode)
2719                   fprintf(debugFP, "ics_type %d\n", ics_type);
2720                 continue;
2721             }
2722             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2723                 ics_type = ICS_CHESSNET;
2724                 ics_prefix = "/";
2725                 if (appData.debugMode)
2726                   fprintf(debugFP, "ics_type %d\n", ics_type);
2727                 continue;
2728             }
2729
2730             if (!loggedOn &&
2731                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2732                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2733                  looking_at(buf, &i, "will be \"*\""))) {
2734               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2735               continue;
2736             }
2737
2738             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2739               char buf[MSG_SIZ];
2740               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2741               DisplayIcsInteractionTitle(buf);
2742               have_set_title = TRUE;
2743             }
2744
2745             /* skip finger notes */
2746             if (started == STARTED_NONE &&
2747                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2748                  (buf[i] == '1' && buf[i+1] == '0')) &&
2749                 buf[i+2] == ':' && buf[i+3] == ' ') {
2750               started = STARTED_CHATTER;
2751               i += 3;
2752               continue;
2753             }
2754
2755             oldi = i;
2756             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2757             if(appData.seekGraph) {
2758                 if(soughtPending && MatchSoughtLine(buf+i)) {
2759                     i = strstr(buf+i, "rated") - buf;
2760                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2761                     next_out = leftover_start = i;
2762                     started = STARTED_CHATTER;
2763                     suppressKibitz = TRUE;
2764                     continue;
2765                 }
2766                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2767                         && looking_at(buf, &i, "* ads displayed")) {
2768                     soughtPending = FALSE;
2769                     seekGraphUp = TRUE;
2770                     DrawSeekGraph();
2771                     continue;
2772                 }
2773                 if(appData.autoRefresh) {
2774                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2775                         int s = (ics_type == ICS_ICC); // ICC format differs
2776                         if(seekGraphUp)
2777                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2778                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2779                         looking_at(buf, &i, "*% "); // eat prompt
2780                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2781                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2782                         next_out = i; // suppress
2783                         continue;
2784                     }
2785                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2786                         char *p = star_match[0];
2787                         while(*p) {
2788                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2789                             while(*p && *p++ != ' '); // next
2790                         }
2791                         looking_at(buf, &i, "*% "); // eat prompt
2792                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2793                         next_out = i;
2794                         continue;
2795                     }
2796                 }
2797             }
2798
2799             /* skip formula vars */
2800             if (started == STARTED_NONE &&
2801                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2802               started = STARTED_CHATTER;
2803               i += 3;
2804               continue;
2805             }
2806
2807             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2808             if (appData.autoKibitz && started == STARTED_NONE &&
2809                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2810                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2811                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2812                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2813                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2814                         suppressKibitz = TRUE;
2815                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2816                         next_out = i;
2817                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2818                                 && (gameMode == IcsPlayingWhite)) ||
2819                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2820                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2821                             started = STARTED_CHATTER; // own kibitz we simply discard
2822                         else {
2823                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2824                             parse_pos = 0; parse[0] = NULLCHAR;
2825                             savingComment = TRUE;
2826                             suppressKibitz = gameMode != IcsObserving ? 2 :
2827                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2828                         }
2829                         continue;
2830                 } else
2831                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2832                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2833                          && atoi(star_match[0])) {
2834                     // suppress the acknowledgements of our own autoKibitz
2835                     char *p;
2836                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2837                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2838                     SendToPlayer(star_match[0], strlen(star_match[0]));
2839                     if(looking_at(buf, &i, "*% ")) // eat prompt
2840                         suppressKibitz = FALSE;
2841                     next_out = i;
2842                     continue;
2843                 }
2844             } // [HGM] kibitz: end of patch
2845
2846             // [HGM] chat: intercept tells by users for which we have an open chat window
2847             channel = -1;
2848             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2849                                            looking_at(buf, &i, "* whispers:") ||
2850                                            looking_at(buf, &i, "* kibitzes:") ||
2851                                            looking_at(buf, &i, "* shouts:") ||
2852                                            looking_at(buf, &i, "* c-shouts:") ||
2853                                            looking_at(buf, &i, "--> * ") ||
2854                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2855                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2856                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2857                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2858                 int p;
2859                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2860                 chattingPartner = -1;
2861
2862                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2863                 for(p=0; p<MAX_CHAT; p++) {
2864                     if(channel == atoi(chatPartner[p])) {
2865                     talker[0] = '['; strcat(talker, "] ");
2866                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2867                     chattingPartner = p; break;
2868                     }
2869                 } else
2870                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2871                 for(p=0; p<MAX_CHAT; p++) {
2872                     if(!strcmp("kibitzes", chatPartner[p])) {
2873                         talker[0] = '['; strcat(talker, "] ");
2874                         chattingPartner = p; break;
2875                     }
2876                 } else
2877                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2878                 for(p=0; p<MAX_CHAT; p++) {
2879                     if(!strcmp("whispers", chatPartner[p])) {
2880                         talker[0] = '['; strcat(talker, "] ");
2881                         chattingPartner = p; break;
2882                     }
2883                 } else
2884                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2885                   if(buf[i-8] == '-' && buf[i-3] == 't')
2886                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2887                     if(!strcmp("c-shouts", chatPartner[p])) {
2888                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2889                         chattingPartner = p; break;
2890                     }
2891                   }
2892                   if(chattingPartner < 0)
2893                   for(p=0; p<MAX_CHAT; p++) {
2894                     if(!strcmp("shouts", chatPartner[p])) {
2895                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2896                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2897                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2898                         chattingPartner = p; break;
2899                     }
2900                   }
2901                 }
2902                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2903                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2904                     talker[0] = 0; Colorize(ColorTell, FALSE);
2905                     chattingPartner = p; break;
2906                 }
2907                 if(chattingPartner<0) i = oldi; else {
2908                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2909                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2910                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2911                     started = STARTED_COMMENT;
2912                     parse_pos = 0; parse[0] = NULLCHAR;
2913                     savingComment = 3 + chattingPartner; // counts as TRUE
2914                     suppressKibitz = TRUE;
2915                     continue;
2916                 }
2917             } // [HGM] chat: end of patch
2918
2919             if (appData.zippyTalk || appData.zippyPlay) {
2920                 /* [DM] Backup address for color zippy lines */
2921                 backup = i;
2922 #if ZIPPY
2923                if (loggedOn == TRUE)
2924                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2925                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2926 #endif
2927             } // [DM] 'else { ' deleted
2928                 if (
2929                     /* Regular tells and says */
2930                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2931                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2932                     looking_at(buf, &i, "* says: ") ||
2933                     /* Don't color "message" or "messages" output */
2934                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2935                     looking_at(buf, &i, "*. * at *:*: ") ||
2936                     looking_at(buf, &i, "--* (*:*): ") ||
2937                     /* Message notifications (same color as tells) */
2938                     looking_at(buf, &i, "* has left a message ") ||
2939                     looking_at(buf, &i, "* just sent you a message:\n") ||
2940                     /* Whispers and kibitzes */
2941                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2942                     looking_at(buf, &i, "* kibitzes: ") ||
2943                     /* Channel tells */
2944                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2945
2946                   if (tkind == 1 && strchr(star_match[0], ':')) {
2947                       /* Avoid "tells you:" spoofs in channels */
2948                      tkind = 3;
2949                   }
2950                   if (star_match[0][0] == NULLCHAR ||
2951                       strchr(star_match[0], ' ') ||
2952                       (tkind == 3 && strchr(star_match[1], ' '))) {
2953                     /* Reject bogus matches */
2954                     i = oldi;
2955                   } else {
2956                     if (appData.colorize) {
2957                       if (oldi > next_out) {
2958                         SendToPlayer(&buf[next_out], oldi - next_out);
2959                         next_out = oldi;
2960                       }
2961                       switch (tkind) {
2962                       case 1:
2963                         Colorize(ColorTell, FALSE);
2964                         curColor = ColorTell;
2965                         break;
2966                       case 2:
2967                         Colorize(ColorKibitz, FALSE);
2968                         curColor = ColorKibitz;
2969                         break;
2970                       case 3:
2971                         p = strrchr(star_match[1], '(');
2972                         if (p == NULL) {
2973                           p = star_match[1];
2974                         } else {
2975                           p++;
2976                         }
2977                         if (atoi(p) == 1) {
2978                           Colorize(ColorChannel1, FALSE);
2979                           curColor = ColorChannel1;
2980                         } else {
2981                           Colorize(ColorChannel, FALSE);
2982                           curColor = ColorChannel;
2983                         }
2984                         break;
2985                       case 5:
2986                         curColor = ColorNormal;
2987                         break;
2988                       }
2989                     }
2990                     if (started == STARTED_NONE && appData.autoComment &&
2991                         (gameMode == IcsObserving ||
2992                          gameMode == IcsPlayingWhite ||
2993                          gameMode == IcsPlayingBlack)) {
2994                       parse_pos = i - oldi;
2995                       memcpy(parse, &buf[oldi], parse_pos);
2996                       parse[parse_pos] = NULLCHAR;
2997                       started = STARTED_COMMENT;
2998                       savingComment = TRUE;
2999                     } else {
3000                       started = STARTED_CHATTER;
3001                       savingComment = FALSE;
3002                     }
3003                     loggedOn = TRUE;
3004                     continue;
3005                   }
3006                 }
3007
3008                 if (looking_at(buf, &i, "* s-shouts: ") ||
3009                     looking_at(buf, &i, "* c-shouts: ")) {
3010                     if (appData.colorize) {
3011                         if (oldi > next_out) {
3012                             SendToPlayer(&buf[next_out], oldi - next_out);
3013                             next_out = oldi;
3014                         }
3015                         Colorize(ColorSShout, FALSE);
3016                         curColor = ColorSShout;
3017                     }
3018                     loggedOn = TRUE;
3019                     started = STARTED_CHATTER;
3020                     continue;
3021                 }
3022
3023                 if (looking_at(buf, &i, "--->")) {
3024                     loggedOn = TRUE;
3025                     continue;
3026                 }
3027
3028                 if (looking_at(buf, &i, "* shouts: ") ||
3029                     looking_at(buf, &i, "--> ")) {
3030                     if (appData.colorize) {
3031                         if (oldi > next_out) {
3032                             SendToPlayer(&buf[next_out], oldi - next_out);
3033                             next_out = oldi;
3034                         }
3035                         Colorize(ColorShout, FALSE);
3036                         curColor = ColorShout;
3037                     }
3038                     loggedOn = TRUE;
3039                     started = STARTED_CHATTER;
3040                     continue;
3041                 }
3042
3043                 if (looking_at( buf, &i, "Challenge:")) {
3044                     if (appData.colorize) {
3045                         if (oldi > next_out) {
3046                             SendToPlayer(&buf[next_out], oldi - next_out);
3047                             next_out = oldi;
3048                         }
3049                         Colorize(ColorChallenge, FALSE);
3050                         curColor = ColorChallenge;
3051                     }
3052                     loggedOn = TRUE;
3053                     continue;
3054                 }
3055
3056                 if (looking_at(buf, &i, "* offers you") ||
3057                     looking_at(buf, &i, "* offers to be") ||
3058                     looking_at(buf, &i, "* would like to") ||
3059                     looking_at(buf, &i, "* requests to") ||
3060                     looking_at(buf, &i, "Your opponent offers") ||
3061                     looking_at(buf, &i, "Your opponent requests")) {
3062
3063                     if (appData.colorize) {
3064                         if (oldi > next_out) {
3065                             SendToPlayer(&buf[next_out], oldi - next_out);
3066                             next_out = oldi;
3067                         }
3068                         Colorize(ColorRequest, FALSE);
3069                         curColor = ColorRequest;
3070                     }
3071                     continue;
3072                 }
3073
3074                 if (looking_at(buf, &i, "* (*) seeking")) {
3075                     if (appData.colorize) {
3076                         if (oldi > next_out) {
3077                             SendToPlayer(&buf[next_out], oldi - next_out);
3078                             next_out = oldi;
3079                         }
3080                         Colorize(ColorSeek, FALSE);
3081                         curColor = ColorSeek;
3082                     }
3083                     continue;
3084             }
3085
3086             if (looking_at(buf, &i, "\\   ")) {
3087                 if (prevColor != ColorNormal) {
3088                     if (oldi > next_out) {
3089                         SendToPlayer(&buf[next_out], oldi - next_out);
3090                         next_out = oldi;
3091                     }
3092                     Colorize(prevColor, TRUE);
3093                     curColor = prevColor;
3094                 }
3095                 if (savingComment) {
3096                     parse_pos = i - oldi;
3097                     memcpy(parse, &buf[oldi], parse_pos);
3098                     parse[parse_pos] = NULLCHAR;
3099                     started = STARTED_COMMENT;
3100                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3101                         chattingPartner = savingComment - 3; // kludge to remember the box
3102                 } else {
3103                     started = STARTED_CHATTER;
3104                 }
3105                 continue;
3106             }
3107
3108             if (looking_at(buf, &i, "Black Strength :") ||
3109                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3110                 looking_at(buf, &i, "<10>") ||
3111                 looking_at(buf, &i, "#@#")) {
3112                 /* Wrong board style */
3113                 loggedOn = TRUE;
3114                 SendToICS(ics_prefix);
3115                 SendToICS("set style 12\n");
3116                 SendToICS(ics_prefix);
3117                 SendToICS("refresh\n");
3118                 continue;
3119             }
3120
3121             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3122                 ICSInitScript();
3123                 have_sent_ICS_logon = 1;
3124                 continue;
3125             }
3126
3127             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3128                 (looking_at(buf, &i, "\n<12> ") ||
3129                  looking_at(buf, &i, "<12> "))) {
3130                 loggedOn = TRUE;
3131                 if (oldi > next_out) {
3132                     SendToPlayer(&buf[next_out], oldi - next_out);
3133                 }
3134                 next_out = i;
3135                 started = STARTED_BOARD;
3136                 parse_pos = 0;
3137                 continue;
3138             }
3139
3140             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3141                 looking_at(buf, &i, "<b1> ")) {
3142                 if (oldi > next_out) {
3143                     SendToPlayer(&buf[next_out], oldi - next_out);
3144                 }
3145                 next_out = i;
3146                 started = STARTED_HOLDINGS;
3147                 parse_pos = 0;
3148                 continue;
3149             }
3150
3151             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3152                 loggedOn = TRUE;
3153                 /* Header for a move list -- first line */
3154
3155                 switch (ics_getting_history) {
3156                   case H_FALSE:
3157                     switch (gameMode) {
3158                       case IcsIdle:
3159                       case BeginningOfGame:
3160                         /* User typed "moves" or "oldmoves" while we
3161                            were idle.  Pretend we asked for these
3162                            moves and soak them up so user can step
3163                            through them and/or save them.
3164                            */
3165                         Reset(FALSE, TRUE);
3166                         gameMode = IcsObserving;
3167                         ModeHighlight();
3168                         ics_gamenum = -1;
3169                         ics_getting_history = H_GOT_UNREQ_HEADER;
3170                         break;
3171                       case EditGame: /*?*/
3172                       case EditPosition: /*?*/
3173                         /* Should above feature work in these modes too? */
3174                         /* For now it doesn't */
3175                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3176                         break;
3177                       default:
3178                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3179                         break;
3180                     }
3181                     break;
3182                   case H_REQUESTED:
3183                     /* Is this the right one? */
3184                     if (gameInfo.white && gameInfo.black &&
3185                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3186                         strcmp(gameInfo.black, star_match[2]) == 0) {
3187                         /* All is well */
3188                         ics_getting_history = H_GOT_REQ_HEADER;
3189                     }
3190                     break;
3191                   case H_GOT_REQ_HEADER:
3192                   case H_GOT_UNREQ_HEADER:
3193                   case H_GOT_UNWANTED_HEADER:
3194                   case H_GETTING_MOVES:
3195                     /* Should not happen */
3196                     DisplayError(_("Error gathering move list: two headers"), 0);
3197                     ics_getting_history = H_FALSE;
3198                     break;
3199                 }
3200
3201                 /* Save player ratings into gameInfo if needed */
3202                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3203                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3204                     (gameInfo.whiteRating == -1 ||
3205                      gameInfo.blackRating == -1)) {
3206
3207                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3208                     gameInfo.blackRating = string_to_rating(star_match[3]);
3209                     if (appData.debugMode)
3210                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3211                               gameInfo.whiteRating, gameInfo.blackRating);
3212                 }
3213                 continue;
3214             }
3215
3216             if (looking_at(buf, &i,
3217               "* * match, initial time: * minute*, increment: * second")) {
3218                 /* Header for a move list -- second line */
3219                 /* Initial board will follow if this is a wild game */
3220                 if (gameInfo.event != NULL) free(gameInfo.event);
3221                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3222                 gameInfo.event = StrSave(str);
3223                 /* [HGM] we switched variant. Translate boards if needed. */
3224                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3225                 continue;
3226             }
3227
3228             if (looking_at(buf, &i, "Move  ")) {
3229                 /* Beginning of a move list */
3230                 switch (ics_getting_history) {
3231                   case H_FALSE:
3232                     /* Normally should not happen */
3233                     /* Maybe user hit reset while we were parsing */
3234                     break;
3235                   case H_REQUESTED:
3236                     /* Happens if we are ignoring a move list that is not
3237                      * the one we just requested.  Common if the user
3238                      * tries to observe two games without turning off
3239                      * getMoveList */
3240                     break;
3241                   case H_GETTING_MOVES:
3242                     /* Should not happen */
3243                     DisplayError(_("Error gathering move list: nested"), 0);
3244                     ics_getting_history = H_FALSE;
3245                     break;
3246                   case H_GOT_REQ_HEADER:
3247                     ics_getting_history = H_GETTING_MOVES;
3248                     started = STARTED_MOVES;
3249                     parse_pos = 0;
3250                     if (oldi > next_out) {
3251                         SendToPlayer(&buf[next_out], oldi - next_out);
3252                     }
3253                     break;
3254                   case H_GOT_UNREQ_HEADER:
3255                     ics_getting_history = H_GETTING_MOVES;
3256                     started = STARTED_MOVES_NOHIDE;
3257                     parse_pos = 0;
3258                     break;
3259                   case H_GOT_UNWANTED_HEADER:
3260                     ics_getting_history = H_FALSE;
3261                     break;
3262                 }
3263                 continue;
3264             }
3265
3266             if (looking_at(buf, &i, "% ") ||
3267                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3268                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3269                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3270                     soughtPending = FALSE;
3271                     seekGraphUp = TRUE;
3272                     DrawSeekGraph();
3273                 }
3274                 if(suppressKibitz) next_out = i;
3275                 savingComment = FALSE;
3276                 suppressKibitz = 0;
3277                 switch (started) {
3278                   case STARTED_MOVES:
3279                   case STARTED_MOVES_NOHIDE:
3280                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3281                     parse[parse_pos + i - oldi] = NULLCHAR;
3282                     ParseGameHistory(parse);
3283 #if ZIPPY
3284                     if (appData.zippyPlay && first.initDone) {
3285                         FeedMovesToProgram(&first, forwardMostMove);
3286                         if (gameMode == IcsPlayingWhite) {
3287                             if (WhiteOnMove(forwardMostMove)) {
3288                                 if (first.sendTime) {
3289                                   if (first.useColors) {
3290                                     SendToProgram("black\n", &first);
3291                                   }
3292                                   SendTimeRemaining(&first, TRUE);
3293                                 }
3294                                 if (first.useColors) {
3295                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3296                                 }
3297                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3298                                 first.maybeThinking = TRUE;
3299                             } else {
3300                                 if (first.usePlayother) {
3301                                   if (first.sendTime) {
3302                                     SendTimeRemaining(&first, TRUE);
3303                                   }
3304                                   SendToProgram("playother\n", &first);
3305                                   firstMove = FALSE;
3306                                 } else {
3307                                   firstMove = TRUE;
3308                                 }
3309                             }
3310                         } else if (gameMode == IcsPlayingBlack) {
3311                             if (!WhiteOnMove(forwardMostMove)) {
3312                                 if (first.sendTime) {
3313                                   if (first.useColors) {
3314                                     SendToProgram("white\n", &first);
3315                                   }
3316                                   SendTimeRemaining(&first, FALSE);
3317                                 }
3318                                 if (first.useColors) {
3319                                   SendToProgram("black\n", &first);
3320                                 }
3321                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3322                                 first.maybeThinking = TRUE;
3323                             } else {
3324                                 if (first.usePlayother) {
3325                                   if (first.sendTime) {
3326                                     SendTimeRemaining(&first, FALSE);
3327                                   }
3328                                   SendToProgram("playother\n", &first);
3329                                   firstMove = FALSE;
3330                                 } else {
3331                                   firstMove = TRUE;
3332                                 }
3333                             }
3334                         }
3335                     }
3336 #endif
3337                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3338                         /* Moves came from oldmoves or moves command
3339                            while we weren't doing anything else.
3340                            */
3341                         currentMove = forwardMostMove;
3342                         ClearHighlights();/*!!could figure this out*/
3343                         flipView = appData.flipView;
3344                         DrawPosition(TRUE, boards[currentMove]);
3345                         DisplayBothClocks();
3346                         snprintf(str, MSG_SIZ, "%s vs. %s",
3347                                 gameInfo.white, gameInfo.black);
3348                         DisplayTitle(str);
3349                         gameMode = IcsIdle;
3350                     } else {
3351                         /* Moves were history of an active game */
3352                         if (gameInfo.resultDetails != NULL) {
3353                             free(gameInfo.resultDetails);
3354                             gameInfo.resultDetails = NULL;
3355                         }
3356                     }
3357                     HistorySet(parseList, backwardMostMove,
3358                                forwardMostMove, currentMove-1);
3359                     DisplayMove(currentMove - 1);
3360                     if (started == STARTED_MOVES) next_out = i;
3361                     started = STARTED_NONE;
3362                     ics_getting_history = H_FALSE;
3363                     break;
3364
3365                   case STARTED_OBSERVE:
3366                     started = STARTED_NONE;
3367                     SendToICS(ics_prefix);
3368                     SendToICS("refresh\n");
3369                     break;
3370
3371                   default:
3372                     break;
3373                 }
3374                 if(bookHit) { // [HGM] book: simulate book reply
3375                     static char bookMove[MSG_SIZ]; // a bit generous?
3376
3377                     programStats.nodes = programStats.depth = programStats.time =
3378                     programStats.score = programStats.got_only_move = 0;
3379                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3380
3381                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3382                     strcat(bookMove, bookHit);
3383                     HandleMachineMove(bookMove, &first);
3384                 }
3385                 continue;
3386             }
3387
3388             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3389                  started == STARTED_HOLDINGS ||
3390                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3391                 /* Accumulate characters in move list or board */
3392                 parse[parse_pos++] = buf[i];
3393             }
3394
3395             /* Start of game messages.  Mostly we detect start of game
3396                when the first board image arrives.  On some versions
3397                of the ICS, though, we need to do a "refresh" after starting
3398                to observe in order to get the current board right away. */
3399             if (looking_at(buf, &i, "Adding game * to observation list")) {
3400                 started = STARTED_OBSERVE;
3401                 continue;
3402             }
3403
3404             /* Handle auto-observe */
3405             if (appData.autoObserve &&
3406                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3407                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3408                 char *player;
3409                 /* Choose the player that was highlighted, if any. */
3410                 if (star_match[0][0] == '\033' ||
3411                     star_match[1][0] != '\033') {
3412                     player = star_match[0];
3413                 } else {
3414                     player = star_match[2];
3415                 }
3416                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3417                         ics_prefix, StripHighlightAndTitle(player));
3418                 SendToICS(str);
3419
3420                 /* Save ratings from notify string */
3421                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3422                 player1Rating = string_to_rating(star_match[1]);
3423                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3424                 player2Rating = string_to_rating(star_match[3]);
3425
3426                 if (appData.debugMode)
3427                   fprintf(debugFP,
3428                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3429                           player1Name, player1Rating,
3430                           player2Name, player2Rating);
3431
3432                 continue;
3433             }
3434
3435             /* Deal with automatic examine mode after a game,
3436                and with IcsObserving -> IcsExamining transition */
3437             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3438                 looking_at(buf, &i, "has made you an examiner of game *")) {
3439
3440                 int gamenum = atoi(star_match[0]);
3441                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3442                     gamenum == ics_gamenum) {
3443                     /* We were already playing or observing this game;
3444                        no need to refetch history */
3445                     gameMode = IcsExamining;
3446                     if (pausing) {
3447                         pauseExamForwardMostMove = forwardMostMove;
3448                     } else if (currentMove < forwardMostMove) {
3449                         ForwardInner(forwardMostMove);
3450                     }
3451                 } else {
3452                     /* I don't think this case really can happen */
3453                     SendToICS(ics_prefix);
3454                     SendToICS("refresh\n");
3455                 }
3456                 continue;
3457             }
3458
3459             /* Error messages */
3460 //          if (ics_user_moved) {
3461             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3462                 if (looking_at(buf, &i, "Illegal move") ||
3463                     looking_at(buf, &i, "Not a legal move") ||
3464                     looking_at(buf, &i, "Your king is in check") ||
3465                     looking_at(buf, &i, "It isn't your turn") ||
3466                     looking_at(buf, &i, "It is not your move")) {
3467                     /* Illegal move */
3468                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3469                         currentMove = forwardMostMove-1;
3470                         DisplayMove(currentMove - 1); /* before DMError */
3471                         DrawPosition(FALSE, boards[currentMove]);
3472                         SwitchClocks(forwardMostMove-1); // [HGM] race
3473                         DisplayBothClocks();
3474                     }
3475                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3476                     ics_user_moved = 0;
3477                     continue;
3478                 }
3479             }
3480
3481             if (looking_at(buf, &i, "still have time") ||
3482                 looking_at(buf, &i, "not out of time") ||
3483                 looking_at(buf, &i, "either player is out of time") ||
3484                 looking_at(buf, &i, "has timeseal; checking")) {
3485                 /* We must have called his flag a little too soon */
3486                 whiteFlag = blackFlag = FALSE;
3487                 continue;
3488             }
3489
3490             if (looking_at(buf, &i, "added * seconds to") ||
3491                 looking_at(buf, &i, "seconds were added to")) {
3492                 /* Update the clocks */
3493                 SendToICS(ics_prefix);
3494                 SendToICS("refresh\n");
3495                 continue;
3496             }
3497
3498             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3499                 ics_clock_paused = TRUE;
3500                 StopClocks();
3501                 continue;
3502             }
3503
3504             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3505                 ics_clock_paused = FALSE;
3506                 StartClocks();
3507                 continue;
3508             }
3509
3510             /* Grab player ratings from the Creating: message.
3511                Note we have to check for the special case when
3512                the ICS inserts things like [white] or [black]. */
3513             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3514                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3515                 /* star_matches:
3516                    0    player 1 name (not necessarily white)
3517                    1    player 1 rating
3518                    2    empty, white, or black (IGNORED)
3519                    3    player 2 name (not necessarily black)
3520                    4    player 2 rating
3521
3522                    The names/ratings are sorted out when the game
3523                    actually starts (below).
3524                 */
3525                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3526                 player1Rating = string_to_rating(star_match[1]);
3527                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3528                 player2Rating = string_to_rating(star_match[4]);
3529
3530                 if (appData.debugMode)
3531                   fprintf(debugFP,
3532                           "Ratings from 'Creating:' %s %d, %s %d\n",
3533                           player1Name, player1Rating,
3534                           player2Name, player2Rating);
3535
3536                 continue;
3537             }
3538
3539             /* Improved generic start/end-of-game messages */
3540             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3541                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3542                 /* If tkind == 0: */
3543                 /* star_match[0] is the game number */
3544                 /*           [1] is the white player's name */
3545                 /*           [2] is the black player's name */
3546                 /* For end-of-game: */
3547                 /*           [3] is the reason for the game end */
3548                 /*           [4] is a PGN end game-token, preceded by " " */
3549                 /* For start-of-game: */
3550                 /*           [3] begins with "Creating" or "Continuing" */
3551                 /*           [4] is " *" or empty (don't care). */
3552                 int gamenum = atoi(star_match[0]);
3553                 char *whitename, *blackname, *why, *endtoken;
3554                 ChessMove endtype = EndOfFile;
3555
3556                 if (tkind == 0) {
3557                   whitename = star_match[1];
3558                   blackname = star_match[2];
3559                   why = star_match[3];
3560                   endtoken = star_match[4];
3561                 } else {
3562                   whitename = star_match[1];
3563                   blackname = star_match[3];
3564                   why = star_match[5];
3565                   endtoken = star_match[6];
3566                 }
3567
3568                 /* Game start messages */
3569                 if (strncmp(why, "Creating ", 9) == 0 ||
3570                     strncmp(why, "Continuing ", 11) == 0) {
3571                     gs_gamenum = gamenum;
3572                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3573                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3574 #if ZIPPY
3575                     if (appData.zippyPlay) {
3576                         ZippyGameStart(whitename, blackname);
3577                     }
3578 #endif /*ZIPPY*/
3579                     partnerBoardValid = FALSE; // [HGM] bughouse
3580                     continue;
3581                 }
3582
3583                 /* Game end messages */
3584                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3585                     ics_gamenum != gamenum) {
3586                     continue;
3587                 }
3588                 while (endtoken[0] == ' ') endtoken++;
3589                 switch (endtoken[0]) {
3590                   case '*':
3591                   default:
3592                     endtype = GameUnfinished;
3593                     break;
3594                   case '0':
3595                     endtype = BlackWins;
3596                     break;
3597                   case '1':
3598                     if (endtoken[1] == '/')
3599                       endtype = GameIsDrawn;
3600                     else
3601                       endtype = WhiteWins;
3602                     break;
3603                 }
3604                 GameEnds(endtype, why, GE_ICS);
3605 #if ZIPPY
3606                 if (appData.zippyPlay && first.initDone) {
3607                     ZippyGameEnd(endtype, why);
3608                     if (first.pr == NULL) {
3609                       /* Start the next process early so that we'll
3610                          be ready for the next challenge */
3611                       StartChessProgram(&first);
3612                     }
3613                     /* Send "new" early, in case this command takes
3614                        a long time to finish, so that we'll be ready
3615                        for the next challenge. */
3616                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3617                     Reset(TRUE, TRUE);
3618                 }
3619 #endif /*ZIPPY*/
3620                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3621                 continue;
3622             }
3623
3624             if (looking_at(buf, &i, "Removing game * from observation") ||
3625                 looking_at(buf, &i, "no longer observing game *") ||
3626                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3627                 if (gameMode == IcsObserving &&
3628                     atoi(star_match[0]) == ics_gamenum)
3629                   {
3630                       /* icsEngineAnalyze */
3631                       if (appData.icsEngineAnalyze) {
3632                             ExitAnalyzeMode();
3633                             ModeHighlight();
3634                       }
3635                       StopClocks();
3636                       gameMode = IcsIdle;
3637                       ics_gamenum = -1;
3638                       ics_user_moved = FALSE;
3639                   }
3640                 continue;
3641             }
3642
3643             if (looking_at(buf, &i, "no longer examining game *")) {
3644                 if (gameMode == IcsExamining &&
3645                     atoi(star_match[0]) == ics_gamenum)
3646                   {
3647                       gameMode = IcsIdle;
3648                       ics_gamenum = -1;
3649                       ics_user_moved = FALSE;
3650                   }
3651                 continue;
3652             }
3653
3654             /* Advance leftover_start past any newlines we find,
3655                so only partial lines can get reparsed */
3656             if (looking_at(buf, &i, "\n")) {
3657                 prevColor = curColor;
3658                 if (curColor != ColorNormal) {
3659                     if (oldi > next_out) {
3660                         SendToPlayer(&buf[next_out], oldi - next_out);
3661                         next_out = oldi;
3662                     }
3663                     Colorize(ColorNormal, FALSE);
3664                     curColor = ColorNormal;
3665                 }
3666                 if (started == STARTED_BOARD) {
3667                     started = STARTED_NONE;
3668                     parse[parse_pos] = NULLCHAR;
3669                     ParseBoard12(parse);
3670                     ics_user_moved = 0;
3671
3672                     /* Send premove here */
3673                     if (appData.premove) {
3674                       char str[MSG_SIZ];
3675                       if (currentMove == 0 &&
3676                           gameMode == IcsPlayingWhite &&
3677                           appData.premoveWhite) {
3678                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3679                         if (appData.debugMode)
3680                           fprintf(debugFP, "Sending premove:\n");
3681                         SendToICS(str);
3682                       } else if (currentMove == 1 &&
3683                                  gameMode == IcsPlayingBlack &&
3684                                  appData.premoveBlack) {
3685                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3686                         if (appData.debugMode)
3687                           fprintf(debugFP, "Sending premove:\n");
3688                         SendToICS(str);
3689                       } else if (gotPremove) {
3690                         gotPremove = 0;
3691                         ClearPremoveHighlights();
3692                         if (appData.debugMode)
3693                           fprintf(debugFP, "Sending premove:\n");
3694                           UserMoveEvent(premoveFromX, premoveFromY,
3695                                         premoveToX, premoveToY,
3696                                         premovePromoChar);
3697                       }
3698                     }
3699
3700                     /* Usually suppress following prompt */
3701                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3702                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3703                         if (looking_at(buf, &i, "*% ")) {
3704                             savingComment = FALSE;
3705                             suppressKibitz = 0;
3706                         }
3707                     }
3708                     next_out = i;
3709                 } else if (started == STARTED_HOLDINGS) {
3710                     int gamenum;
3711                     char new_piece[MSG_SIZ];
3712                     started = STARTED_NONE;
3713                     parse[parse_pos] = NULLCHAR;
3714                     if (appData.debugMode)
3715                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3716                                                         parse, currentMove);
3717                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3718                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3719                         if (gameInfo.variant == VariantNormal) {
3720                           /* [HGM] We seem to switch variant during a game!
3721                            * Presumably no holdings were displayed, so we have
3722                            * to move the position two files to the right to
3723                            * create room for them!
3724                            */
3725                           VariantClass newVariant;
3726                           switch(gameInfo.boardWidth) { // base guess on board width
3727                                 case 9:  newVariant = VariantShogi; break;
3728                                 case 10: newVariant = VariantGreat; break;
3729                                 default: newVariant = VariantCrazyhouse; break;
3730                           }
3731                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3732                           /* Get a move list just to see the header, which
3733                              will tell us whether this is really bug or zh */
3734                           if (ics_getting_history == H_FALSE) {
3735                             ics_getting_history = H_REQUESTED;
3736                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3737                             SendToICS(str);
3738                           }
3739                         }
3740                         new_piece[0] = NULLCHAR;
3741                         sscanf(parse, "game %d white [%s black [%s <- %s",
3742                                &gamenum, white_holding, black_holding,
3743                                new_piece);
3744                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3745                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3746                         /* [HGM] copy holdings to board holdings area */
3747                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3748                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3749                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3750 #if ZIPPY
3751                         if (appData.zippyPlay && first.initDone) {
3752                             ZippyHoldings(white_holding, black_holding,
3753                                           new_piece);
3754                         }
3755 #endif /*ZIPPY*/
3756                         if (tinyLayout || smallLayout) {
3757                             char wh[16], bh[16];
3758                             PackHolding(wh, white_holding);
3759                             PackHolding(bh, black_holding);
3760                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3761                                     gameInfo.white, gameInfo.black);
3762                         } else {
3763                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3764                                     gameInfo.white, white_holding,
3765                                     gameInfo.black, black_holding);
3766                         }
3767                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3768                         DrawPosition(FALSE, boards[currentMove]);
3769                         DisplayTitle(str);
3770                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3771                         sscanf(parse, "game %d white [%s black [%s <- %s",
3772                                &gamenum, white_holding, black_holding,
3773                                new_piece);
3774                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3775                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3776                         /* [HGM] copy holdings to partner-board holdings area */
3777                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3778                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3779                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3780                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3781                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3782                       }
3783                     }
3784                     /* Suppress following prompt */
3785                     if (looking_at(buf, &i, "*% ")) {
3786                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3787                         savingComment = FALSE;
3788                         suppressKibitz = 0;
3789                     }
3790                     next_out = i;
3791                 }
3792                 continue;
3793             }
3794
3795             i++;                /* skip unparsed character and loop back */
3796         }
3797
3798         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3799 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3800 //          SendToPlayer(&buf[next_out], i - next_out);
3801             started != STARTED_HOLDINGS && leftover_start > next_out) {
3802             SendToPlayer(&buf[next_out], leftover_start - next_out);
3803             next_out = i;
3804         }
3805
3806         leftover_len = buf_len - leftover_start;
3807         /* if buffer ends with something we couldn't parse,
3808            reparse it after appending the next read */
3809
3810     } else if (count == 0) {
3811         RemoveInputSource(isr);
3812         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3813     } else {
3814         DisplayFatalError(_("Error reading from ICS"), error, 1);
3815     }
3816 }
3817
3818
3819 /* Board style 12 looks like this:
3820
3821    <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
3822
3823  * The "<12> " is stripped before it gets to this routine.  The two
3824  * trailing 0's (flip state and clock ticking) are later addition, and
3825  * some chess servers may not have them, or may have only the first.
3826  * Additional trailing fields may be added in the future.
3827  */
3828
3829 #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"
3830
3831 #define RELATION_OBSERVING_PLAYED    0
3832 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3833 #define RELATION_PLAYING_MYMOVE      1
3834 #define RELATION_PLAYING_NOTMYMOVE  -1
3835 #define RELATION_EXAMINING           2
3836 #define RELATION_ISOLATED_BOARD     -3
3837 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3838
3839 void
3840 ParseBoard12(string)
3841      char *string;
3842 {
3843     GameMode newGameMode;
3844     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3845     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3846     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3847     char to_play, board_chars[200];
3848     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3849     char black[32], white[32];
3850     Board board;
3851     int prevMove = currentMove;
3852     int ticking = 2;
3853     ChessMove moveType;
3854     int fromX, fromY, toX, toY;
3855     char promoChar;
3856     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3857     char *bookHit = NULL; // [HGM] book
3858     Boolean weird = FALSE, reqFlag = FALSE;
3859
3860     fromX = fromY = toX = toY = -1;
3861
3862     newGame = FALSE;
3863
3864     if (appData.debugMode)
3865       fprintf(debugFP, _("Parsing board: %s\n"), string);
3866
3867     move_str[0] = NULLCHAR;
3868     elapsed_time[0] = NULLCHAR;
3869     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3870         int  i = 0, j;
3871         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3872             if(string[i] == ' ') { ranks++; files = 0; }
3873             else files++;
3874             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3875             i++;
3876         }
3877         for(j = 0; j <i; j++) board_chars[j] = string[j];
3878         board_chars[i] = '\0';
3879         string += i + 1;
3880     }
3881     n = sscanf(string, PATTERN, &to_play, &double_push,
3882                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3883                &gamenum, white, black, &relation, &basetime, &increment,
3884                &white_stren, &black_stren, &white_time, &black_time,
3885                &moveNum, str, elapsed_time, move_str, &ics_flip,
3886                &ticking);
3887
3888     if (n < 21) {
3889         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3890         DisplayError(str, 0);
3891         return;
3892     }
3893
3894     /* Convert the move number to internal form */
3895     moveNum = (moveNum - 1) * 2;
3896     if (to_play == 'B') moveNum++;
3897     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3898       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3899                         0, 1);
3900       return;
3901     }
3902
3903     switch (relation) {
3904       case RELATION_OBSERVING_PLAYED:
3905       case RELATION_OBSERVING_STATIC:
3906         if (gamenum == -1) {
3907             /* Old ICC buglet */
3908             relation = RELATION_OBSERVING_STATIC;
3909         }
3910         newGameMode = IcsObserving;
3911         break;
3912       case RELATION_PLAYING_MYMOVE:
3913       case RELATION_PLAYING_NOTMYMOVE:
3914         newGameMode =
3915           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3916             IcsPlayingWhite : IcsPlayingBlack;
3917         break;
3918       case RELATION_EXAMINING:
3919         newGameMode = IcsExamining;
3920         break;
3921       case RELATION_ISOLATED_BOARD:
3922       default:
3923         /* Just display this board.  If user was doing something else,
3924            we will forget about it until the next board comes. */
3925         newGameMode = IcsIdle;
3926         break;
3927       case RELATION_STARTING_POSITION:
3928         newGameMode = gameMode;
3929         break;
3930     }
3931
3932     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3933          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3934       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3935       char *toSqr;
3936       for (k = 0; k < ranks; k++) {
3937         for (j = 0; j < files; j++)
3938           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3939         if(gameInfo.holdingsWidth > 1) {
3940              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3941              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3942         }
3943       }
3944       CopyBoard(partnerBoard, board);
3945       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3946         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3947         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3948       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3949       if(toSqr = strchr(str, '-')) {
3950         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3951         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3952       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3953       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3954       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3955       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3956       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3957       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3958                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3959       DisplayMessage(partnerStatus, "");
3960         partnerBoardValid = TRUE;
3961       return;
3962     }
3963
3964     /* Modify behavior for initial board display on move listing
3965        of wild games.
3966        */
3967     switch (ics_getting_history) {
3968       case H_FALSE:
3969       case H_REQUESTED:
3970         break;
3971       case H_GOT_REQ_HEADER:
3972       case H_GOT_UNREQ_HEADER:
3973         /* This is the initial position of the current game */
3974         gamenum = ics_gamenum;
3975         moveNum = 0;            /* old ICS bug workaround */
3976         if (to_play == 'B') {
3977           startedFromSetupPosition = TRUE;
3978           blackPlaysFirst = TRUE;
3979           moveNum = 1;
3980           if (forwardMostMove == 0) forwardMostMove = 1;
3981           if (backwardMostMove == 0) backwardMostMove = 1;
3982           if (currentMove == 0) currentMove = 1;
3983         }
3984         newGameMode = gameMode;
3985         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3986         break;
3987       case H_GOT_UNWANTED_HEADER:
3988         /* This is an initial board that we don't want */
3989         return;
3990       case H_GETTING_MOVES:
3991         /* Should not happen */
3992         DisplayError(_("Error gathering move list: extra board"), 0);
3993         ics_getting_history = H_FALSE;
3994         return;
3995     }
3996
3997    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3998                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
3999      /* [HGM] We seem to have switched variant unexpectedly
4000       * Try to guess new variant from board size
4001       */
4002           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4003           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4004           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4005           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4006           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4007           if(!weird) newVariant = VariantNormal;
4008           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4009           /* Get a move list just to see the header, which
4010              will tell us whether this is really bug or zh */
4011           if (ics_getting_history == H_FALSE) {
4012             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4013             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4014             SendToICS(str);
4015           }
4016     }
4017
4018     /* Take action if this is the first board of a new game, or of a
4019        different game than is currently being displayed.  */
4020     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4021         relation == RELATION_ISOLATED_BOARD) {
4022
4023         /* Forget the old game and get the history (if any) of the new one */
4024         if (gameMode != BeginningOfGame) {
4025           Reset(TRUE, TRUE);
4026         }
4027         newGame = TRUE;
4028         if (appData.autoRaiseBoard) BoardToTop();
4029         prevMove = -3;
4030         if (gamenum == -1) {
4031             newGameMode = IcsIdle;
4032         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4033                    appData.getMoveList && !reqFlag) {
4034             /* Need to get game history */
4035             ics_getting_history = H_REQUESTED;
4036             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4037             SendToICS(str);
4038         }
4039
4040         /* Initially flip the board to have black on the bottom if playing
4041            black or if the ICS flip flag is set, but let the user change
4042            it with the Flip View button. */
4043         flipView = appData.autoFlipView ?
4044           (newGameMode == IcsPlayingBlack) || ics_flip :
4045           appData.flipView;
4046
4047         /* Done with values from previous mode; copy in new ones */
4048         gameMode = newGameMode;
4049         ModeHighlight();
4050         ics_gamenum = gamenum;
4051         if (gamenum == gs_gamenum) {
4052             int klen = strlen(gs_kind);
4053             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4054             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4055             gameInfo.event = StrSave(str);
4056         } else {
4057             gameInfo.event = StrSave("ICS game");
4058         }
4059         gameInfo.site = StrSave(appData.icsHost);
4060         gameInfo.date = PGNDate();
4061         gameInfo.round = StrSave("-");
4062         gameInfo.white = StrSave(white);
4063         gameInfo.black = StrSave(black);
4064         timeControl = basetime * 60 * 1000;
4065         timeControl_2 = 0;
4066         timeIncrement = increment * 1000;
4067         movesPerSession = 0;
4068         gameInfo.timeControl = TimeControlTagValue();
4069         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4070   if (appData.debugMode) {
4071     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4072     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4073     setbuf(debugFP, NULL);
4074   }
4075
4076         gameInfo.outOfBook = NULL;
4077
4078         /* Do we have the ratings? */
4079         if (strcmp(player1Name, white) == 0 &&
4080             strcmp(player2Name, black) == 0) {
4081             if (appData.debugMode)
4082               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4083                       player1Rating, player2Rating);
4084             gameInfo.whiteRating = player1Rating;
4085             gameInfo.blackRating = player2Rating;
4086         } else if (strcmp(player2Name, white) == 0 &&
4087                    strcmp(player1Name, black) == 0) {
4088             if (appData.debugMode)
4089               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4090                       player2Rating, player1Rating);
4091             gameInfo.whiteRating = player2Rating;
4092             gameInfo.blackRating = player1Rating;
4093         }
4094         player1Name[0] = player2Name[0] = NULLCHAR;
4095
4096         /* Silence shouts if requested */
4097         if (appData.quietPlay &&
4098             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4099             SendToICS(ics_prefix);
4100             SendToICS("set shout 0\n");
4101         }
4102     }
4103
4104     /* Deal with midgame name changes */
4105     if (!newGame) {
4106         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4107             if (gameInfo.white) free(gameInfo.white);
4108             gameInfo.white = StrSave(white);
4109         }
4110         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4111             if (gameInfo.black) free(gameInfo.black);
4112             gameInfo.black = StrSave(black);
4113         }
4114     }
4115
4116     /* Throw away game result if anything actually changes in examine mode */
4117     if (gameMode == IcsExamining && !newGame) {
4118         gameInfo.result = GameUnfinished;
4119         if (gameInfo.resultDetails != NULL) {
4120             free(gameInfo.resultDetails);
4121             gameInfo.resultDetails = NULL;
4122         }
4123     }
4124
4125     /* In pausing && IcsExamining mode, we ignore boards coming
4126        in if they are in a different variation than we are. */
4127     if (pauseExamInvalid) return;
4128     if (pausing && gameMode == IcsExamining) {
4129         if (moveNum <= pauseExamForwardMostMove) {
4130             pauseExamInvalid = TRUE;
4131             forwardMostMove = pauseExamForwardMostMove;
4132             return;
4133         }
4134     }
4135
4136   if (appData.debugMode) {
4137     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4138   }
4139     /* Parse the board */
4140     for (k = 0; k < ranks; k++) {
4141       for (j = 0; j < files; j++)
4142         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4143       if(gameInfo.holdingsWidth > 1) {
4144            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4145            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4146       }
4147     }
4148     CopyBoard(boards[moveNum], board);
4149     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4150     if (moveNum == 0) {
4151         startedFromSetupPosition =
4152           !CompareBoards(board, initialPosition);
4153         if(startedFromSetupPosition)
4154             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4155     }
4156
4157     /* [HGM] Set castling rights. Take the outermost Rooks,
4158        to make it also work for FRC opening positions. Note that board12
4159        is really defective for later FRC positions, as it has no way to
4160        indicate which Rook can castle if they are on the same side of King.
4161        For the initial position we grant rights to the outermost Rooks,
4162        and remember thos rights, and we then copy them on positions
4163        later in an FRC game. This means WB might not recognize castlings with
4164        Rooks that have moved back to their original position as illegal,
4165        but in ICS mode that is not its job anyway.
4166     */
4167     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4168     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4169
4170         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4171             if(board[0][i] == WhiteRook) j = i;
4172         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4173         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4174             if(board[0][i] == WhiteRook) j = i;
4175         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4176         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4177             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4178         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4179         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4180             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4181         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4182
4183         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4184         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4185             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4186         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4187             if(board[BOARD_HEIGHT-1][k] == bKing)
4188                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4189         if(gameInfo.variant == VariantTwoKings) {
4190             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4191             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4192             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4193         }
4194     } else { int r;
4195         r = boards[moveNum][CASTLING][0] = initialRights[0];
4196         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4197         r = boards[moveNum][CASTLING][1] = initialRights[1];
4198         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4199         r = boards[moveNum][CASTLING][3] = initialRights[3];
4200         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4201         r = boards[moveNum][CASTLING][4] = initialRights[4];
4202         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4203         /* wildcastle kludge: always assume King has rights */
4204         r = boards[moveNum][CASTLING][2] = initialRights[2];
4205         r = boards[moveNum][CASTLING][5] = initialRights[5];
4206     }
4207     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4208     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4209
4210
4211     if (ics_getting_history == H_GOT_REQ_HEADER ||
4212         ics_getting_history == H_GOT_UNREQ_HEADER) {
4213         /* This was an initial position from a move list, not
4214            the current position */
4215         return;
4216     }
4217
4218     /* Update currentMove and known move number limits */
4219     newMove = newGame || moveNum > forwardMostMove;
4220
4221     if (newGame) {
4222         forwardMostMove = backwardMostMove = currentMove = moveNum;
4223         if (gameMode == IcsExamining && moveNum == 0) {
4224           /* Workaround for ICS limitation: we are not told the wild
4225              type when starting to examine a game.  But if we ask for
4226              the move list, the move list header will tell us */
4227             ics_getting_history = H_REQUESTED;
4228             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4229             SendToICS(str);
4230         }
4231     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4232                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4233 #if ZIPPY
4234         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4235         /* [HGM] applied this also to an engine that is silently watching        */
4236         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4237             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4238             gameInfo.variant == currentlyInitializedVariant) {
4239           takeback = forwardMostMove - moveNum;
4240           for (i = 0; i < takeback; i++) {
4241             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4242             SendToProgram("undo\n", &first);
4243           }
4244         }
4245 #endif
4246
4247         forwardMostMove = moveNum;
4248         if (!pausing || currentMove > forwardMostMove)
4249           currentMove = forwardMostMove;
4250     } else {
4251         /* New part of history that is not contiguous with old part */
4252         if (pausing && gameMode == IcsExamining) {
4253             pauseExamInvalid = TRUE;
4254             forwardMostMove = pauseExamForwardMostMove;
4255             return;
4256         }
4257         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4258 #if ZIPPY
4259             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4260                 // [HGM] when we will receive the move list we now request, it will be
4261                 // fed to the engine from the first move on. So if the engine is not
4262                 // in the initial position now, bring it there.
4263                 InitChessProgram(&first, 0);
4264             }
4265 #endif
4266             ics_getting_history = H_REQUESTED;
4267             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4268             SendToICS(str);
4269         }
4270         forwardMostMove = backwardMostMove = currentMove = moveNum;
4271     }
4272
4273     /* Update the clocks */
4274     if (strchr(elapsed_time, '.')) {
4275       /* Time is in ms */
4276       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4277       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4278     } else {
4279       /* Time is in seconds */
4280       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4281       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4282     }
4283
4284
4285 #if ZIPPY
4286     if (appData.zippyPlay && newGame &&
4287         gameMode != IcsObserving && gameMode != IcsIdle &&
4288         gameMode != IcsExamining)
4289       ZippyFirstBoard(moveNum, basetime, increment);
4290 #endif
4291
4292     /* Put the move on the move list, first converting
4293        to canonical algebraic form. */
4294     if (moveNum > 0) {
4295   if (appData.debugMode) {
4296     if (appData.debugMode) { int f = forwardMostMove;
4297         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4298                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4299                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4300     }
4301     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4302     fprintf(debugFP, "moveNum = %d\n", moveNum);
4303     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4304     setbuf(debugFP, NULL);
4305   }
4306         if (moveNum <= backwardMostMove) {
4307             /* We don't know what the board looked like before
4308                this move.  Punt. */
4309           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4310             strcat(parseList[moveNum - 1], " ");
4311             strcat(parseList[moveNum - 1], elapsed_time);
4312             moveList[moveNum - 1][0] = NULLCHAR;
4313         } else if (strcmp(move_str, "none") == 0) {
4314             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4315             /* Again, we don't know what the board looked like;
4316                this is really the start of the game. */
4317             parseList[moveNum - 1][0] = NULLCHAR;
4318             moveList[moveNum - 1][0] = NULLCHAR;
4319             backwardMostMove = moveNum;
4320             startedFromSetupPosition = TRUE;
4321             fromX = fromY = toX = toY = -1;
4322         } else {
4323           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4324           //                 So we parse the long-algebraic move string in stead of the SAN move
4325           int valid; char buf[MSG_SIZ], *prom;
4326
4327           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4328                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4329           // str looks something like "Q/a1-a2"; kill the slash
4330           if(str[1] == '/')
4331             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4332           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4333           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4334                 strcat(buf, prom); // long move lacks promo specification!
4335           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4336                 if(appData.debugMode)
4337                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4338                 safeStrCpy(move_str, buf, MSG_SIZ);
4339           }
4340           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4341                                 &fromX, &fromY, &toX, &toY, &promoChar)
4342                || ParseOneMove(buf, moveNum - 1, &moveType,
4343                                 &fromX, &fromY, &toX, &toY, &promoChar);
4344           // end of long SAN patch
4345           if (valid) {
4346             (void) CoordsToAlgebraic(boards[moveNum - 1],
4347                                      PosFlags(moveNum - 1),
4348                                      fromY, fromX, toY, toX, promoChar,
4349                                      parseList[moveNum-1]);
4350             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4351               case MT_NONE:
4352               case MT_STALEMATE:
4353               default:
4354                 break;
4355               case MT_CHECK:
4356                 if(gameInfo.variant != VariantShogi)
4357                     strcat(parseList[moveNum - 1], "+");
4358                 break;
4359               case MT_CHECKMATE:
4360               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4361                 strcat(parseList[moveNum - 1], "#");
4362                 break;
4363             }
4364             strcat(parseList[moveNum - 1], " ");
4365             strcat(parseList[moveNum - 1], elapsed_time);
4366             /* currentMoveString is set as a side-effect of ParseOneMove */
4367             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4368             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4369             strcat(moveList[moveNum - 1], "\n");
4370
4371             if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4372               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4373                 ChessSquare old, new = boards[moveNum][k][j];
4374                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4375                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4376                   if(old == new) continue;
4377                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4378                   else if(new == WhiteWazir || new == BlackWazir) {
4379                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4380                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4381                       else boards[moveNum][k][j] = old; // preserve type of Gold
4382                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4383                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4384               }
4385           } else {
4386             /* Move from ICS was illegal!?  Punt. */
4387             if (appData.debugMode) {
4388               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4389               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4390             }
4391             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4392             strcat(parseList[moveNum - 1], " ");
4393             strcat(parseList[moveNum - 1], elapsed_time);
4394             moveList[moveNum - 1][0] = NULLCHAR;
4395             fromX = fromY = toX = toY = -1;
4396           }
4397         }
4398   if (appData.debugMode) {
4399     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4400     setbuf(debugFP, NULL);
4401   }
4402
4403 #if ZIPPY
4404         /* Send move to chess program (BEFORE animating it). */
4405         if (appData.zippyPlay && !newGame && newMove &&
4406            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4407
4408             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4409                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4410                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4411                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4412                             move_str);
4413                     DisplayError(str, 0);
4414                 } else {
4415                     if (first.sendTime) {
4416                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4417                     }
4418                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4419                     if (firstMove && !bookHit) {
4420                         firstMove = FALSE;
4421                         if (first.useColors) {
4422                           SendToProgram(gameMode == IcsPlayingWhite ?
4423                                         "white\ngo\n" :
4424                                         "black\ngo\n", &first);
4425                         } else {
4426                           SendToProgram("go\n", &first);
4427                         }
4428                         first.maybeThinking = TRUE;
4429                     }
4430                 }
4431             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4432               if (moveList[moveNum - 1][0] == NULLCHAR) {
4433                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4434                 DisplayError(str, 0);
4435               } else {
4436                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4437                 SendMoveToProgram(moveNum - 1, &first);
4438               }
4439             }
4440         }
4441 #endif
4442     }
4443
4444     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4445         /* If move comes from a remote source, animate it.  If it
4446            isn't remote, it will have already been animated. */
4447         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4448             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4449         }
4450         if (!pausing && appData.highlightLastMove) {
4451             SetHighlights(fromX, fromY, toX, toY);
4452         }
4453     }
4454
4455     /* Start the clocks */
4456     whiteFlag = blackFlag = FALSE;
4457     appData.clockMode = !(basetime == 0 && increment == 0);
4458     if (ticking == 0) {
4459       ics_clock_paused = TRUE;
4460       StopClocks();
4461     } else if (ticking == 1) {
4462       ics_clock_paused = FALSE;
4463     }
4464     if (gameMode == IcsIdle ||
4465         relation == RELATION_OBSERVING_STATIC ||
4466         relation == RELATION_EXAMINING ||
4467         ics_clock_paused)
4468       DisplayBothClocks();
4469     else
4470       StartClocks();
4471
4472     /* Display opponents and material strengths */
4473     if (gameInfo.variant != VariantBughouse &&
4474         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4475         if (tinyLayout || smallLayout) {
4476             if(gameInfo.variant == VariantNormal)
4477               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4478                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4479                     basetime, increment);
4480             else
4481               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4482                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4483                     basetime, increment, (int) gameInfo.variant);
4484         } else {
4485             if(gameInfo.variant == VariantNormal)
4486               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4487                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4488                     basetime, increment);
4489             else
4490               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4491                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4492                     basetime, increment, VariantName(gameInfo.variant));
4493         }
4494         DisplayTitle(str);
4495   if (appData.debugMode) {
4496     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4497   }
4498     }
4499
4500
4501     /* Display the board */
4502     if (!pausing && !appData.noGUI) {
4503
4504       if (appData.premove)
4505           if (!gotPremove ||
4506              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4507              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4508               ClearPremoveHighlights();
4509
4510       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4511         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4512       DrawPosition(j, boards[currentMove]);
4513
4514       DisplayMove(moveNum - 1);
4515       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4516             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4517               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4518         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4519       }
4520     }
4521
4522     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4523 #if ZIPPY
4524     if(bookHit) { // [HGM] book: simulate book reply
4525         static char bookMove[MSG_SIZ]; // a bit generous?
4526
4527         programStats.nodes = programStats.depth = programStats.time =
4528         programStats.score = programStats.got_only_move = 0;
4529         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4530
4531         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4532         strcat(bookMove, bookHit);
4533         HandleMachineMove(bookMove, &first);
4534     }
4535 #endif
4536 }
4537
4538 void
4539 GetMoveListEvent()
4540 {
4541     char buf[MSG_SIZ];
4542     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4543         ics_getting_history = H_REQUESTED;
4544         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4545         SendToICS(buf);
4546     }
4547 }
4548
4549 void
4550 AnalysisPeriodicEvent(force)
4551      int force;
4552 {
4553     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4554          && !force) || !appData.periodicUpdates)
4555       return;
4556
4557     /* Send . command to Crafty to collect stats */
4558     SendToProgram(".\n", &first);
4559
4560     /* Don't send another until we get a response (this makes
4561        us stop sending to old Crafty's which don't understand
4562        the "." command (sending illegal cmds resets node count & time,
4563        which looks bad)) */
4564     programStats.ok_to_send = 0;
4565 }
4566
4567 void ics_update_width(new_width)
4568         int new_width;
4569 {
4570         ics_printf("set width %d\n", new_width);
4571 }
4572
4573 void
4574 SendMoveToProgram(moveNum, cps)
4575      int moveNum;
4576      ChessProgramState *cps;
4577 {
4578     char buf[MSG_SIZ];
4579
4580     if (cps->useUsermove) {
4581       SendToProgram("usermove ", cps);
4582     }
4583     if (cps->useSAN) {
4584       char *space;
4585       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4586         int len = space - parseList[moveNum];
4587         memcpy(buf, parseList[moveNum], len);
4588         buf[len++] = '\n';
4589         buf[len] = NULLCHAR;
4590       } else {
4591         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4592       }
4593       SendToProgram(buf, cps);
4594     } else {
4595       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4596         AlphaRank(moveList[moveNum], 4);
4597         SendToProgram(moveList[moveNum], cps);
4598         AlphaRank(moveList[moveNum], 4); // and back
4599       } else
4600       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4601        * the engine. It would be nice to have a better way to identify castle
4602        * moves here. */
4603       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4604                                                                          && cps->useOOCastle) {
4605         int fromX = moveList[moveNum][0] - AAA;
4606         int fromY = moveList[moveNum][1] - ONE;
4607         int toX = moveList[moveNum][2] - AAA;
4608         int toY = moveList[moveNum][3] - ONE;
4609         if((boards[moveNum][fromY][fromX] == WhiteKing
4610             && boards[moveNum][toY][toX] == WhiteRook)
4611            || (boards[moveNum][fromY][fromX] == BlackKing
4612                && boards[moveNum][toY][toX] == BlackRook)) {
4613           if(toX > fromX) SendToProgram("O-O\n", cps);
4614           else SendToProgram("O-O-O\n", cps);
4615         }
4616         else SendToProgram(moveList[moveNum], cps);
4617       }
4618       else SendToProgram(moveList[moveNum], cps);
4619       /* End of additions by Tord */
4620     }
4621
4622     /* [HGM] setting up the opening has brought engine in force mode! */
4623     /*       Send 'go' if we are in a mode where machine should play. */
4624     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4625         (gameMode == TwoMachinesPlay   ||
4626 #if ZIPPY
4627          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4628 #endif
4629          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4630         SendToProgram("go\n", cps);
4631   if (appData.debugMode) {
4632     fprintf(debugFP, "(extra)\n");
4633   }
4634     }
4635     setboardSpoiledMachineBlack = 0;
4636 }
4637
4638 void
4639 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4640      ChessMove moveType;
4641      int fromX, fromY, toX, toY;
4642      char promoChar;
4643 {
4644     char user_move[MSG_SIZ];
4645
4646     switch (moveType) {
4647       default:
4648         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4649                 (int)moveType, fromX, fromY, toX, toY);
4650         DisplayError(user_move + strlen("say "), 0);
4651         break;
4652       case WhiteKingSideCastle:
4653       case BlackKingSideCastle:
4654       case WhiteQueenSideCastleWild:
4655       case BlackQueenSideCastleWild:
4656       /* PUSH Fabien */
4657       case WhiteHSideCastleFR:
4658       case BlackHSideCastleFR:
4659       /* POP Fabien */
4660         snprintf(user_move, MSG_SIZ, "o-o\n");
4661         break;
4662       case WhiteQueenSideCastle:
4663       case BlackQueenSideCastle:
4664       case WhiteKingSideCastleWild:
4665       case BlackKingSideCastleWild:
4666       /* PUSH Fabien */
4667       case WhiteASideCastleFR:
4668       case BlackASideCastleFR:
4669       /* POP Fabien */
4670         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4671         break;
4672       case WhiteNonPromotion:
4673       case BlackNonPromotion:
4674         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4675         break;
4676       case WhitePromotion:
4677       case BlackPromotion:
4678         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4679           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4680                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4681                 PieceToChar(WhiteFerz));
4682         else if(gameInfo.variant == VariantGreat)
4683           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4684                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4685                 PieceToChar(WhiteMan));
4686         else
4687           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4688                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4689                 promoChar);
4690         break;
4691       case WhiteDrop:
4692       case BlackDrop:
4693       drop:
4694         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4695                  ToUpper(PieceToChar((ChessSquare) fromX)),
4696                  AAA + toX, ONE + toY);
4697         break;
4698       case IllegalMove:  /* could be a variant we don't quite understand */
4699         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4700       case NormalMove:
4701       case WhiteCapturesEnPassant:
4702       case BlackCapturesEnPassant:
4703         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4704                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4705         break;
4706     }
4707     SendToICS(user_move);
4708     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4709         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4710 }
4711
4712 void
4713 UploadGameEvent()
4714 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4715     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4716     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4717     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4718         DisplayError("You cannot do this while you are playing or observing", 0);
4719         return;
4720     }
4721     if(gameMode != IcsExamining) { // is this ever not the case?
4722         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4723
4724         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4725           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4726         } else { // on FICS we must first go to general examine mode
4727           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4728         }
4729         if(gameInfo.variant != VariantNormal) {
4730             // try figure out wild number, as xboard names are not always valid on ICS
4731             for(i=1; i<=36; i++) {
4732               snprintf(buf, MSG_SIZ, "wild/%d", i);
4733                 if(StringToVariant(buf) == gameInfo.variant) break;
4734             }
4735             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4736             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4737             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4738         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4739         SendToICS(ics_prefix);
4740         SendToICS(buf);
4741         if(startedFromSetupPosition || backwardMostMove != 0) {
4742           fen = PositionToFEN(backwardMostMove, NULL);
4743           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4744             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4745             SendToICS(buf);
4746           } else { // FICS: everything has to set by separate bsetup commands
4747             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4748             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4749             SendToICS(buf);
4750             if(!WhiteOnMove(backwardMostMove)) {
4751                 SendToICS("bsetup tomove black\n");
4752             }
4753             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4754             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4755             SendToICS(buf);
4756             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4757             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4758             SendToICS(buf);
4759             i = boards[backwardMostMove][EP_STATUS];
4760             if(i >= 0) { // set e.p.
4761               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4762                 SendToICS(buf);
4763             }
4764             bsetup++;
4765           }
4766         }
4767       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4768             SendToICS("bsetup done\n"); // switch to normal examining.
4769     }
4770     for(i = backwardMostMove; i<last; i++) {
4771         char buf[20];
4772         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4773         SendToICS(buf);
4774     }
4775     SendToICS(ics_prefix);
4776     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4777 }
4778
4779 void
4780 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4781      int rf, ff, rt, ft;
4782      char promoChar;
4783      char move[7];
4784 {
4785     if (rf == DROP_RANK) {
4786       sprintf(move, "%c@%c%c\n",
4787                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4788     } else {
4789         if (promoChar == 'x' || promoChar == NULLCHAR) {
4790           sprintf(move, "%c%c%c%c\n",
4791                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4792         } else {
4793             sprintf(move, "%c%c%c%c%c\n",
4794                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4795         }
4796     }
4797 }
4798
4799 void
4800 ProcessICSInitScript(f)
4801      FILE *f;
4802 {
4803     char buf[MSG_SIZ];
4804
4805     while (fgets(buf, MSG_SIZ, f)) {
4806         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4807     }
4808
4809     fclose(f);
4810 }
4811
4812
4813 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4814 void
4815 AlphaRank(char *move, int n)
4816 {
4817 //    char *p = move, c; int x, y;
4818
4819     if (appData.debugMode) {
4820         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4821     }
4822
4823     if(move[1]=='*' &&
4824        move[2]>='0' && move[2]<='9' &&
4825        move[3]>='a' && move[3]<='x'    ) {
4826         move[1] = '@';
4827         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4828         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4829     } else
4830     if(move[0]>='0' && move[0]<='9' &&
4831        move[1]>='a' && move[1]<='x' &&
4832        move[2]>='0' && move[2]<='9' &&
4833        move[3]>='a' && move[3]<='x'    ) {
4834         /* input move, Shogi -> normal */
4835         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4836         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4837         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4838         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4839     } else
4840     if(move[1]=='@' &&
4841        move[3]>='0' && move[3]<='9' &&
4842        move[2]>='a' && move[2]<='x'    ) {
4843         move[1] = '*';
4844         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4845         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4846     } else
4847     if(
4848        move[0]>='a' && move[0]<='x' &&
4849        move[3]>='0' && move[3]<='9' &&
4850        move[2]>='a' && move[2]<='x'    ) {
4851          /* output move, normal -> Shogi */
4852         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4853         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4854         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4855         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4856         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4857     }
4858     if (appData.debugMode) {
4859         fprintf(debugFP, "   out = '%s'\n", move);
4860     }
4861 }
4862
4863 char yy_textstr[8000];
4864
4865 /* Parser for moves from gnuchess, ICS, or user typein box */
4866 Boolean
4867 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4868      char *move;
4869      int moveNum;
4870      ChessMove *moveType;
4871      int *fromX, *fromY, *toX, *toY;
4872      char *promoChar;
4873 {
4874     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4875
4876     switch (*moveType) {
4877       case WhitePromotion:
4878       case BlackPromotion:
4879       case WhiteNonPromotion:
4880       case BlackNonPromotion:
4881       case NormalMove:
4882       case WhiteCapturesEnPassant:
4883       case BlackCapturesEnPassant:
4884       case WhiteKingSideCastle:
4885       case WhiteQueenSideCastle:
4886       case BlackKingSideCastle:
4887       case BlackQueenSideCastle:
4888       case WhiteKingSideCastleWild:
4889       case WhiteQueenSideCastleWild:
4890       case BlackKingSideCastleWild:
4891       case BlackQueenSideCastleWild:
4892       /* Code added by Tord: */
4893       case WhiteHSideCastleFR:
4894       case WhiteASideCastleFR:
4895       case BlackHSideCastleFR:
4896       case BlackASideCastleFR:
4897       /* End of code added by Tord */
4898       case IllegalMove:         /* bug or odd chess variant */
4899         *fromX = currentMoveString[0] - AAA;
4900         *fromY = currentMoveString[1] - ONE;
4901         *toX = currentMoveString[2] - AAA;
4902         *toY = currentMoveString[3] - ONE;
4903         *promoChar = currentMoveString[4];
4904         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4905             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4906     if (appData.debugMode) {
4907         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4908     }
4909             *fromX = *fromY = *toX = *toY = 0;
4910             return FALSE;
4911         }
4912         if (appData.testLegality) {
4913           return (*moveType != IllegalMove);
4914         } else {
4915           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4916                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4917         }
4918
4919       case WhiteDrop:
4920       case BlackDrop:
4921         *fromX = *moveType == WhiteDrop ?
4922           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4923           (int) CharToPiece(ToLower(currentMoveString[0]));
4924         *fromY = DROP_RANK;
4925         *toX = currentMoveString[2] - AAA;
4926         *toY = currentMoveString[3] - ONE;
4927         *promoChar = NULLCHAR;
4928         return TRUE;
4929
4930       case AmbiguousMove:
4931       case ImpossibleMove:
4932       case EndOfFile:
4933       case ElapsedTime:
4934       case Comment:
4935       case PGNTag:
4936       case NAG:
4937       case WhiteWins:
4938       case BlackWins:
4939       case GameIsDrawn:
4940       default:
4941     if (appData.debugMode) {
4942         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4943     }
4944         /* bug? */
4945         *fromX = *fromY = *toX = *toY = 0;
4946         *promoChar = NULLCHAR;
4947         return FALSE;
4948     }
4949 }
4950
4951
4952 void
4953 ParsePV(char *pv, Boolean storeComments)
4954 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4955   int fromX, fromY, toX, toY; char promoChar;
4956   ChessMove moveType;
4957   Boolean valid;
4958   int nr = 0;
4959
4960   endPV = forwardMostMove;
4961   do {
4962     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4963     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4964     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4965 if(appData.debugMode){
4966 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);
4967 }
4968     if(!valid && nr == 0 &&
4969        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4970         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4971         // Hande case where played move is different from leading PV move
4972         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4973         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4974         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4975         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4976           endPV += 2; // if position different, keep this
4977           moveList[endPV-1][0] = fromX + AAA;
4978           moveList[endPV-1][1] = fromY + ONE;
4979           moveList[endPV-1][2] = toX + AAA;
4980           moveList[endPV-1][3] = toY + ONE;
4981           parseList[endPV-1][0] = NULLCHAR;
4982           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4983         }
4984       }
4985     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4986     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4987     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4988     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4989         valid++; // allow comments in PV
4990         continue;
4991     }
4992     nr++;
4993     if(endPV+1 > framePtr) break; // no space, truncate
4994     if(!valid) break;
4995     endPV++;
4996     CopyBoard(boards[endPV], boards[endPV-1]);
4997     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4998     moveList[endPV-1][0] = fromX + AAA;
4999     moveList[endPV-1][1] = fromY + ONE;
5000     moveList[endPV-1][2] = toX + AAA;
5001     moveList[endPV-1][3] = toY + ONE;
5002     moveList[endPV-1][4] = promoChar;
5003     moveList[endPV-1][5] = NULLCHAR;
5004     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5005     if(storeComments)
5006         CoordsToAlgebraic(boards[endPV - 1],
5007                              PosFlags(endPV - 1),
5008                              fromY, fromX, toY, toX, promoChar,
5009                              parseList[endPV - 1]);
5010     else
5011         parseList[endPV-1][0] = NULLCHAR;
5012   } while(valid);
5013   currentMove = endPV;
5014   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5015   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5016                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5017   DrawPosition(TRUE, boards[currentMove]);
5018 }
5019
5020 static int lastX, lastY;
5021
5022 Boolean
5023 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5024 {
5025         int startPV;
5026         char *p;
5027
5028         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5029         lastX = x; lastY = y;
5030         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5031         startPV = index;
5032         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5033         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5034         index = startPV;
5035         do{ while(buf[index] && buf[index] != '\n') index++;
5036         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5037         buf[index] = 0;
5038         ParsePV(buf+startPV, FALSE);
5039         *start = startPV; *end = index-1;
5040         return TRUE;
5041 }
5042
5043 Boolean
5044 LoadPV(int x, int y)
5045 { // called on right mouse click to load PV
5046   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5047   lastX = x; lastY = y;
5048   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5049   return TRUE;
5050 }
5051
5052 void
5053 UnLoadPV()
5054 {
5055   if(endPV < 0) return;
5056   endPV = -1;
5057   currentMove = forwardMostMove;
5058   ClearPremoveHighlights();
5059   DrawPosition(TRUE, boards[currentMove]);
5060 }
5061
5062 void
5063 MovePV(int x, int y, int h)
5064 { // step through PV based on mouse coordinates (called on mouse move)
5065   int margin = h>>3, step = 0;
5066
5067   if(endPV < 0) return;
5068   // we must somehow check if right button is still down (might be released off board!)
5069   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5070   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5071   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5072   if(!step) return;
5073   lastX = x; lastY = y;
5074   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5075   currentMove += step;
5076   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5077   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5078                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5079   DrawPosition(FALSE, boards[currentMove]);
5080 }
5081
5082
5083 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5084 // All positions will have equal probability, but the current method will not provide a unique
5085 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5086 #define DARK 1
5087 #define LITE 2
5088 #define ANY 3
5089
5090 int squaresLeft[4];
5091 int piecesLeft[(int)BlackPawn];
5092 int seed, nrOfShuffles;
5093
5094 void GetPositionNumber()
5095 {       // sets global variable seed
5096         int i;
5097
5098         seed = appData.defaultFrcPosition;
5099         if(seed < 0) { // randomize based on time for negative FRC position numbers
5100                 for(i=0; i<50; i++) seed += random();
5101                 seed = random() ^ random() >> 8 ^ random() << 8;
5102                 if(seed<0) seed = -seed;
5103         }
5104 }
5105
5106 int put(Board board, int pieceType, int rank, int n, int shade)
5107 // put the piece on the (n-1)-th empty squares of the given shade
5108 {
5109         int i;
5110
5111         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5112                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5113                         board[rank][i] = (ChessSquare) pieceType;
5114                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5115                         squaresLeft[ANY]--;
5116                         piecesLeft[pieceType]--;
5117                         return i;
5118                 }
5119         }
5120         return -1;
5121 }
5122
5123
5124 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5125 // calculate where the next piece goes, (any empty square), and put it there
5126 {
5127         int i;
5128
5129         i = seed % squaresLeft[shade];
5130         nrOfShuffles *= squaresLeft[shade];
5131         seed /= squaresLeft[shade];
5132         put(board, pieceType, rank, i, shade);
5133 }
5134
5135 void AddTwoPieces(Board board, int pieceType, int rank)
5136 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5137 {
5138         int i, n=squaresLeft[ANY], j=n-1, k;
5139
5140         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5141         i = seed % k;  // pick one
5142         nrOfShuffles *= k;
5143         seed /= k;
5144         while(i >= j) i -= j--;
5145         j = n - 1 - j; i += j;
5146         put(board, pieceType, rank, j, ANY);
5147         put(board, pieceType, rank, i, ANY);
5148 }
5149
5150 void SetUpShuffle(Board board, int number)
5151 {
5152         int i, p, first=1;
5153
5154         GetPositionNumber(); nrOfShuffles = 1;
5155
5156         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5157         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5158         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5159
5160         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5161
5162         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5163             p = (int) board[0][i];
5164             if(p < (int) BlackPawn) piecesLeft[p] ++;
5165             board[0][i] = EmptySquare;
5166         }
5167
5168         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5169             // shuffles restricted to allow normal castling put KRR first
5170             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5171                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5172             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5173                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5174             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5175                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5176             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5177                 put(board, WhiteRook, 0, 0, ANY);
5178             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5179         }
5180
5181         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5182             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5183             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5184                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5185                 while(piecesLeft[p] >= 2) {
5186                     AddOnePiece(board, p, 0, LITE);
5187                     AddOnePiece(board, p, 0, DARK);
5188                 }
5189                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5190             }
5191
5192         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5193             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5194             // but we leave King and Rooks for last, to possibly obey FRC restriction
5195             if(p == (int)WhiteRook) continue;
5196             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5197             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5198         }
5199
5200         // now everything is placed, except perhaps King (Unicorn) and Rooks
5201
5202         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5203             // Last King gets castling rights
5204             while(piecesLeft[(int)WhiteUnicorn]) {
5205                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5206                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5207             }
5208
5209             while(piecesLeft[(int)WhiteKing]) {
5210                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5211                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5212             }
5213
5214
5215         } else {
5216             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5217             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5218         }
5219
5220         // Only Rooks can be left; simply place them all
5221         while(piecesLeft[(int)WhiteRook]) {
5222                 i = put(board, WhiteRook, 0, 0, ANY);
5223                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5224                         if(first) {
5225                                 first=0;
5226                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5227                         }
5228                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5229                 }
5230         }
5231         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5232             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5233         }
5234
5235         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5236 }
5237
5238 int SetCharTable( char *table, const char * map )
5239 /* [HGM] moved here from winboard.c because of its general usefulness */
5240 /*       Basically a safe strcpy that uses the last character as King */
5241 {
5242     int result = FALSE; int NrPieces;
5243
5244     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5245                     && NrPieces >= 12 && !(NrPieces&1)) {
5246         int i; /* [HGM] Accept even length from 12 to 34 */
5247
5248         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5249         for( i=0; i<NrPieces/2-1; i++ ) {
5250             table[i] = map[i];
5251             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5252         }
5253         table[(int) WhiteKing]  = map[NrPieces/2-1];
5254         table[(int) BlackKing]  = map[NrPieces-1];
5255
5256         result = TRUE;
5257     }
5258
5259     return result;
5260 }
5261
5262 void Prelude(Board board)
5263 {       // [HGM] superchess: random selection of exo-pieces
5264         int i, j, k; ChessSquare p;
5265         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5266
5267         GetPositionNumber(); // use FRC position number
5268
5269         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5270             SetCharTable(pieceToChar, appData.pieceToCharTable);
5271             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5272                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5273         }
5274
5275         j = seed%4;                 seed /= 4;
5276         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = 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%3 + (seed%3 >= j); seed /= 3;
5280         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = 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%3;                 seed /= 3;
5284         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5285         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5286         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5287         j = seed%2 + (seed%2 >= j); seed /= 2;
5288         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5289         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5290         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5291         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5292         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5293         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5294         put(board, exoPieces[0],    0, 0, ANY);
5295         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5296 }
5297
5298 void
5299 InitPosition(redraw)
5300      int redraw;
5301 {
5302     ChessSquare (* pieces)[BOARD_FILES];
5303     int i, j, pawnRow, overrule,
5304     oldx = gameInfo.boardWidth,
5305     oldy = gameInfo.boardHeight,
5306     oldh = gameInfo.holdingsWidth;
5307     static int oldv;
5308
5309     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5310
5311     /* [AS] Initialize pv info list [HGM] and game status */
5312     {
5313         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5314             pvInfoList[i].depth = 0;
5315             boards[i][EP_STATUS] = EP_NONE;
5316             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5317         }
5318
5319         initialRulePlies = 0; /* 50-move counter start */
5320
5321         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5322         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5323     }
5324
5325
5326     /* [HGM] logic here is completely changed. In stead of full positions */
5327     /* the initialized data only consist of the two backranks. The switch */
5328     /* selects which one we will use, which is than copied to the Board   */
5329     /* initialPosition, which for the rest is initialized by Pawns and    */
5330     /* empty squares. This initial position is then copied to boards[0],  */
5331     /* possibly after shuffling, so that it remains available.            */
5332
5333     gameInfo.holdingsWidth = 0; /* default board sizes */
5334     gameInfo.boardWidth    = 8;
5335     gameInfo.boardHeight   = 8;
5336     gameInfo.holdingsSize  = 0;
5337     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5338     for(i=0; i<BOARD_FILES-2; i++)
5339       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5340     initialPosition[EP_STATUS] = EP_NONE;
5341     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5342     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5343          SetCharTable(pieceNickName, appData.pieceNickNames);
5344     else SetCharTable(pieceNickName, "............");
5345     pieces = FIDEArray;
5346
5347     switch (gameInfo.variant) {
5348     case VariantFischeRandom:
5349       shuffleOpenings = TRUE;
5350     default:
5351       break;
5352     case VariantShatranj:
5353       pieces = ShatranjArray;
5354       nrCastlingRights = 0;
5355       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5356       break;
5357     case VariantMakruk:
5358       pieces = makrukArray;
5359       nrCastlingRights = 0;
5360       startedFromSetupPosition = TRUE;
5361       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5362       break;
5363     case VariantTwoKings:
5364       pieces = twoKingsArray;
5365       break;
5366     case VariantCapaRandom:
5367       shuffleOpenings = TRUE;
5368     case VariantCapablanca:
5369       pieces = CapablancaArray;
5370       gameInfo.boardWidth = 10;
5371       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5372       break;
5373     case VariantGothic:
5374       pieces = GothicArray;
5375       gameInfo.boardWidth = 10;
5376       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5377       break;
5378     case VariantSChess:
5379       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5380       gameInfo.holdingsSize = 7;
5381       break;
5382     case VariantJanus:
5383       pieces = JanusArray;
5384       gameInfo.boardWidth = 10;
5385       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5386       nrCastlingRights = 6;
5387         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5388         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5389         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5390         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5391         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5392         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5393       break;
5394     case VariantFalcon:
5395       pieces = FalconArray;
5396       gameInfo.boardWidth = 10;
5397       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5398       break;
5399     case VariantXiangqi:
5400       pieces = XiangqiArray;
5401       gameInfo.boardWidth  = 9;
5402       gameInfo.boardHeight = 10;
5403       nrCastlingRights = 0;
5404       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5405       break;
5406     case VariantShogi:
5407       pieces = ShogiArray;
5408       gameInfo.boardWidth  = 9;
5409       gameInfo.boardHeight = 9;
5410       gameInfo.holdingsSize = 7;
5411       nrCastlingRights = 0;
5412       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5413       break;
5414     case VariantCourier:
5415       pieces = CourierArray;
5416       gameInfo.boardWidth  = 12;
5417       nrCastlingRights = 0;
5418       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5419       break;
5420     case VariantKnightmate:
5421       pieces = KnightmateArray;
5422       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5423       break;
5424     case VariantSpartan:
5425       pieces = SpartanArray;
5426       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5427       break;
5428     case VariantFairy:
5429       pieces = fairyArray;
5430       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5431       break;
5432     case VariantGreat:
5433       pieces = GreatArray;
5434       gameInfo.boardWidth = 10;
5435       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5436       gameInfo.holdingsSize = 8;
5437       break;
5438     case VariantSuper:
5439       pieces = FIDEArray;
5440       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5441       gameInfo.holdingsSize = 8;
5442       startedFromSetupPosition = TRUE;
5443       break;
5444     case VariantCrazyhouse:
5445     case VariantBughouse:
5446       pieces = FIDEArray;
5447       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5448       gameInfo.holdingsSize = 5;
5449       break;
5450     case VariantWildCastle:
5451       pieces = FIDEArray;
5452       /* !!?shuffle with kings guaranteed to be on d or e file */
5453       shuffleOpenings = 1;
5454       break;
5455     case VariantNoCastle:
5456       pieces = FIDEArray;
5457       nrCastlingRights = 0;
5458       /* !!?unconstrained back-rank shuffle */
5459       shuffleOpenings = 1;
5460       break;
5461     }
5462
5463     overrule = 0;
5464     if(appData.NrFiles >= 0) {
5465         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5466         gameInfo.boardWidth = appData.NrFiles;
5467     }
5468     if(appData.NrRanks >= 0) {
5469         gameInfo.boardHeight = appData.NrRanks;
5470     }
5471     if(appData.holdingsSize >= 0) {
5472         i = appData.holdingsSize;
5473         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5474         gameInfo.holdingsSize = i;
5475     }
5476     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5477     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5478         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5479
5480     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5481     if(pawnRow < 1) pawnRow = 1;
5482     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5483
5484     /* User pieceToChar list overrules defaults */
5485     if(appData.pieceToCharTable != NULL)
5486         SetCharTable(pieceToChar, appData.pieceToCharTable);
5487
5488     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5489
5490         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5491             s = (ChessSquare) 0; /* account holding counts in guard band */
5492         for( i=0; i<BOARD_HEIGHT; i++ )
5493             initialPosition[i][j] = s;
5494
5495         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5496         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5497         initialPosition[pawnRow][j] = WhitePawn;
5498         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5499         if(gameInfo.variant == VariantXiangqi) {
5500             if(j&1) {
5501                 initialPosition[pawnRow][j] =
5502                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5503                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5504                    initialPosition[2][j] = WhiteCannon;
5505                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5506                 }
5507             }
5508         }
5509         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5510     }
5511     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5512
5513             j=BOARD_LEFT+1;
5514             initialPosition[1][j] = WhiteBishop;
5515             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5516             j=BOARD_RGHT-2;
5517             initialPosition[1][j] = WhiteRook;
5518             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5519     }
5520
5521     if( nrCastlingRights == -1) {
5522         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5523         /*       This sets default castling rights from none to normal corners   */
5524         /* Variants with other castling rights must set them themselves above    */
5525         nrCastlingRights = 6;
5526
5527         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5528         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5529         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5530         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5531         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5532         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5533      }
5534
5535      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5536      if(gameInfo.variant == VariantGreat) { // promotion commoners
5537         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5538         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5539         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5540         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5541      }
5542      if( gameInfo.variant == VariantSChess ) {
5543       initialPosition[1][0] = BlackMarshall;
5544       initialPosition[2][0] = BlackAngel;
5545       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5546       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5547       initialPosition[1][1] = initialPosition[2][1] = 
5548       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5549      }
5550   if (appData.debugMode) {
5551     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5552   }
5553     if(shuffleOpenings) {
5554         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5555         startedFromSetupPosition = TRUE;
5556     }
5557     if(startedFromPositionFile) {
5558       /* [HGM] loadPos: use PositionFile for every new game */
5559       CopyBoard(initialPosition, filePosition);
5560       for(i=0; i<nrCastlingRights; i++)
5561           initialRights[i] = filePosition[CASTLING][i];
5562       startedFromSetupPosition = TRUE;
5563     }
5564
5565     CopyBoard(boards[0], initialPosition);
5566
5567     if(oldx != gameInfo.boardWidth ||
5568        oldy != gameInfo.boardHeight ||
5569        oldv != gameInfo.variant ||
5570        oldh != gameInfo.holdingsWidth
5571                                          )
5572             InitDrawingSizes(-2 ,0);
5573
5574     oldv = gameInfo.variant;
5575     if (redraw)
5576       DrawPosition(TRUE, boards[currentMove]);
5577 }
5578
5579 void
5580 SendBoard(cps, moveNum)
5581      ChessProgramState *cps;
5582      int moveNum;
5583 {
5584     char message[MSG_SIZ];
5585
5586     if (cps->useSetboard) {
5587       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5588       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5589       SendToProgram(message, cps);
5590       free(fen);
5591
5592     } else {
5593       ChessSquare *bp;
5594       int i, j;
5595       /* Kludge to set black to move, avoiding the troublesome and now
5596        * deprecated "black" command.
5597        */
5598       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5599         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5600
5601       SendToProgram("edit\n", cps);
5602       SendToProgram("#\n", cps);
5603       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5604         bp = &boards[moveNum][i][BOARD_LEFT];
5605         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5606           if ((int) *bp < (int) BlackPawn) {
5607             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5608                     AAA + j, ONE + i);
5609             if(message[0] == '+' || message[0] == '~') {
5610               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5611                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5612                         AAA + j, ONE + i);
5613             }
5614             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5615                 message[1] = BOARD_RGHT   - 1 - j + '1';
5616                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5617             }
5618             SendToProgram(message, cps);
5619           }
5620         }
5621       }
5622
5623       SendToProgram("c\n", cps);
5624       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5625         bp = &boards[moveNum][i][BOARD_LEFT];
5626         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5627           if (((int) *bp != (int) EmptySquare)
5628               && ((int) *bp >= (int) BlackPawn)) {
5629             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5630                     AAA + j, ONE + i);
5631             if(message[0] == '+' || message[0] == '~') {
5632               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5633                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5634                         AAA + j, ONE + i);
5635             }
5636             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5637                 message[1] = BOARD_RGHT   - 1 - j + '1';
5638                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5639             }
5640             SendToProgram(message, cps);
5641           }
5642         }
5643       }
5644
5645       SendToProgram(".\n", cps);
5646     }
5647     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5648 }
5649
5650 static int autoQueen; // [HGM] oneclick
5651
5652 int
5653 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5654 {
5655     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5656     /* [HGM] add Shogi promotions */
5657     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5658     ChessSquare piece;
5659     ChessMove moveType;
5660     Boolean premove;
5661
5662     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5663     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5664
5665     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5666       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5667         return FALSE;
5668
5669     piece = boards[currentMove][fromY][fromX];
5670     if(gameInfo.variant == VariantShogi) {
5671         promotionZoneSize = BOARD_HEIGHT/3;
5672         highestPromotingPiece = (int)WhiteFerz;
5673     } else if(gameInfo.variant == VariantMakruk) {
5674         promotionZoneSize = 3;
5675     }
5676
5677     // Treat Lance as Pawn when it is not representing Amazon
5678     if(gameInfo.variant != VariantSuper) {
5679         if(piece == WhiteLance) piece = WhitePawn; else
5680         if(piece == BlackLance) piece = BlackPawn;
5681     }
5682
5683     // next weed out all moves that do not touch the promotion zone at all
5684     if((int)piece >= BlackPawn) {
5685         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5686              return FALSE;
5687         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5688     } else {
5689         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5690            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5691     }
5692
5693     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5694
5695     // weed out mandatory Shogi promotions
5696     if(gameInfo.variant == VariantShogi) {
5697         if(piece >= BlackPawn) {
5698             if(toY == 0 && piece == BlackPawn ||
5699                toY == 0 && piece == BlackQueen ||
5700                toY <= 1 && piece == BlackKnight) {
5701                 *promoChoice = '+';
5702                 return FALSE;
5703             }
5704         } else {
5705             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5706                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5707                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5708                 *promoChoice = '+';
5709                 return FALSE;
5710             }
5711         }
5712     }
5713
5714     // weed out obviously illegal Pawn moves
5715     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5716         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5717         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5718         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5719         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5720         // note we are not allowed to test for valid (non-)capture, due to premove
5721     }
5722
5723     // we either have a choice what to promote to, or (in Shogi) whether to promote
5724     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5725         *promoChoice = PieceToChar(BlackFerz);  // no choice
5726         return FALSE;
5727     }
5728     // no sense asking what we must promote to if it is going to explode...
5729     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5730         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5731         return FALSE;
5732     }
5733     if(autoQueen) { // predetermined
5734         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5735              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5736         else *promoChoice = PieceToChar(BlackQueen);
5737         return FALSE;
5738     }
5739
5740     // suppress promotion popup on illegal moves that are not premoves
5741     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5742               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5743     if(appData.testLegality && !premove) {
5744         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5745                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5746         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5747             return FALSE;
5748     }
5749
5750     return TRUE;
5751 }
5752
5753 int
5754 InPalace(row, column)
5755      int row, column;
5756 {   /* [HGM] for Xiangqi */
5757     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5758          column < (BOARD_WIDTH + 4)/2 &&
5759          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5760     return FALSE;
5761 }
5762
5763 int
5764 PieceForSquare (x, y)
5765      int x;
5766      int y;
5767 {
5768   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5769      return -1;
5770   else
5771      return boards[currentMove][y][x];
5772 }
5773
5774 int
5775 OKToStartUserMove(x, y)
5776      int x, y;
5777 {
5778     ChessSquare from_piece;
5779     int white_piece;
5780
5781     if (matchMode) return FALSE;
5782     if (gameMode == EditPosition) return TRUE;
5783
5784     if (x >= 0 && y >= 0)
5785       from_piece = boards[currentMove][y][x];
5786     else
5787       from_piece = EmptySquare;
5788
5789     if (from_piece == EmptySquare) return FALSE;
5790
5791     white_piece = (int)from_piece >= (int)WhitePawn &&
5792       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5793
5794     switch (gameMode) {
5795       case PlayFromGameFile:
5796       case AnalyzeFile:
5797       case TwoMachinesPlay:
5798       case EndOfGame:
5799         return FALSE;
5800
5801       case IcsObserving:
5802       case IcsIdle:
5803         return FALSE;
5804
5805       case MachinePlaysWhite:
5806       case IcsPlayingBlack:
5807         if (appData.zippyPlay) return FALSE;
5808         if (white_piece) {
5809             DisplayMoveError(_("You are playing Black"));
5810             return FALSE;
5811         }
5812         break;
5813
5814       case MachinePlaysBlack:
5815       case IcsPlayingWhite:
5816         if (appData.zippyPlay) return FALSE;
5817         if (!white_piece) {
5818             DisplayMoveError(_("You are playing White"));
5819             return FALSE;
5820         }
5821         break;
5822
5823       case EditGame:
5824         if (!white_piece && WhiteOnMove(currentMove)) {
5825             DisplayMoveError(_("It is White's turn"));
5826             return FALSE;
5827         }
5828         if (white_piece && !WhiteOnMove(currentMove)) {
5829             DisplayMoveError(_("It is Black's turn"));
5830             return FALSE;
5831         }
5832         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5833             /* Editing correspondence game history */
5834             /* Could disallow this or prompt for confirmation */
5835             cmailOldMove = -1;
5836         }
5837         break;
5838
5839       case BeginningOfGame:
5840         if (appData.icsActive) return FALSE;
5841         if (!appData.noChessProgram) {
5842             if (!white_piece) {
5843                 DisplayMoveError(_("You are playing White"));
5844                 return FALSE;
5845             }
5846         }
5847         break;
5848
5849       case Training:
5850         if (!white_piece && WhiteOnMove(currentMove)) {
5851             DisplayMoveError(_("It is White's turn"));
5852             return FALSE;
5853         }
5854         if (white_piece && !WhiteOnMove(currentMove)) {
5855             DisplayMoveError(_("It is Black's turn"));
5856             return FALSE;
5857         }
5858         break;
5859
5860       default:
5861       case IcsExamining:
5862         break;
5863     }
5864     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5865         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5866         && gameMode != AnalyzeFile && gameMode != Training) {
5867         DisplayMoveError(_("Displayed position is not current"));
5868         return FALSE;
5869     }
5870     return TRUE;
5871 }
5872
5873 Boolean
5874 OnlyMove(int *x, int *y, Boolean captures) {
5875     DisambiguateClosure cl;
5876     if (appData.zippyPlay) return FALSE;
5877     switch(gameMode) {
5878       case MachinePlaysBlack:
5879       case IcsPlayingWhite:
5880       case BeginningOfGame:
5881         if(!WhiteOnMove(currentMove)) return FALSE;
5882         break;
5883       case MachinePlaysWhite:
5884       case IcsPlayingBlack:
5885         if(WhiteOnMove(currentMove)) return FALSE;
5886         break;
5887       case EditGame:
5888         break;
5889       default:
5890         return FALSE;
5891     }
5892     cl.pieceIn = EmptySquare;
5893     cl.rfIn = *y;
5894     cl.ffIn = *x;
5895     cl.rtIn = -1;
5896     cl.ftIn = -1;
5897     cl.promoCharIn = NULLCHAR;
5898     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5899     if( cl.kind == NormalMove ||
5900         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5901         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5902         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5903       fromX = cl.ff;
5904       fromY = cl.rf;
5905       *x = cl.ft;
5906       *y = cl.rt;
5907       return TRUE;
5908     }
5909     if(cl.kind != ImpossibleMove) return FALSE;
5910     cl.pieceIn = EmptySquare;
5911     cl.rfIn = -1;
5912     cl.ffIn = -1;
5913     cl.rtIn = *y;
5914     cl.ftIn = *x;
5915     cl.promoCharIn = NULLCHAR;
5916     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5917     if( cl.kind == NormalMove ||
5918         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5919         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5920         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5921       fromX = cl.ff;
5922       fromY = cl.rf;
5923       *x = cl.ft;
5924       *y = cl.rt;
5925       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5926       return TRUE;
5927     }
5928     return FALSE;
5929 }
5930
5931 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5932 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5933 int lastLoadGameUseList = FALSE;
5934 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5935 ChessMove lastLoadGameStart = EndOfFile;
5936
5937 void
5938 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5939      int fromX, fromY, toX, toY;
5940      int promoChar;
5941 {
5942     ChessMove moveType;
5943     ChessSquare pdown, pup;
5944
5945     /* Check if the user is playing in turn.  This is complicated because we
5946        let the user "pick up" a piece before it is his turn.  So the piece he
5947        tried to pick up may have been captured by the time he puts it down!
5948        Therefore we use the color the user is supposed to be playing in this
5949        test, not the color of the piece that is currently on the starting
5950        square---except in EditGame mode, where the user is playing both
5951        sides; fortunately there the capture race can't happen.  (It can
5952        now happen in IcsExamining mode, but that's just too bad.  The user
5953        will get a somewhat confusing message in that case.)
5954        */
5955
5956     switch (gameMode) {
5957       case PlayFromGameFile:
5958       case AnalyzeFile:
5959       case TwoMachinesPlay:
5960       case EndOfGame:
5961       case IcsObserving:
5962       case IcsIdle:
5963         /* We switched into a game mode where moves are not accepted,
5964            perhaps while the mouse button was down. */
5965         return;
5966
5967       case MachinePlaysWhite:
5968         /* User is moving for Black */
5969         if (WhiteOnMove(currentMove)) {
5970             DisplayMoveError(_("It is White's turn"));
5971             return;
5972         }
5973         break;
5974
5975       case MachinePlaysBlack:
5976         /* User is moving for White */
5977         if (!WhiteOnMove(currentMove)) {
5978             DisplayMoveError(_("It is Black's turn"));
5979             return;
5980         }
5981         break;
5982
5983       case EditGame:
5984       case IcsExamining:
5985       case BeginningOfGame:
5986       case AnalyzeMode:
5987       case Training:
5988         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5989             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5990             /* User is moving for Black */
5991             if (WhiteOnMove(currentMove)) {
5992                 DisplayMoveError(_("It is White's turn"));
5993                 return;
5994             }
5995         } else {
5996             /* User is moving for White */
5997             if (!WhiteOnMove(currentMove)) {
5998                 DisplayMoveError(_("It is Black's turn"));
5999                 return;
6000             }
6001         }
6002         break;
6003
6004       case IcsPlayingBlack:
6005         /* User is moving for Black */
6006         if (WhiteOnMove(currentMove)) {
6007             if (!appData.premove) {
6008                 DisplayMoveError(_("It is White's turn"));
6009             } else if (toX >= 0 && toY >= 0) {
6010                 premoveToX = toX;
6011                 premoveToY = toY;
6012                 premoveFromX = fromX;
6013                 premoveFromY = fromY;
6014                 premovePromoChar = promoChar;
6015                 gotPremove = 1;
6016                 if (appData.debugMode)
6017                     fprintf(debugFP, "Got premove: fromX %d,"
6018                             "fromY %d, toX %d, toY %d\n",
6019                             fromX, fromY, toX, toY);
6020             }
6021             return;
6022         }
6023         break;
6024
6025       case IcsPlayingWhite:
6026         /* User is moving for White */
6027         if (!WhiteOnMove(currentMove)) {
6028             if (!appData.premove) {
6029                 DisplayMoveError(_("It is Black's turn"));
6030             } else if (toX >= 0 && toY >= 0) {
6031                 premoveToX = toX;
6032                 premoveToY = toY;
6033                 premoveFromX = fromX;
6034                 premoveFromY = fromY;
6035                 premovePromoChar = promoChar;
6036                 gotPremove = 1;
6037                 if (appData.debugMode)
6038                     fprintf(debugFP, "Got premove: fromX %d,"
6039                             "fromY %d, toX %d, toY %d\n",
6040                             fromX, fromY, toX, toY);
6041             }
6042             return;
6043         }
6044         break;
6045
6046       default:
6047         break;
6048
6049       case EditPosition:
6050         /* EditPosition, empty square, or different color piece;
6051            click-click move is possible */
6052         if (toX == -2 || toY == -2) {
6053             boards[0][fromY][fromX] = EmptySquare;
6054             DrawPosition(FALSE, boards[currentMove]);
6055             return;
6056         } else if (toX >= 0 && toY >= 0) {
6057             boards[0][toY][toX] = boards[0][fromY][fromX];
6058             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6059                 if(boards[0][fromY][0] != EmptySquare) {
6060                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6061                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6062                 }
6063             } else
6064             if(fromX == BOARD_RGHT+1) {
6065                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6066                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6067                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6068                 }
6069             } else
6070             boards[0][fromY][fromX] = EmptySquare;
6071             DrawPosition(FALSE, boards[currentMove]);
6072             return;
6073         }
6074         return;
6075     }
6076
6077     if(toX < 0 || toY < 0) return;
6078     pdown = boards[currentMove][fromY][fromX];
6079     pup = boards[currentMove][toY][toX];
6080
6081     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6082     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
6083          if( pup != EmptySquare ) return;
6084          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6085            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6086                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6087            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6088            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6089            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6090            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6091          fromY = DROP_RANK;
6092     }
6093
6094     /* [HGM] always test for legality, to get promotion info */
6095     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6096                                          fromY, fromX, toY, toX, promoChar);
6097     /* [HGM] but possibly ignore an IllegalMove result */
6098     if (appData.testLegality) {
6099         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6100             DisplayMoveError(_("Illegal move"));
6101             return;
6102         }
6103     }
6104
6105     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6106 }
6107
6108 /* Common tail of UserMoveEvent and DropMenuEvent */
6109 int
6110 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6111      ChessMove moveType;
6112      int fromX, fromY, toX, toY;
6113      /*char*/int promoChar;
6114 {
6115     char *bookHit = 0;
6116
6117     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6118         // [HGM] superchess: suppress promotions to non-available piece
6119         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6120         if(WhiteOnMove(currentMove)) {
6121             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6122         } else {
6123             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6124         }
6125     }
6126
6127     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6128        move type in caller when we know the move is a legal promotion */
6129     if(moveType == NormalMove && promoChar)
6130         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6131
6132     /* [HGM] <popupFix> The following if has been moved here from
6133        UserMoveEvent(). Because it seemed to belong here (why not allow
6134        piece drops in training games?), and because it can only be
6135        performed after it is known to what we promote. */
6136     if (gameMode == Training) {
6137       /* compare the move played on the board to the next move in the
6138        * game. If they match, display the move and the opponent's response.
6139        * If they don't match, display an error message.
6140        */
6141       int saveAnimate;
6142       Board testBoard;
6143       CopyBoard(testBoard, boards[currentMove]);
6144       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6145
6146       if (CompareBoards(testBoard, boards[currentMove+1])) {
6147         ForwardInner(currentMove+1);
6148
6149         /* Autoplay the opponent's response.
6150          * if appData.animate was TRUE when Training mode was entered,
6151          * the response will be animated.
6152          */
6153         saveAnimate = appData.animate;
6154         appData.animate = animateTraining;
6155         ForwardInner(currentMove+1);
6156         appData.animate = saveAnimate;
6157
6158         /* check for the end of the game */
6159         if (currentMove >= forwardMostMove) {
6160           gameMode = PlayFromGameFile;
6161           ModeHighlight();
6162           SetTrainingModeOff();
6163           DisplayInformation(_("End of game"));
6164         }
6165       } else {
6166         DisplayError(_("Incorrect move"), 0);
6167       }
6168       return 1;
6169     }
6170
6171   /* Ok, now we know that the move is good, so we can kill
6172      the previous line in Analysis Mode */
6173   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6174                                 && currentMove < forwardMostMove) {
6175     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6176     else forwardMostMove = currentMove;
6177   }
6178
6179   /* If we need the chess program but it's dead, restart it */
6180   ResurrectChessProgram();
6181
6182   /* A user move restarts a paused game*/
6183   if (pausing)
6184     PauseEvent();
6185
6186   thinkOutput[0] = NULLCHAR;
6187
6188   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6189
6190   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6191
6192   if (gameMode == BeginningOfGame) {
6193     if (appData.noChessProgram) {
6194       gameMode = EditGame;
6195       SetGameInfo();
6196     } else {
6197       char buf[MSG_SIZ];
6198       gameMode = MachinePlaysBlack;
6199       StartClocks();
6200       SetGameInfo();
6201       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6202       DisplayTitle(buf);
6203       if (first.sendName) {
6204         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6205         SendToProgram(buf, &first);
6206       }
6207       StartClocks();
6208     }
6209     ModeHighlight();
6210   }
6211
6212   /* Relay move to ICS or chess engine */
6213   if (appData.icsActive) {
6214     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6215         gameMode == IcsExamining) {
6216       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6217         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6218         SendToICS("draw ");
6219         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6220       }
6221       // also send plain move, in case ICS does not understand atomic claims
6222       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6223       ics_user_moved = 1;
6224     }
6225   } else {
6226     if (first.sendTime && (gameMode == BeginningOfGame ||
6227                            gameMode == MachinePlaysWhite ||
6228                            gameMode == MachinePlaysBlack)) {
6229       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6230     }
6231     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6232          // [HGM] book: if program might be playing, let it use book
6233         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6234         first.maybeThinking = TRUE;
6235     } else SendMoveToProgram(forwardMostMove-1, &first);
6236     if (currentMove == cmailOldMove + 1) {
6237       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6238     }
6239   }
6240
6241   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6242
6243   switch (gameMode) {
6244   case EditGame:
6245     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6246     case MT_NONE:
6247     case MT_CHECK:
6248       break;
6249     case MT_CHECKMATE:
6250     case MT_STAINMATE:
6251       if (WhiteOnMove(currentMove)) {
6252         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6253       } else {
6254         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6255       }
6256       break;
6257     case MT_STALEMATE:
6258       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6259       break;
6260     }
6261     break;
6262
6263   case MachinePlaysBlack:
6264   case MachinePlaysWhite:
6265     /* disable certain menu options while machine is thinking */
6266     SetMachineThinkingEnables();
6267     break;
6268
6269   default:
6270     break;
6271   }
6272
6273   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6274
6275   if(bookHit) { // [HGM] book: simulate book reply
6276         static char bookMove[MSG_SIZ]; // a bit generous?
6277
6278         programStats.nodes = programStats.depth = programStats.time =
6279         programStats.score = programStats.got_only_move = 0;
6280         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6281
6282         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6283         strcat(bookMove, bookHit);
6284         HandleMachineMove(bookMove, &first);
6285   }
6286   return 1;
6287 }
6288
6289 void
6290 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6291      Board board;
6292      int flags;
6293      ChessMove kind;
6294      int rf, ff, rt, ft;
6295      VOIDSTAR closure;
6296 {
6297     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6298     Markers *m = (Markers *) closure;
6299     if(rf == fromY && ff == fromX)
6300         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6301                          || kind == WhiteCapturesEnPassant
6302                          || kind == BlackCapturesEnPassant);
6303     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6304 }
6305
6306 void
6307 MarkTargetSquares(int clear)
6308 {
6309   int x, y;
6310   if(!appData.markers || !appData.highlightDragging ||
6311      !appData.testLegality || gameMode == EditPosition) return;
6312   if(clear) {
6313     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6314   } else {
6315     int capt = 0;
6316     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6317     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6318       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6319       if(capt)
6320       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6321     }
6322   }
6323   DrawPosition(TRUE, NULL);
6324 }
6325
6326 int
6327 Explode(Board board, int fromX, int fromY, int toX, int toY)
6328 {
6329     if(gameInfo.variant == VariantAtomic &&
6330        (board[toY][toX] != EmptySquare ||                     // capture?
6331         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6332                          board[fromY][fromX] == BlackPawn   )
6333       )) {
6334         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6335         return TRUE;
6336     }
6337     return FALSE;
6338 }
6339
6340 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6341
6342 void LeftClick(ClickType clickType, int xPix, int yPix)
6343 {
6344     int x, y;
6345     Boolean saveAnimate;
6346     static int second = 0, promotionChoice = 0, dragging = 0;
6347     char promoChoice = NULLCHAR;
6348
6349     if(appData.seekGraph && appData.icsActive && loggedOn &&
6350         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6351         SeekGraphClick(clickType, xPix, yPix, 0);
6352         return;
6353     }
6354
6355     if (clickType == Press) ErrorPopDown();
6356     MarkTargetSquares(1);
6357
6358     x = EventToSquare(xPix, BOARD_WIDTH);
6359     y = EventToSquare(yPix, BOARD_HEIGHT);
6360     if (!flipView && y >= 0) {
6361         y = BOARD_HEIGHT - 1 - y;
6362     }
6363     if (flipView && x >= 0) {
6364         x = BOARD_WIDTH - 1 - x;
6365     }
6366
6367     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6368         if(clickType == Release) return; // ignore upclick of click-click destination
6369         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6370         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6371         if(gameInfo.holdingsWidth &&
6372                 (WhiteOnMove(currentMove)
6373                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6374                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6375             // click in right holdings, for determining promotion piece
6376             ChessSquare p = boards[currentMove][y][x];
6377             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6378             if(p != EmptySquare) {
6379                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6380                 fromX = fromY = -1;
6381                 return;
6382             }
6383         }
6384         DrawPosition(FALSE, boards[currentMove]);
6385         return;
6386     }
6387
6388     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6389     if(clickType == Press
6390             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6391               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6392               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6393         return;
6394
6395     autoQueen = appData.alwaysPromoteToQueen;
6396
6397     if (fromX == -1) {
6398       gatingPiece = EmptySquare;
6399       if (clickType != Press) {
6400         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6401             DragPieceEnd(xPix, yPix); dragging = 0;
6402             DrawPosition(FALSE, NULL);
6403         }
6404         return;
6405       }
6406       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6407             /* First square */
6408             if (OKToStartUserMove(x, y)) {
6409                 fromX = x;
6410                 fromY = y;
6411                 second = 0;
6412                 MarkTargetSquares(0);
6413                 DragPieceBegin(xPix, yPix); dragging = 1;
6414                 if (appData.highlightDragging) {
6415                     SetHighlights(x, y, -1, -1);
6416                 }
6417             }
6418             return;
6419         }
6420     }
6421
6422     /* fromX != -1 */
6423     if (clickType == Press && gameMode != EditPosition) {
6424         ChessSquare fromP;
6425         ChessSquare toP;
6426         int frc;
6427
6428         // ignore off-board to clicks
6429         if(y < 0 || x < 0) return;
6430
6431         /* Check if clicking again on the same color piece */
6432         fromP = boards[currentMove][fromY][fromX];
6433         toP = boards[currentMove][y][x];
6434         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6435         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6436              WhitePawn <= toP && toP <= WhiteKing &&
6437              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6438              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6439             (BlackPawn <= fromP && fromP <= BlackKing &&
6440              BlackPawn <= toP && toP <= BlackKing &&
6441              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6442              !(fromP == BlackKing && toP == BlackRook && frc))) {
6443             /* Clicked again on same color piece -- changed his mind */
6444             second = (x == fromX && y == fromY);
6445            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6446             if (appData.highlightDragging) {
6447                 SetHighlights(x, y, -1, -1);
6448             } else {
6449                 ClearHighlights();
6450             }
6451             if (OKToStartUserMove(x, y)) {
6452                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6453                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6454                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6455                  gatingPiece = boards[currentMove][fromY][fromX];
6456                 else gatingPiece = EmptySquare;
6457                 fromX = x;
6458                 fromY = y; dragging = 1;
6459                 MarkTargetSquares(0);
6460                 DragPieceBegin(xPix, yPix);
6461             }
6462            }
6463            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6464            second = FALSE; 
6465         }
6466         // ignore clicks on holdings
6467         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6468     }
6469
6470     if (clickType == Release && x == fromX && y == fromY) {
6471         DragPieceEnd(xPix, yPix); dragging = 0;
6472         if (appData.animateDragging) {
6473             /* Undo animation damage if any */
6474             DrawPosition(FALSE, NULL);
6475         }
6476         if (second) {
6477             /* Second up/down in same square; just abort move */
6478             second = 0;
6479             fromX = fromY = -1;
6480             gatingPiece = EmptySquare;
6481             ClearHighlights();
6482             gotPremove = 0;
6483             ClearPremoveHighlights();
6484         } else {
6485             /* First upclick in same square; start click-click mode */
6486             SetHighlights(x, y, -1, -1);
6487         }
6488         return;
6489     }
6490
6491     /* we now have a different from- and (possibly off-board) to-square */
6492     /* Completed move */
6493     toX = x;
6494     toY = y;
6495     saveAnimate = appData.animate;
6496     if (clickType == Press) {
6497         /* Finish clickclick move */
6498         if (appData.animate || appData.highlightLastMove) {
6499             SetHighlights(fromX, fromY, toX, toY);
6500         } else {
6501             ClearHighlights();
6502         }
6503     } else {
6504         /* Finish drag move */
6505         if (appData.highlightLastMove) {
6506             SetHighlights(fromX, fromY, toX, toY);
6507         } else {
6508             ClearHighlights();
6509         }
6510         DragPieceEnd(xPix, yPix); dragging = 0;
6511         /* Don't animate move and drag both */
6512         appData.animate = FALSE;
6513     }
6514
6515     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6516     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6517         ChessSquare piece = boards[currentMove][fromY][fromX];
6518         if(gameMode == EditPosition && piece != EmptySquare &&
6519            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6520             int n;
6521
6522             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6523                 n = PieceToNumber(piece - (int)BlackPawn);
6524                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6525                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6526                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6527             } else
6528             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6529                 n = PieceToNumber(piece);
6530                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6531                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6532                 boards[currentMove][n][BOARD_WIDTH-2]++;
6533             }
6534             boards[currentMove][fromY][fromX] = EmptySquare;
6535         }
6536         ClearHighlights();
6537         fromX = fromY = -1;
6538         DrawPosition(TRUE, boards[currentMove]);
6539         return;
6540     }
6541
6542     // off-board moves should not be highlighted
6543     if(x < 0 || y < 0) ClearHighlights();
6544
6545     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6546
6547     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6548         SetHighlights(fromX, fromY, toX, toY);
6549         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6550             // [HGM] super: promotion to captured piece selected from holdings
6551             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6552             promotionChoice = TRUE;
6553             // kludge follows to temporarily execute move on display, without promoting yet
6554             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6555             boards[currentMove][toY][toX] = p;
6556             DrawPosition(FALSE, boards[currentMove]);
6557             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6558             boards[currentMove][toY][toX] = q;
6559             DisplayMessage("Click in holdings to choose piece", "");
6560             return;
6561         }
6562         PromotionPopUp();
6563     } else {
6564         int oldMove = currentMove;
6565         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6566         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6567         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6568         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6569            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6570             DrawPosition(TRUE, boards[currentMove]);
6571         fromX = fromY = -1;
6572     }
6573     appData.animate = saveAnimate;
6574     if (appData.animate || appData.animateDragging) {
6575         /* Undo animation damage if needed */
6576         DrawPosition(FALSE, NULL);
6577     }
6578 }
6579
6580 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6581 {   // front-end-free part taken out of PieceMenuPopup
6582     int whichMenu; int xSqr, ySqr;
6583
6584     if(seekGraphUp) { // [HGM] seekgraph
6585         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6586         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6587         return -2;
6588     }
6589
6590     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6591          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6592         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6593         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6594         if(action == Press)   {
6595             originalFlip = flipView;
6596             flipView = !flipView; // temporarily flip board to see game from partners perspective
6597             DrawPosition(TRUE, partnerBoard);
6598             DisplayMessage(partnerStatus, "");
6599             partnerUp = TRUE;
6600         } else if(action == Release) {
6601             flipView = originalFlip;
6602             DrawPosition(TRUE, boards[currentMove]);
6603             partnerUp = FALSE;
6604         }
6605         return -2;
6606     }
6607
6608     xSqr = EventToSquare(x, BOARD_WIDTH);
6609     ySqr = EventToSquare(y, BOARD_HEIGHT);
6610     if (action == Release) UnLoadPV(); // [HGM] pv
6611     if (action != Press) return -2; // return code to be ignored
6612     switch (gameMode) {
6613       case IcsExamining:
6614         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6615       case EditPosition:
6616         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6617         if (xSqr < 0 || ySqr < 0) return -1;\r
6618         whichMenu = 0; // edit-position menu
6619         break;
6620       case IcsObserving:
6621         if(!appData.icsEngineAnalyze) return -1;
6622       case IcsPlayingWhite:
6623       case IcsPlayingBlack:
6624         if(!appData.zippyPlay) goto noZip;
6625       case AnalyzeMode:
6626       case AnalyzeFile:
6627       case MachinePlaysWhite:
6628       case MachinePlaysBlack:
6629       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6630         if (!appData.dropMenu) {
6631           LoadPV(x, y);
6632           return 2; // flag front-end to grab mouse events
6633         }
6634         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6635            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6636       case EditGame:
6637       noZip:
6638         if (xSqr < 0 || ySqr < 0) return -1;
6639         if (!appData.dropMenu || appData.testLegality &&
6640             gameInfo.variant != VariantBughouse &&
6641             gameInfo.variant != VariantCrazyhouse) return -1;
6642         whichMenu = 1; // drop menu
6643         break;
6644       default:
6645         return -1;
6646     }
6647
6648     if (((*fromX = xSqr) < 0) ||
6649         ((*fromY = ySqr) < 0)) {
6650         *fromX = *fromY = -1;
6651         return -1;
6652     }
6653     if (flipView)
6654       *fromX = BOARD_WIDTH - 1 - *fromX;
6655     else
6656       *fromY = BOARD_HEIGHT - 1 - *fromY;
6657
6658     return whichMenu;
6659 }
6660
6661 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6662 {
6663 //    char * hint = lastHint;
6664     FrontEndProgramStats stats;
6665
6666     stats.which = cps == &first ? 0 : 1;
6667     stats.depth = cpstats->depth;
6668     stats.nodes = cpstats->nodes;
6669     stats.score = cpstats->score;
6670     stats.time = cpstats->time;
6671     stats.pv = cpstats->movelist;
6672     stats.hint = lastHint;
6673     stats.an_move_index = 0;
6674     stats.an_move_count = 0;
6675
6676     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6677         stats.hint = cpstats->move_name;
6678         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6679         stats.an_move_count = cpstats->nr_moves;
6680     }
6681
6682     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
6683
6684     SetProgramStats( &stats );
6685 }
6686
6687 void
6688 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6689 {       // count all piece types
6690         int p, f, r;
6691         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6692         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6693         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6694                 p = board[r][f];
6695                 pCnt[p]++;
6696                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6697                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6698                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6699                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6700                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6701                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6702         }
6703 }
6704
6705 int
6706 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6707 {
6708         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6709         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6710
6711         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6712         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6713         if(myPawns == 2 && nMine == 3) // KPP
6714             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6715         if(myPawns == 1 && nMine == 2) // KP
6716             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6717         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6718             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6719         if(myPawns) return FALSE;
6720         if(pCnt[WhiteRook+side])
6721             return pCnt[BlackRook-side] ||
6722                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6723                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6724                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6725         if(pCnt[WhiteCannon+side]) {
6726             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6727             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6728         }
6729         if(pCnt[WhiteKnight+side])
6730             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6731         return FALSE;
6732 }
6733
6734 int
6735 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6736 {
6737         VariantClass v = gameInfo.variant;
6738
6739         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6740         if(v == VariantShatranj) return TRUE; // always winnable through baring
6741         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6742         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6743
6744         if(v == VariantXiangqi) {
6745                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6746
6747                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6748                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6749                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6750                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6751                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6752                 if(stale) // we have at least one last-rank P plus perhaps C
6753                     return majors // KPKX
6754                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6755                 else // KCA*E*
6756                     return pCnt[WhiteFerz+side] // KCAK
6757                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6758                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6759                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6760
6761         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6762                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6763
6764                 if(nMine == 1) return FALSE; // bare King
6765                 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
6766                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6767                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6768                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6769                 if(pCnt[WhiteKnight+side])
6770                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6771                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6772                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6773                 if(nBishops)
6774                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6775                 if(pCnt[WhiteAlfil+side])
6776                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6777                 if(pCnt[WhiteWazir+side])
6778                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6779         }
6780
6781         return TRUE;
6782 }
6783
6784 int
6785 Adjudicate(ChessProgramState *cps)
6786 {       // [HGM] some adjudications useful with buggy engines
6787         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6788         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6789         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6790         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6791         int k, count = 0; static int bare = 1;
6792         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6793         Boolean canAdjudicate = !appData.icsActive;
6794
6795         // most tests only when we understand the game, i.e. legality-checking on
6796             if( appData.testLegality )
6797             {   /* [HGM] Some more adjudications for obstinate engines */
6798                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6799                 static int moveCount = 6;
6800                 ChessMove result;
6801                 char *reason = NULL;
6802
6803                 /* Count what is on board. */
6804                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6805
6806                 /* Some material-based adjudications that have to be made before stalemate test */
6807                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6808                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6809                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6810                      if(canAdjudicate && appData.checkMates) {
6811                          if(engineOpponent)
6812                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6813                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6814                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6815                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6816                          return 1;
6817                      }
6818                 }
6819
6820                 /* Bare King in Shatranj (loses) or Losers (wins) */
6821                 if( nrW == 1 || nrB == 1) {
6822                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6823                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6824                      if(canAdjudicate && appData.checkMates) {
6825                          if(engineOpponent)
6826                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6827                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6828                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6829                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6830                          return 1;
6831                      }
6832                   } else
6833                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6834                   {    /* bare King */
6835                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6836                         if(canAdjudicate && appData.checkMates) {
6837                             /* but only adjudicate if adjudication enabled */
6838                             if(engineOpponent)
6839                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6840                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6841                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6842                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6843                             return 1;
6844                         }
6845                   }
6846                 } else bare = 1;
6847
6848
6849             // don't wait for engine to announce game end if we can judge ourselves
6850             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6851               case MT_CHECK:
6852                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6853                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6854                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6855                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6856                             checkCnt++;
6857                         if(checkCnt >= 2) {
6858                             reason = "Xboard adjudication: 3rd check";
6859                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6860                             break;
6861                         }
6862                     }
6863                 }
6864               case MT_NONE:
6865               default:
6866                 break;
6867               case MT_STALEMATE:
6868               case MT_STAINMATE:
6869                 reason = "Xboard adjudication: Stalemate";
6870                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6871                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6872                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6873                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6874                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6875                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6876                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6877                                                                         EP_CHECKMATE : EP_WINS);
6878                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6879                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6880                 }
6881                 break;
6882               case MT_CHECKMATE:
6883                 reason = "Xboard adjudication: Checkmate";
6884                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6885                 break;
6886             }
6887
6888                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6889                     case EP_STALEMATE:
6890                         result = GameIsDrawn; break;
6891                     case EP_CHECKMATE:
6892                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6893                     case EP_WINS:
6894                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6895                     default:
6896                         result = EndOfFile;
6897                 }
6898                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6899                     if(engineOpponent)
6900                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6901                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6902                     GameEnds( result, reason, GE_XBOARD );
6903                     return 1;
6904                 }
6905
6906                 /* Next absolutely insufficient mating material. */
6907                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6908                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6909                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6910
6911                      /* always flag draws, for judging claims */
6912                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6913
6914                      if(canAdjudicate && appData.materialDraws) {
6915                          /* but only adjudicate them if adjudication enabled */
6916                          if(engineOpponent) {
6917                            SendToProgram("force\n", engineOpponent); // suppress reply
6918                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6919                          }
6920                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6921                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6922                          return 1;
6923                      }
6924                 }
6925
6926                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6927                 if(gameInfo.variant == VariantXiangqi ?
6928                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6929                  : nrW + nrB == 4 &&
6930                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6931                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6932                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6933                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6934                    ) ) {
6935                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6936                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6937                           if(engineOpponent) {
6938                             SendToProgram("force\n", engineOpponent); // suppress reply
6939                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6940                           }
6941                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6942                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6943                           return 1;
6944                      }
6945                 } else moveCount = 6;
6946             }
6947         if (appData.debugMode) { int i;
6948             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6949                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6950                     appData.drawRepeats);
6951             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6952               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6953
6954         }
6955
6956         // Repetition draws and 50-move rule can be applied independently of legality testing
6957
6958                 /* Check for rep-draws */
6959                 count = 0;
6960                 for(k = forwardMostMove-2;
6961                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6962                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6963                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6964                     k-=2)
6965                 {   int rights=0;
6966                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6967                         /* compare castling rights */
6968                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6969                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6970                                 rights++; /* King lost rights, while rook still had them */
6971                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6972                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6973                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6974                                    rights++; /* but at least one rook lost them */
6975                         }
6976                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6977                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6978                                 rights++;
6979                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6980                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6981                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6982                                    rights++;
6983                         }
6984                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6985                             && appData.drawRepeats > 1) {
6986                              /* adjudicate after user-specified nr of repeats */
6987                              int result = GameIsDrawn;
6988                              char *details = "XBoard adjudication: repetition draw";
6989                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6990                                 // [HGM] xiangqi: check for forbidden perpetuals
6991                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6992                                 for(m=forwardMostMove; m>k; m-=2) {
6993                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6994                                         ourPerpetual = 0; // the current mover did not always check
6995                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6996                                         hisPerpetual = 0; // the opponent did not always check
6997                                 }
6998                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6999                                                                         ourPerpetual, hisPerpetual);
7000                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7001                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7002                                     details = "Xboard adjudication: perpetual checking";
7003                                 } else
7004                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7005                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7006                                 } else
7007                                 // Now check for perpetual chases
7008                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7009                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7010                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7011                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7012                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7013                                         details = "Xboard adjudication: perpetual chasing";
7014                                     } else
7015                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7016                                         break; // Abort repetition-checking loop.
7017                                 }
7018                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7019                              }
7020                              if(engineOpponent) {
7021                                SendToProgram("force\n", engineOpponent); // suppress reply
7022                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7023                              }
7024                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7025                              GameEnds( result, details, GE_XBOARD );
7026                              return 1;
7027                         }
7028                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7029                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7030                     }
7031                 }
7032
7033                 /* Now we test for 50-move draws. Determine ply count */
7034                 count = forwardMostMove;
7035                 /* look for last irreversble move */
7036                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7037                     count--;
7038                 /* if we hit starting position, add initial plies */
7039                 if( count == backwardMostMove )
7040                     count -= initialRulePlies;
7041                 count = forwardMostMove - count;
7042                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7043                         // adjust reversible move counter for checks in Xiangqi
7044                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7045                         if(i < backwardMostMove) i = backwardMostMove;
7046                         while(i <= forwardMostMove) {
7047                                 lastCheck = inCheck; // check evasion does not count
7048                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7049                                 if(inCheck || lastCheck) count--; // check does not count
7050                                 i++;
7051                         }
7052                 }
7053                 if( count >= 100)
7054                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7055                          /* this is used to judge if draw claims are legal */
7056                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7057                          if(engineOpponent) {
7058                            SendToProgram("force\n", engineOpponent); // suppress reply
7059                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7060                          }
7061                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7062                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7063                          return 1;
7064                 }
7065
7066                 /* if draw offer is pending, treat it as a draw claim
7067                  * when draw condition present, to allow engines a way to
7068                  * claim draws before making their move to avoid a race
7069                  * condition occurring after their move
7070                  */
7071                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7072                          char *p = NULL;
7073                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7074                              p = "Draw claim: 50-move rule";
7075                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7076                              p = "Draw claim: 3-fold repetition";
7077                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7078                              p = "Draw claim: insufficient mating material";
7079                          if( p != NULL && canAdjudicate) {
7080                              if(engineOpponent) {
7081                                SendToProgram("force\n", engineOpponent); // suppress reply
7082                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7083                              }
7084                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7085                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7086                              return 1;
7087                          }
7088                 }
7089
7090                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7091                     if(engineOpponent) {
7092                       SendToProgram("force\n", engineOpponent); // suppress reply
7093                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7094                     }
7095                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7096                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7097                     return 1;
7098                 }
7099         return 0;
7100 }
7101
7102 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7103 {   // [HGM] book: this routine intercepts moves to simulate book replies
7104     char *bookHit = NULL;
7105
7106     //first determine if the incoming move brings opponent into his book
7107     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7108         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7109     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7110     if(bookHit != NULL && !cps->bookSuspend) {
7111         // make sure opponent is not going to reply after receiving move to book position
7112         SendToProgram("force\n", cps);
7113         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7114     }
7115     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7116     // now arrange restart after book miss
7117     if(bookHit) {
7118         // after a book hit we never send 'go', and the code after the call to this routine
7119         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7120         char buf[MSG_SIZ];
7121         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7122         SendToProgram(buf, cps);
7123         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7124     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7125         SendToProgram("go\n", cps);
7126         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7127     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7128         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7129             SendToProgram("go\n", cps);
7130         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7131     }
7132     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7133 }
7134
7135 char *savedMessage;
7136 ChessProgramState *savedState;
7137 void DeferredBookMove(void)
7138 {
7139         if(savedState->lastPing != savedState->lastPong)
7140                     ScheduleDelayedEvent(DeferredBookMove, 10);
7141         else
7142         HandleMachineMove(savedMessage, savedState);
7143 }
7144
7145 void
7146 HandleMachineMove(message, cps)
7147      char *message;
7148      ChessProgramState *cps;
7149 {
7150     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7151     char realname[MSG_SIZ];
7152     int fromX, fromY, toX, toY;
7153     ChessMove moveType;
7154     char promoChar;
7155     char *p;
7156     int machineWhite;
7157     char *bookHit;
7158
7159     cps->userError = 0;
7160
7161 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7162     /*
7163      * Kludge to ignore BEL characters
7164      */
7165     while (*message == '\007') message++;
7166
7167     /*
7168      * [HGM] engine debug message: ignore lines starting with '#' character
7169      */
7170     if(cps->debug && *message == '#') return;
7171
7172     /*
7173      * Look for book output
7174      */
7175     if (cps == &first && bookRequested) {
7176         if (message[0] == '\t' || message[0] == ' ') {
7177             /* Part of the book output is here; append it */
7178             strcat(bookOutput, message);
7179             strcat(bookOutput, "  \n");
7180             return;
7181         } else if (bookOutput[0] != NULLCHAR) {
7182             /* All of book output has arrived; display it */
7183             char *p = bookOutput;
7184             while (*p != NULLCHAR) {
7185                 if (*p == '\t') *p = ' ';
7186                 p++;
7187             }
7188             DisplayInformation(bookOutput);
7189             bookRequested = FALSE;
7190             /* Fall through to parse the current output */
7191         }
7192     }
7193
7194     /*
7195      * Look for machine move.
7196      */
7197     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7198         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7199     {
7200         /* This method is only useful on engines that support ping */
7201         if (cps->lastPing != cps->lastPong) {
7202           if (gameMode == BeginningOfGame) {
7203             /* Extra move from before last new; ignore */
7204             if (appData.debugMode) {
7205                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7206             }
7207           } else {
7208             if (appData.debugMode) {
7209                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7210                         cps->which, gameMode);
7211             }
7212
7213             SendToProgram("undo\n", cps);
7214           }
7215           return;
7216         }
7217
7218         switch (gameMode) {
7219           case BeginningOfGame:
7220             /* Extra move from before last reset; ignore */
7221             if (appData.debugMode) {
7222                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7223             }
7224             return;
7225
7226           case EndOfGame:
7227           case IcsIdle:
7228           default:
7229             /* Extra move after we tried to stop.  The mode test is
7230                not a reliable way of detecting this problem, but it's
7231                the best we can do on engines that don't support ping.
7232             */
7233             if (appData.debugMode) {
7234                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7235                         cps->which, gameMode);
7236             }
7237             SendToProgram("undo\n", cps);
7238             return;
7239
7240           case MachinePlaysWhite:
7241           case IcsPlayingWhite:
7242             machineWhite = TRUE;
7243             break;
7244
7245           case MachinePlaysBlack:
7246           case IcsPlayingBlack:
7247             machineWhite = FALSE;
7248             break;
7249
7250           case TwoMachinesPlay:
7251             machineWhite = (cps->twoMachinesColor[0] == 'w');
7252             break;
7253         }
7254         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7255             if (appData.debugMode) {
7256                 fprintf(debugFP,
7257                         "Ignoring move out of turn by %s, gameMode %d"
7258                         ", forwardMost %d\n",
7259                         cps->which, gameMode, forwardMostMove);
7260             }
7261             return;
7262         }
7263
7264     if (appData.debugMode) { int f = forwardMostMove;
7265         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7266                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7267                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7268     }
7269         if(cps->alphaRank) AlphaRank(machineMove, 4);
7270         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7271                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7272             /* Machine move could not be parsed; ignore it. */
7273           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7274                     machineMove, cps->which);
7275             DisplayError(buf1, 0);
7276             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7277                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7278             if (gameMode == TwoMachinesPlay) {
7279               GameEnds(machineWhite ? BlackWins : WhiteWins,
7280                        buf1, GE_XBOARD);
7281             }
7282             return;
7283         }
7284
7285         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7286         /* So we have to redo legality test with true e.p. status here,  */
7287         /* to make sure an illegal e.p. capture does not slip through,   */
7288         /* to cause a forfeit on a justified illegal-move complaint      */
7289         /* of the opponent.                                              */
7290         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7291            ChessMove moveType;
7292            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7293                              fromY, fromX, toY, toX, promoChar);
7294             if (appData.debugMode) {
7295                 int i;
7296                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7297                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7298                 fprintf(debugFP, "castling rights\n");
7299             }
7300             if(moveType == IllegalMove) {
7301               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7302                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7303                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7304                            buf1, GE_XBOARD);
7305                 return;
7306            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7307            /* [HGM] Kludge to handle engines that send FRC-style castling
7308               when they shouldn't (like TSCP-Gothic) */
7309            switch(moveType) {
7310              case WhiteASideCastleFR:
7311              case BlackASideCastleFR:
7312                toX+=2;
7313                currentMoveString[2]++;
7314                break;
7315              case WhiteHSideCastleFR:
7316              case BlackHSideCastleFR:
7317                toX--;
7318                currentMoveString[2]--;
7319                break;
7320              default: ; // nothing to do, but suppresses warning of pedantic compilers
7321            }
7322         }
7323         hintRequested = FALSE;
7324         lastHint[0] = NULLCHAR;
7325         bookRequested = FALSE;
7326         /* Program may be pondering now */
7327         cps->maybeThinking = TRUE;
7328         if (cps->sendTime == 2) cps->sendTime = 1;
7329         if (cps->offeredDraw) cps->offeredDraw--;
7330
7331         /* currentMoveString is set as a side-effect of ParseOneMove */
7332         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7333         strcat(machineMove, "\n");
7334         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7335
7336         /* [AS] Save move info*/
7337         pvInfoList[ forwardMostMove ].score = programStats.score;
7338         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7339         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7340
7341         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7342
7343         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7344         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7345             int count = 0;
7346
7347             while( count < adjudicateLossPlies ) {
7348                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7349
7350                 if( count & 1 ) {
7351                     score = -score; /* Flip score for winning side */
7352                 }
7353
7354                 if( score > adjudicateLossThreshold ) {
7355                     break;
7356                 }
7357
7358                 count++;
7359             }
7360
7361             if( count >= adjudicateLossPlies ) {
7362                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7363
7364                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7365                     "Xboard adjudication",
7366                     GE_XBOARD );
7367
7368                 return;
7369             }
7370         }
7371
7372         if(Adjudicate(cps)) {
7373             DrawPosition(FALSE, boards[currentMove = forwardMostMove-1]);
7374             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7375             return; // [HGM] adjudicate: for all automatic game ends
7376         }
7377
7378 #if ZIPPY
7379         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7380             first.initDone) {
7381           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7382                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7383                 SendToICS("draw ");
7384                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7385           }
7386           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7387           ics_user_moved = 1;
7388           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7389                 char buf[3*MSG_SIZ];
7390
7391                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7392                         programStats.score / 100.,
7393                         programStats.depth,
7394                         programStats.time / 100.,
7395                         (unsigned int)programStats.nodes,
7396                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7397                         programStats.movelist);
7398                 SendToICS(buf);
7399 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7400           }
7401         }
7402 #endif
7403
7404         /* [AS] Clear stats for next move */
7405         ClearProgramStats();
7406         thinkOutput[0] = NULLCHAR;
7407         hiddenThinkOutputState = 0;
7408
7409         bookHit = NULL;
7410         if (gameMode == TwoMachinesPlay) {
7411             /* [HGM] relaying draw offers moved to after reception of move */
7412             /* and interpreting offer as claim if it brings draw condition */
7413             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7414                 SendToProgram("draw\n", cps->other);
7415             }
7416             if (cps->other->sendTime) {
7417                 SendTimeRemaining(cps->other,
7418                                   cps->other->twoMachinesColor[0] == 'w');
7419             }
7420             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7421             if (firstMove && !bookHit) {
7422                 firstMove = FALSE;
7423                 if (cps->other->useColors) {
7424                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7425                 }
7426                 SendToProgram("go\n", cps->other);
7427             }
7428             cps->other->maybeThinking = TRUE;
7429         }
7430
7431         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7432
7433         if (!pausing && appData.ringBellAfterMoves) {
7434             RingBell();
7435         }
7436
7437         /*
7438          * Reenable menu items that were disabled while
7439          * machine was thinking
7440          */
7441         if (gameMode != TwoMachinesPlay)
7442             SetUserThinkingEnables();
7443
7444         // [HGM] book: after book hit opponent has received move and is now in force mode
7445         // force the book reply into it, and then fake that it outputted this move by jumping
7446         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7447         if(bookHit) {
7448                 static char bookMove[MSG_SIZ]; // a bit generous?
7449
7450                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7451                 strcat(bookMove, bookHit);
7452                 message = bookMove;
7453                 cps = cps->other;
7454                 programStats.nodes = programStats.depth = programStats.time =
7455                 programStats.score = programStats.got_only_move = 0;
7456                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7457
7458                 if(cps->lastPing != cps->lastPong) {
7459                     savedMessage = message; // args for deferred call
7460                     savedState = cps;
7461                     ScheduleDelayedEvent(DeferredBookMove, 10);
7462                     return;
7463                 }
7464                 goto FakeBookMove;
7465         }
7466
7467         return;
7468     }
7469
7470     /* Set special modes for chess engines.  Later something general
7471      *  could be added here; for now there is just one kludge feature,
7472      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7473      *  when "xboard" is given as an interactive command.
7474      */
7475     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7476         cps->useSigint = FALSE;
7477         cps->useSigterm = FALSE;
7478     }
7479     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7480       ParseFeatures(message+8, cps);
7481       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7482     }
7483
7484     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7485       int dummy, s=6; char buf[MSG_SIZ];
7486       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7487       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7488       ParseFEN(boards[0], &dummy, message+s);
7489       DrawPosition(TRUE, boards[0]);
7490       startedFromSetupPosition = TRUE;
7491       return;
7492     }
7493     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7494      * want this, I was asked to put it in, and obliged.
7495      */
7496     if (!strncmp(message, "setboard ", 9)) {
7497         Board initial_position;
7498
7499         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7500
7501         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7502             DisplayError(_("Bad FEN received from engine"), 0);
7503             return ;
7504         } else {
7505            Reset(TRUE, FALSE);
7506            CopyBoard(boards[0], initial_position);
7507            initialRulePlies = FENrulePlies;
7508            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7509            else gameMode = MachinePlaysBlack;
7510            DrawPosition(FALSE, boards[currentMove]);
7511         }
7512         return;
7513     }
7514
7515     /*
7516      * Look for communication commands
7517      */
7518     if (!strncmp(message, "telluser ", 9)) {
7519         if(message[9] == '\\' && message[10] == '\\')
7520             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7521         DisplayNote(message + 9);
7522         return;
7523     }
7524     if (!strncmp(message, "tellusererror ", 14)) {
7525         cps->userError = 1;
7526         if(message[14] == '\\' && message[15] == '\\')
7527             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7528         DisplayError(message + 14, 0);
7529         return;
7530     }
7531     if (!strncmp(message, "tellopponent ", 13)) {
7532       if (appData.icsActive) {
7533         if (loggedOn) {
7534           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7535           SendToICS(buf1);
7536         }
7537       } else {
7538         DisplayNote(message + 13);
7539       }
7540       return;
7541     }
7542     if (!strncmp(message, "tellothers ", 11)) {
7543       if (appData.icsActive) {
7544         if (loggedOn) {
7545           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7546           SendToICS(buf1);
7547         }
7548       }
7549       return;
7550     }
7551     if (!strncmp(message, "tellall ", 8)) {
7552       if (appData.icsActive) {
7553         if (loggedOn) {
7554           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7555           SendToICS(buf1);
7556         }
7557       } else {
7558         DisplayNote(message + 8);
7559       }
7560       return;
7561     }
7562     if (strncmp(message, "warning", 7) == 0) {
7563         /* Undocumented feature, use tellusererror in new code */
7564         DisplayError(message, 0);
7565         return;
7566     }
7567     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7568         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7569         strcat(realname, " query");
7570         AskQuestion(realname, buf2, buf1, cps->pr);
7571         return;
7572     }
7573     /* Commands from the engine directly to ICS.  We don't allow these to be
7574      *  sent until we are logged on. Crafty kibitzes have been known to
7575      *  interfere with the login process.
7576      */
7577     if (loggedOn) {
7578         if (!strncmp(message, "tellics ", 8)) {
7579             SendToICS(message + 8);
7580             SendToICS("\n");
7581             return;
7582         }
7583         if (!strncmp(message, "tellicsnoalias ", 15)) {
7584             SendToICS(ics_prefix);
7585             SendToICS(message + 15);
7586             SendToICS("\n");
7587             return;
7588         }
7589         /* The following are for backward compatibility only */
7590         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7591             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7592             SendToICS(ics_prefix);
7593             SendToICS(message);
7594             SendToICS("\n");
7595             return;
7596         }
7597     }
7598     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7599         return;
7600     }
7601     /*
7602      * If the move is illegal, cancel it and redraw the board.
7603      * Also deal with other error cases.  Matching is rather loose
7604      * here to accommodate engines written before the spec.
7605      */
7606     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7607         strncmp(message, "Error", 5) == 0) {
7608         if (StrStr(message, "name") ||
7609             StrStr(message, "rating") || StrStr(message, "?") ||
7610             StrStr(message, "result") || StrStr(message, "board") ||
7611             StrStr(message, "bk") || StrStr(message, "computer") ||
7612             StrStr(message, "variant") || StrStr(message, "hint") ||
7613             StrStr(message, "random") || StrStr(message, "depth") ||
7614             StrStr(message, "accepted")) {
7615             return;
7616         }
7617         if (StrStr(message, "protover")) {
7618           /* Program is responding to input, so it's apparently done
7619              initializing, and this error message indicates it is
7620              protocol version 1.  So we don't need to wait any longer
7621              for it to initialize and send feature commands. */
7622           FeatureDone(cps, 1);
7623           cps->protocolVersion = 1;
7624           return;
7625         }
7626         cps->maybeThinking = FALSE;
7627
7628         if (StrStr(message, "draw")) {
7629             /* Program doesn't have "draw" command */
7630             cps->sendDrawOffers = 0;
7631             return;
7632         }
7633         if (cps->sendTime != 1 &&
7634             (StrStr(message, "time") || StrStr(message, "otim"))) {
7635           /* Program apparently doesn't have "time" or "otim" command */
7636           cps->sendTime = 0;
7637           return;
7638         }
7639         if (StrStr(message, "analyze")) {
7640             cps->analysisSupport = FALSE;
7641             cps->analyzing = FALSE;
7642             Reset(FALSE, TRUE);
7643             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7644             DisplayError(buf2, 0);
7645             return;
7646         }
7647         if (StrStr(message, "(no matching move)st")) {
7648           /* Special kludge for GNU Chess 4 only */
7649           cps->stKludge = TRUE;
7650           SendTimeControl(cps, movesPerSession, timeControl,
7651                           timeIncrement, appData.searchDepth,
7652                           searchTime);
7653           return;
7654         }
7655         if (StrStr(message, "(no matching move)sd")) {
7656           /* Special kludge for GNU Chess 4 only */
7657           cps->sdKludge = TRUE;
7658           SendTimeControl(cps, movesPerSession, timeControl,
7659                           timeIncrement, appData.searchDepth,
7660                           searchTime);
7661           return;
7662         }
7663         if (!StrStr(message, "llegal")) {
7664             return;
7665         }
7666         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7667             gameMode == IcsIdle) return;
7668         if (forwardMostMove <= backwardMostMove) return;
7669         if (pausing) PauseEvent();
7670       if(appData.forceIllegal) {
7671             // [HGM] illegal: machine refused move; force position after move into it
7672           SendToProgram("force\n", cps);
7673           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7674                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7675                 // when black is to move, while there might be nothing on a2 or black
7676                 // might already have the move. So send the board as if white has the move.
7677                 // But first we must change the stm of the engine, as it refused the last move
7678                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7679                 if(WhiteOnMove(forwardMostMove)) {
7680                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7681                     SendBoard(cps, forwardMostMove); // kludgeless board
7682                 } else {
7683                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7684                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7685                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7686                 }
7687           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7688             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7689                  gameMode == TwoMachinesPlay)
7690               SendToProgram("go\n", cps);
7691             return;
7692       } else
7693         if (gameMode == PlayFromGameFile) {
7694             /* Stop reading this game file */
7695             gameMode = EditGame;
7696             ModeHighlight();
7697         }
7698         /* [HGM] illegal-move claim should forfeit game when Xboard */
7699         /* only passes fully legal moves                            */
7700         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7701             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7702                                 "False illegal-move claim", GE_XBOARD );
7703             return; // do not take back move we tested as valid
7704         }
7705         currentMove = forwardMostMove-1;
7706         DisplayMove(currentMove-1); /* before DisplayMoveError */
7707         SwitchClocks(forwardMostMove-1); // [HGM] race
7708         DisplayBothClocks();
7709         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7710                 parseList[currentMove], cps->which);
7711         DisplayMoveError(buf1);
7712         DrawPosition(FALSE, boards[currentMove]);
7713         return;
7714     }
7715     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7716         /* Program has a broken "time" command that
7717            outputs a string not ending in newline.
7718            Don't use it. */
7719         cps->sendTime = 0;
7720     }
7721
7722     /*
7723      * If chess program startup fails, exit with an error message.
7724      * Attempts to recover here are futile.
7725      */
7726     if ((StrStr(message, "unknown host") != NULL)
7727         || (StrStr(message, "No remote directory") != NULL)
7728         || (StrStr(message, "not found") != NULL)
7729         || (StrStr(message, "No such file") != NULL)
7730         || (StrStr(message, "can't alloc") != NULL)
7731         || (StrStr(message, "Permission denied") != NULL)) {
7732
7733         cps->maybeThinking = FALSE;
7734         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7735                 cps->which, cps->program, cps->host, message);
7736         RemoveInputSource(cps->isr);
7737         DisplayFatalError(buf1, 0, 1);
7738         return;
7739     }
7740
7741     /*
7742      * Look for hint output
7743      */
7744     if (sscanf(message, "Hint: %s", buf1) == 1) {
7745         if (cps == &first && hintRequested) {
7746             hintRequested = FALSE;
7747             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7748                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7749                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7750                                     PosFlags(forwardMostMove),
7751                                     fromY, fromX, toY, toX, promoChar, buf1);
7752                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7753                 DisplayInformation(buf2);
7754             } else {
7755                 /* Hint move could not be parsed!? */
7756               snprintf(buf2, sizeof(buf2),
7757                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7758                         buf1, cps->which);
7759                 DisplayError(buf2, 0);
7760             }
7761         } else {
7762           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7763         }
7764         return;
7765     }
7766
7767     /*
7768      * Ignore other messages if game is not in progress
7769      */
7770     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7771         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7772
7773     /*
7774      * look for win, lose, draw, or draw offer
7775      */
7776     if (strncmp(message, "1-0", 3) == 0) {
7777         char *p, *q, *r = "";
7778         p = strchr(message, '{');
7779         if (p) {
7780             q = strchr(p, '}');
7781             if (q) {
7782                 *q = NULLCHAR;
7783                 r = p + 1;
7784             }
7785         }
7786         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7787         return;
7788     } else if (strncmp(message, "0-1", 3) == 0) {
7789         char *p, *q, *r = "";
7790         p = strchr(message, '{');
7791         if (p) {
7792             q = strchr(p, '}');
7793             if (q) {
7794                 *q = NULLCHAR;
7795                 r = p + 1;
7796             }
7797         }
7798         /* Kludge for Arasan 4.1 bug */
7799         if (strcmp(r, "Black resigns") == 0) {
7800             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7801             return;
7802         }
7803         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7804         return;
7805     } else if (strncmp(message, "1/2", 3) == 0) {
7806         char *p, *q, *r = "";
7807         p = strchr(message, '{');
7808         if (p) {
7809             q = strchr(p, '}');
7810             if (q) {
7811                 *q = NULLCHAR;
7812                 r = p + 1;
7813             }
7814         }
7815
7816         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7817         return;
7818
7819     } else if (strncmp(message, "White resign", 12) == 0) {
7820         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7821         return;
7822     } else if (strncmp(message, "Black resign", 12) == 0) {
7823         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7824         return;
7825     } else if (strncmp(message, "White matches", 13) == 0 ||
7826                strncmp(message, "Black matches", 13) == 0   ) {
7827         /* [HGM] ignore GNUShogi noises */
7828         return;
7829     } else if (strncmp(message, "White", 5) == 0 &&
7830                message[5] != '(' &&
7831                StrStr(message, "Black") == NULL) {
7832         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7833         return;
7834     } else if (strncmp(message, "Black", 5) == 0 &&
7835                message[5] != '(') {
7836         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7837         return;
7838     } else if (strcmp(message, "resign") == 0 ||
7839                strcmp(message, "computer resigns") == 0) {
7840         switch (gameMode) {
7841           case MachinePlaysBlack:
7842           case IcsPlayingBlack:
7843             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7844             break;
7845           case MachinePlaysWhite:
7846           case IcsPlayingWhite:
7847             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7848             break;
7849           case TwoMachinesPlay:
7850             if (cps->twoMachinesColor[0] == 'w')
7851               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7852             else
7853               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7854             break;
7855           default:
7856             /* can't happen */
7857             break;
7858         }
7859         return;
7860     } else if (strncmp(message, "opponent mates", 14) == 0) {
7861         switch (gameMode) {
7862           case MachinePlaysBlack:
7863           case IcsPlayingBlack:
7864             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7865             break;
7866           case MachinePlaysWhite:
7867           case IcsPlayingWhite:
7868             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7869             break;
7870           case TwoMachinesPlay:
7871             if (cps->twoMachinesColor[0] == 'w')
7872               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7873             else
7874               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7875             break;
7876           default:
7877             /* can't happen */
7878             break;
7879         }
7880         return;
7881     } else if (strncmp(message, "computer mates", 14) == 0) {
7882         switch (gameMode) {
7883           case MachinePlaysBlack:
7884           case IcsPlayingBlack:
7885             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7886             break;
7887           case MachinePlaysWhite:
7888           case IcsPlayingWhite:
7889             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7890             break;
7891           case TwoMachinesPlay:
7892             if (cps->twoMachinesColor[0] == 'w')
7893               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7894             else
7895               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7896             break;
7897           default:
7898             /* can't happen */
7899             break;
7900         }
7901         return;
7902     } else if (strncmp(message, "checkmate", 9) == 0) {
7903         if (WhiteOnMove(forwardMostMove)) {
7904             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7905         } else {
7906             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7907         }
7908         return;
7909     } else if (strstr(message, "Draw") != NULL ||
7910                strstr(message, "game is a draw") != NULL) {
7911         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7912         return;
7913     } else if (strstr(message, "offer") != NULL &&
7914                strstr(message, "draw") != NULL) {
7915 #if ZIPPY
7916         if (appData.zippyPlay && first.initDone) {
7917             /* Relay offer to ICS */
7918             SendToICS(ics_prefix);
7919             SendToICS("draw\n");
7920         }
7921 #endif
7922         cps->offeredDraw = 2; /* valid until this engine moves twice */
7923         if (gameMode == TwoMachinesPlay) {
7924             if (cps->other->offeredDraw) {
7925                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7926             /* [HGM] in two-machine mode we delay relaying draw offer      */
7927             /* until after we also have move, to see if it is really claim */
7928             }
7929         } else if (gameMode == MachinePlaysWhite ||
7930                    gameMode == MachinePlaysBlack) {
7931           if (userOfferedDraw) {
7932             DisplayInformation(_("Machine accepts your draw offer"));
7933             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7934           } else {
7935             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7936           }
7937         }
7938     }
7939
7940
7941     /*
7942      * Look for thinking output
7943      */
7944     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7945           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7946                                 ) {
7947         int plylev, mvleft, mvtot, curscore, time;
7948         char mvname[MOVE_LEN];
7949         u64 nodes; // [DM]
7950         char plyext;
7951         int ignore = FALSE;
7952         int prefixHint = FALSE;
7953         mvname[0] = NULLCHAR;
7954
7955         switch (gameMode) {
7956           case MachinePlaysBlack:
7957           case IcsPlayingBlack:
7958             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7959             break;
7960           case MachinePlaysWhite:
7961           case IcsPlayingWhite:
7962             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7963             break;
7964           case AnalyzeMode:
7965           case AnalyzeFile:
7966             break;
7967           case IcsObserving: /* [DM] icsEngineAnalyze */
7968             if (!appData.icsEngineAnalyze) ignore = TRUE;
7969             break;
7970           case TwoMachinesPlay:
7971             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7972                 ignore = TRUE;
7973             }
7974             break;
7975           default:
7976             ignore = TRUE;
7977             break;
7978         }
7979
7980         if (!ignore) {
7981             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7982             buf1[0] = NULLCHAR;
7983             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7984                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7985
7986                 if (plyext != ' ' && plyext != '\t') {
7987                     time *= 100;
7988                 }
7989
7990                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7991                 if( cps->scoreIsAbsolute &&
7992                     ( gameMode == MachinePlaysBlack ||
7993                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7994                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7995                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7996                      !WhiteOnMove(currentMove)
7997                     ) )
7998                 {
7999                     curscore = -curscore;
8000                 }
8001
8002
8003                 tempStats.depth = plylev;
8004                 tempStats.nodes = nodes;
8005                 tempStats.time = time;
8006                 tempStats.score = curscore;
8007                 tempStats.got_only_move = 0;
8008
8009                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8010                         int ticklen;
8011
8012                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8013                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8014                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8015                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8016                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8017                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8018                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8019                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8020                 }
8021
8022                 /* Buffer overflow protection */
8023                 if (buf1[0] != NULLCHAR) {
8024                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8025                         && appData.debugMode) {
8026                         fprintf(debugFP,
8027                                 "PV is too long; using the first %u bytes.\n",
8028                                 (unsigned) sizeof(tempStats.movelist) - 1);
8029                     }
8030
8031                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8032                 } else {
8033                     sprintf(tempStats.movelist, " no PV\n");
8034                 }
8035
8036                 if (tempStats.seen_stat) {
8037                     tempStats.ok_to_send = 1;
8038                 }
8039
8040                 if (strchr(tempStats.movelist, '(') != NULL) {
8041                     tempStats.line_is_book = 1;
8042                     tempStats.nr_moves = 0;
8043                     tempStats.moves_left = 0;
8044                 } else {
8045                     tempStats.line_is_book = 0;
8046                 }
8047
8048                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8049                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8050
8051                 SendProgramStatsToFrontend( cps, &tempStats );
8052
8053                 /*
8054                     [AS] Protect the thinkOutput buffer from overflow... this
8055                     is only useful if buf1 hasn't overflowed first!
8056                 */
8057                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8058                          plylev,
8059                          (gameMode == TwoMachinesPlay ?
8060                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8061                          ((double) curscore) / 100.0,
8062                          prefixHint ? lastHint : "",
8063                          prefixHint ? " " : "" );
8064
8065                 if( buf1[0] != NULLCHAR ) {
8066                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8067
8068                     if( strlen(buf1) > max_len ) {
8069                         if( appData.debugMode) {
8070                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8071                         }
8072                         buf1[max_len+1] = '\0';
8073                     }
8074
8075                     strcat( thinkOutput, buf1 );
8076                 }
8077
8078                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8079                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8080                     DisplayMove(currentMove - 1);
8081                 }
8082                 return;
8083
8084             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8085                 /* crafty (9.25+) says "(only move) <move>"
8086                  * if there is only 1 legal move
8087                  */
8088                 sscanf(p, "(only move) %s", buf1);
8089                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8090                 sprintf(programStats.movelist, "%s (only move)", buf1);
8091                 programStats.depth = 1;
8092                 programStats.nr_moves = 1;
8093                 programStats.moves_left = 1;
8094                 programStats.nodes = 1;
8095                 programStats.time = 1;
8096                 programStats.got_only_move = 1;
8097
8098                 /* Not really, but we also use this member to
8099                    mean "line isn't going to change" (Crafty
8100                    isn't searching, so stats won't change) */
8101                 programStats.line_is_book = 1;
8102
8103                 SendProgramStatsToFrontend( cps, &programStats );
8104
8105                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8106                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8107                     DisplayMove(currentMove - 1);
8108                 }
8109                 return;
8110             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8111                               &time, &nodes, &plylev, &mvleft,
8112                               &mvtot, mvname) >= 5) {
8113                 /* The stat01: line is from Crafty (9.29+) in response
8114                    to the "." command */
8115                 programStats.seen_stat = 1;
8116                 cps->maybeThinking = TRUE;
8117
8118                 if (programStats.got_only_move || !appData.periodicUpdates)
8119                   return;
8120
8121                 programStats.depth = plylev;
8122                 programStats.time = time;
8123                 programStats.nodes = nodes;
8124                 programStats.moves_left = mvleft;
8125                 programStats.nr_moves = mvtot;
8126                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8127                 programStats.ok_to_send = 1;
8128                 programStats.movelist[0] = '\0';
8129
8130                 SendProgramStatsToFrontend( cps, &programStats );
8131
8132                 return;
8133
8134             } else if (strncmp(message,"++",2) == 0) {
8135                 /* Crafty 9.29+ outputs this */
8136                 programStats.got_fail = 2;
8137                 return;
8138
8139             } else if (strncmp(message,"--",2) == 0) {
8140                 /* Crafty 9.29+ outputs this */
8141                 programStats.got_fail = 1;
8142                 return;
8143
8144             } else if (thinkOutput[0] != NULLCHAR &&
8145                        strncmp(message, "    ", 4) == 0) {
8146                 unsigned message_len;
8147
8148                 p = message;
8149                 while (*p && *p == ' ') p++;
8150
8151                 message_len = strlen( p );
8152
8153                 /* [AS] Avoid buffer overflow */
8154                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8155                     strcat(thinkOutput, " ");
8156                     strcat(thinkOutput, p);
8157                 }
8158
8159                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8160                     strcat(programStats.movelist, " ");
8161                     strcat(programStats.movelist, p);
8162                 }
8163
8164                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8165                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8166                     DisplayMove(currentMove - 1);
8167                 }
8168                 return;
8169             }
8170         }
8171         else {
8172             buf1[0] = NULLCHAR;
8173
8174             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8175                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8176             {
8177                 ChessProgramStats cpstats;
8178
8179                 if (plyext != ' ' && plyext != '\t') {
8180                     time *= 100;
8181                 }
8182
8183                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8184                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8185                     curscore = -curscore;
8186                 }
8187
8188                 cpstats.depth = plylev;
8189                 cpstats.nodes = nodes;
8190                 cpstats.time = time;
8191                 cpstats.score = curscore;
8192                 cpstats.got_only_move = 0;
8193                 cpstats.movelist[0] = '\0';
8194
8195                 if (buf1[0] != NULLCHAR) {
8196                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8197                 }
8198
8199                 cpstats.ok_to_send = 0;
8200                 cpstats.line_is_book = 0;
8201                 cpstats.nr_moves = 0;
8202                 cpstats.moves_left = 0;
8203
8204                 SendProgramStatsToFrontend( cps, &cpstats );
8205             }
8206         }
8207     }
8208 }
8209
8210
8211 /* Parse a game score from the character string "game", and
8212    record it as the history of the current game.  The game
8213    score is NOT assumed to start from the standard position.
8214    The display is not updated in any way.
8215    */
8216 void
8217 ParseGameHistory(game)
8218      char *game;
8219 {
8220     ChessMove moveType;
8221     int fromX, fromY, toX, toY, boardIndex;
8222     char promoChar;
8223     char *p, *q;
8224     char buf[MSG_SIZ];
8225
8226     if (appData.debugMode)
8227       fprintf(debugFP, "Parsing game history: %s\n", game);
8228
8229     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8230     gameInfo.site = StrSave(appData.icsHost);
8231     gameInfo.date = PGNDate();
8232     gameInfo.round = StrSave("-");
8233
8234     /* Parse out names of players */
8235     while (*game == ' ') game++;
8236     p = buf;
8237     while (*game != ' ') *p++ = *game++;
8238     *p = NULLCHAR;
8239     gameInfo.white = StrSave(buf);
8240     while (*game == ' ') game++;
8241     p = buf;
8242     while (*game != ' ' && *game != '\n') *p++ = *game++;
8243     *p = NULLCHAR;
8244     gameInfo.black = StrSave(buf);
8245
8246     /* Parse moves */
8247     boardIndex = blackPlaysFirst ? 1 : 0;
8248     yynewstr(game);
8249     for (;;) {
8250         yyboardindex = boardIndex;
8251         moveType = (ChessMove) Myylex();
8252         switch (moveType) {
8253           case IllegalMove:             /* maybe suicide chess, etc. */
8254   if (appData.debugMode) {
8255     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8256     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8257     setbuf(debugFP, NULL);
8258   }
8259           case WhitePromotion:
8260           case BlackPromotion:
8261           case WhiteNonPromotion:
8262           case BlackNonPromotion:
8263           case NormalMove:
8264           case WhiteCapturesEnPassant:
8265           case BlackCapturesEnPassant:
8266           case WhiteKingSideCastle:
8267           case WhiteQueenSideCastle:
8268           case BlackKingSideCastle:
8269           case BlackQueenSideCastle:
8270           case WhiteKingSideCastleWild:
8271           case WhiteQueenSideCastleWild:
8272           case BlackKingSideCastleWild:
8273           case BlackQueenSideCastleWild:
8274           /* PUSH Fabien */
8275           case WhiteHSideCastleFR:
8276           case WhiteASideCastleFR:
8277           case BlackHSideCastleFR:
8278           case BlackASideCastleFR:
8279           /* POP Fabien */
8280             fromX = currentMoveString[0] - AAA;
8281             fromY = currentMoveString[1] - ONE;
8282             toX = currentMoveString[2] - AAA;
8283             toY = currentMoveString[3] - ONE;
8284             promoChar = currentMoveString[4];
8285             break;
8286           case WhiteDrop:
8287           case BlackDrop:
8288             fromX = moveType == WhiteDrop ?
8289               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8290             (int) CharToPiece(ToLower(currentMoveString[0]));
8291             fromY = DROP_RANK;
8292             toX = currentMoveString[2] - AAA;
8293             toY = currentMoveString[3] - ONE;
8294             promoChar = NULLCHAR;
8295             break;
8296           case AmbiguousMove:
8297             /* bug? */
8298             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8299   if (appData.debugMode) {
8300     fprintf(debugFP, "Ambiguous 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 ImpossibleMove:
8307             /* bug? */
8308             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8309   if (appData.debugMode) {
8310     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8311     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8312     setbuf(debugFP, NULL);
8313   }
8314             DisplayError(buf, 0);
8315             return;
8316           case EndOfFile:
8317             if (boardIndex < backwardMostMove) {
8318                 /* Oops, gap.  How did that happen? */
8319                 DisplayError(_("Gap in move list"), 0);
8320                 return;
8321             }
8322             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8323             if (boardIndex > forwardMostMove) {
8324                 forwardMostMove = boardIndex;
8325             }
8326             return;
8327           case ElapsedTime:
8328             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8329                 strcat(parseList[boardIndex-1], " ");
8330                 strcat(parseList[boardIndex-1], yy_text);
8331             }
8332             continue;
8333           case Comment:
8334           case PGNTag:
8335           case NAG:
8336           default:
8337             /* ignore */
8338             continue;
8339           case WhiteWins:
8340           case BlackWins:
8341           case GameIsDrawn:
8342           case GameUnfinished:
8343             if (gameMode == IcsExamining) {
8344                 if (boardIndex < backwardMostMove) {
8345                     /* Oops, gap.  How did that happen? */
8346                     return;
8347                 }
8348                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8349                 return;
8350             }
8351             gameInfo.result = moveType;
8352             p = strchr(yy_text, '{');
8353             if (p == NULL) p = strchr(yy_text, '(');
8354             if (p == NULL) {
8355                 p = yy_text;
8356                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8357             } else {
8358                 q = strchr(p, *p == '{' ? '}' : ')');
8359                 if (q != NULL) *q = NULLCHAR;
8360                 p++;
8361             }
8362             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8363             gameInfo.resultDetails = StrSave(p);
8364             continue;
8365         }
8366         if (boardIndex >= forwardMostMove &&
8367             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8368             backwardMostMove = blackPlaysFirst ? 1 : 0;
8369             return;
8370         }
8371         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8372                                  fromY, fromX, toY, toX, promoChar,
8373                                  parseList[boardIndex]);
8374         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8375         /* currentMoveString is set as a side-effect of yylex */
8376         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8377         strcat(moveList[boardIndex], "\n");
8378         boardIndex++;
8379         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8380         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8381           case MT_NONE:
8382           case MT_STALEMATE:
8383           default:
8384             break;
8385           case MT_CHECK:
8386             if(gameInfo.variant != VariantShogi)
8387                 strcat(parseList[boardIndex - 1], "+");
8388             break;
8389           case MT_CHECKMATE:
8390           case MT_STAINMATE:
8391             strcat(parseList[boardIndex - 1], "#");
8392             break;
8393         }
8394     }
8395 }
8396
8397
8398 /* Apply a move to the given board  */
8399 void
8400 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8401      int fromX, fromY, toX, toY;
8402      int promoChar;
8403      Board board;
8404 {
8405   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8406   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8407
8408     /* [HGM] compute & store e.p. status and castling rights for new position */
8409     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8410
8411       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8412       oldEP = (signed char)board[EP_STATUS];
8413       board[EP_STATUS] = EP_NONE;
8414
8415       if( board[toY][toX] != EmptySquare )
8416            board[EP_STATUS] = EP_CAPTURE;
8417
8418   if (fromY == DROP_RANK) {
8419         /* must be first */
8420         piece = board[toY][toX] = (ChessSquare) fromX;
8421   } else {
8422       int i;
8423
8424       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8425            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8426                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8427       } else
8428       if( board[fromY][fromX] == WhitePawn ) {
8429            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8430                board[EP_STATUS] = EP_PAWN_MOVE;
8431            if( toY-fromY==2) {
8432                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8433                         gameInfo.variant != VariantBerolina || toX < fromX)
8434                       board[EP_STATUS] = toX | berolina;
8435                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8436                         gameInfo.variant != VariantBerolina || toX > fromX)
8437                       board[EP_STATUS] = toX;
8438            }
8439       } else
8440       if( board[fromY][fromX] == BlackPawn ) {
8441            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8442                board[EP_STATUS] = EP_PAWN_MOVE;
8443            if( toY-fromY== -2) {
8444                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8445                         gameInfo.variant != VariantBerolina || toX < fromX)
8446                       board[EP_STATUS] = toX | berolina;
8447                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8448                         gameInfo.variant != VariantBerolina || toX > fromX)
8449                       board[EP_STATUS] = toX;
8450            }
8451        }
8452
8453        for(i=0; i<nrCastlingRights; i++) {
8454            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8455               board[CASTLING][i] == toX   && castlingRank[i] == toY
8456              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8457        }
8458
8459      if (fromX == toX && fromY == toY) return;
8460
8461      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8462      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8463      if(gameInfo.variant == VariantKnightmate)
8464          king += (int) WhiteUnicorn - (int) WhiteKing;
8465
8466     /* Code added by Tord: */
8467     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8468     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8469         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8470       board[fromY][fromX] = EmptySquare;
8471       board[toY][toX] = EmptySquare;
8472       if((toX > fromX) != (piece == WhiteRook)) {
8473         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8474       } else {
8475         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8476       }
8477     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8478                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8479       board[fromY][fromX] = EmptySquare;
8480       board[toY][toX] = EmptySquare;
8481       if((toX > fromX) != (piece == BlackRook)) {
8482         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8483       } else {
8484         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8485       }
8486     /* End of code added by Tord */
8487
8488     } else if (board[fromY][fromX] == king
8489         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8490         && toY == fromY && toX > fromX+1) {
8491         board[fromY][fromX] = EmptySquare;
8492         board[toY][toX] = king;
8493         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8494         board[fromY][BOARD_RGHT-1] = EmptySquare;
8495     } else if (board[fromY][fromX] == king
8496         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8497                && toY == fromY && toX < fromX-1) {
8498         board[fromY][fromX] = EmptySquare;
8499         board[toY][toX] = king;
8500         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8501         board[fromY][BOARD_LEFT] = EmptySquare;
8502     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8503                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8504                && toY >= BOARD_HEIGHT-promoRank
8505                ) {
8506         /* white pawn promotion */
8507         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8508         if (board[toY][toX] == EmptySquare) {
8509             board[toY][toX] = WhiteQueen;
8510         }
8511         if(gameInfo.variant==VariantBughouse ||
8512            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8513             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8514         board[fromY][fromX] = EmptySquare;
8515     } else if ((fromY == BOARD_HEIGHT-4)
8516                && (toX != fromX)
8517                && gameInfo.variant != VariantXiangqi
8518                && gameInfo.variant != VariantBerolina
8519                && (board[fromY][fromX] == WhitePawn)
8520                && (board[toY][toX] == EmptySquare)) {
8521         board[fromY][fromX] = EmptySquare;
8522         board[toY][toX] = WhitePawn;
8523         captured = board[toY - 1][toX];
8524         board[toY - 1][toX] = EmptySquare;
8525     } else if ((fromY == BOARD_HEIGHT-4)
8526                && (toX == fromX)
8527                && gameInfo.variant == VariantBerolina
8528                && (board[fromY][fromX] == WhitePawn)
8529                && (board[toY][toX] == EmptySquare)) {
8530         board[fromY][fromX] = EmptySquare;
8531         board[toY][toX] = WhitePawn;
8532         if(oldEP & EP_BEROLIN_A) {
8533                 captured = board[fromY][fromX-1];
8534                 board[fromY][fromX-1] = EmptySquare;
8535         }else{  captured = board[fromY][fromX+1];
8536                 board[fromY][fromX+1] = EmptySquare;
8537         }
8538     } else if (board[fromY][fromX] == king
8539         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8540                && toY == fromY && toX > fromX+1) {
8541         board[fromY][fromX] = EmptySquare;
8542         board[toY][toX] = king;
8543         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8544         board[fromY][BOARD_RGHT-1] = EmptySquare;
8545     } else if (board[fromY][fromX] == king
8546         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8547                && toY == fromY && toX < fromX-1) {
8548         board[fromY][fromX] = EmptySquare;
8549         board[toY][toX] = king;
8550         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8551         board[fromY][BOARD_LEFT] = EmptySquare;
8552     } else if (fromY == 7 && fromX == 3
8553                && board[fromY][fromX] == BlackKing
8554                && toY == 7 && toX == 5) {
8555         board[fromY][fromX] = EmptySquare;
8556         board[toY][toX] = BlackKing;
8557         board[fromY][7] = EmptySquare;
8558         board[toY][4] = BlackRook;
8559     } else if (fromY == 7 && fromX == 3
8560                && board[fromY][fromX] == BlackKing
8561                && toY == 7 && toX == 1) {
8562         board[fromY][fromX] = EmptySquare;
8563         board[toY][toX] = BlackKing;
8564         board[fromY][0] = EmptySquare;
8565         board[toY][2] = BlackRook;
8566     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8567                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8568                && toY < promoRank
8569                ) {
8570         /* black pawn promotion */
8571         board[toY][toX] = CharToPiece(ToLower(promoChar));
8572         if (board[toY][toX] == EmptySquare) {
8573             board[toY][toX] = BlackQueen;
8574         }
8575         if(gameInfo.variant==VariantBughouse ||
8576            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8577             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8578         board[fromY][fromX] = EmptySquare;
8579     } else if ((fromY == 3)
8580                && (toX != fromX)
8581                && gameInfo.variant != VariantXiangqi
8582                && gameInfo.variant != VariantBerolina
8583                && (board[fromY][fromX] == BlackPawn)
8584                && (board[toY][toX] == EmptySquare)) {
8585         board[fromY][fromX] = EmptySquare;
8586         board[toY][toX] = BlackPawn;
8587         captured = board[toY + 1][toX];
8588         board[toY + 1][toX] = EmptySquare;
8589     } else if ((fromY == 3)
8590                && (toX == fromX)
8591                && gameInfo.variant == VariantBerolina
8592                && (board[fromY][fromX] == BlackPawn)
8593                && (board[toY][toX] == EmptySquare)) {
8594         board[fromY][fromX] = EmptySquare;
8595         board[toY][toX] = BlackPawn;
8596         if(oldEP & EP_BEROLIN_A) {
8597                 captured = board[fromY][fromX-1];
8598                 board[fromY][fromX-1] = EmptySquare;
8599         }else{  captured = board[fromY][fromX+1];
8600                 board[fromY][fromX+1] = EmptySquare;
8601         }
8602     } else {
8603         board[toY][toX] = board[fromY][fromX];
8604         board[fromY][fromX] = EmptySquare;
8605     }
8606   }
8607
8608     if (gameInfo.holdingsWidth != 0) {
8609
8610       /* !!A lot more code needs to be written to support holdings  */
8611       /* [HGM] OK, so I have written it. Holdings are stored in the */
8612       /* penultimate board files, so they are automaticlly stored   */
8613       /* in the game history.                                       */
8614       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8615                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8616         /* Delete from holdings, by decreasing count */
8617         /* and erasing image if necessary            */
8618         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8619         if(p < (int) BlackPawn) { /* white drop */
8620              p -= (int)WhitePawn;
8621                  p = PieceToNumber((ChessSquare)p);
8622              if(p >= gameInfo.holdingsSize) p = 0;
8623              if(--board[p][BOARD_WIDTH-2] <= 0)
8624                   board[p][BOARD_WIDTH-1] = EmptySquare;
8625              if((int)board[p][BOARD_WIDTH-2] < 0)
8626                         board[p][BOARD_WIDTH-2] = 0;
8627         } else {                  /* black drop */
8628              p -= (int)BlackPawn;
8629                  p = PieceToNumber((ChessSquare)p);
8630              if(p >= gameInfo.holdingsSize) p = 0;
8631              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8632                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8633              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8634                         board[BOARD_HEIGHT-1-p][1] = 0;
8635         }
8636       }
8637       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8638           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8639         /* [HGM] holdings: Add to holdings, if holdings exist */
8640         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8641                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8642                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8643         }
8644         p = (int) captured;
8645         if (p >= (int) BlackPawn) {
8646           p -= (int)BlackPawn;
8647           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8648                   /* in Shogi restore piece to its original  first */
8649                   captured = (ChessSquare) (DEMOTED captured);
8650                   p = DEMOTED p;
8651           }
8652           p = PieceToNumber((ChessSquare)p);
8653           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8654           board[p][BOARD_WIDTH-2]++;
8655           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8656         } else {
8657           p -= (int)WhitePawn;
8658           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8659                   captured = (ChessSquare) (DEMOTED captured);
8660                   p = DEMOTED p;
8661           }
8662           p = PieceToNumber((ChessSquare)p);
8663           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8664           board[BOARD_HEIGHT-1-p][1]++;
8665           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8666         }
8667       }
8668     } else if (gameInfo.variant == VariantAtomic) {
8669       if (captured != EmptySquare) {
8670         int y, x;
8671         for (y = toY-1; y <= toY+1; y++) {
8672           for (x = toX-1; x <= toX+1; x++) {
8673             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8674                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8675               board[y][x] = EmptySquare;
8676             }
8677           }
8678         }
8679         board[toY][toX] = EmptySquare;
8680       }
8681     }
8682     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8683         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8684     } else
8685     if(promoChar == '+') {
8686         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8687         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8688     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8689         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8690     }
8691     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8692                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8693         // [HGM] superchess: take promotion piece out of holdings
8694         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8695         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8696             if(!--board[k][BOARD_WIDTH-2])
8697                 board[k][BOARD_WIDTH-1] = EmptySquare;
8698         } else {
8699             if(!--board[BOARD_HEIGHT-1-k][1])
8700                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8701         }
8702     }
8703
8704 }
8705
8706 /* Updates forwardMostMove */
8707 void
8708 MakeMove(fromX, fromY, toX, toY, promoChar)
8709      int fromX, fromY, toX, toY;
8710      int promoChar;
8711 {
8712 //    forwardMostMove++; // [HGM] bare: moved downstream
8713
8714     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8715         int timeLeft; static int lastLoadFlag=0; int king, piece;
8716         piece = boards[forwardMostMove][fromY][fromX];
8717         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8718         if(gameInfo.variant == VariantKnightmate)
8719             king += (int) WhiteUnicorn - (int) WhiteKing;
8720         if(forwardMostMove == 0) {
8721             if(blackPlaysFirst)
8722                 fprintf(serverMoves, "%s;", second.tidy);
8723             fprintf(serverMoves, "%s;", first.tidy);
8724             if(!blackPlaysFirst)
8725                 fprintf(serverMoves, "%s;", second.tidy);
8726         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8727         lastLoadFlag = loadFlag;
8728         // print base move
8729         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8730         // print castling suffix
8731         if( toY == fromY && piece == king ) {
8732             if(toX-fromX > 1)
8733                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8734             if(fromX-toX >1)
8735                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8736         }
8737         // e.p. suffix
8738         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8739              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8740              boards[forwardMostMove][toY][toX] == EmptySquare
8741              && fromX != toX && fromY != toY)
8742                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8743         // promotion suffix
8744         if(promoChar != NULLCHAR)
8745                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8746         if(!loadFlag) {
8747             fprintf(serverMoves, "/%d/%d",
8748                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8749             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8750             else                      timeLeft = blackTimeRemaining/1000;
8751             fprintf(serverMoves, "/%d", timeLeft);
8752         }
8753         fflush(serverMoves);
8754     }
8755
8756     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8757       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8758                         0, 1);
8759       return;
8760     }
8761     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8762     if (commentList[forwardMostMove+1] != NULL) {
8763         free(commentList[forwardMostMove+1]);
8764         commentList[forwardMostMove+1] = NULL;
8765     }
8766     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8767     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8768     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8769     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8770     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8771     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8772     gameInfo.result = GameUnfinished;
8773     if (gameInfo.resultDetails != NULL) {
8774         free(gameInfo.resultDetails);
8775         gameInfo.resultDetails = NULL;
8776     }
8777     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8778                               moveList[forwardMostMove - 1]);
8779     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8780                              PosFlags(forwardMostMove - 1),
8781                              fromY, fromX, toY, toX, promoChar,
8782                              parseList[forwardMostMove - 1]);
8783     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8784       case MT_NONE:
8785       case MT_STALEMATE:
8786       default:
8787         break;
8788       case MT_CHECK:
8789         if(gameInfo.variant != VariantShogi)
8790             strcat(parseList[forwardMostMove - 1], "+");
8791         break;
8792       case MT_CHECKMATE:
8793       case MT_STAINMATE:
8794         strcat(parseList[forwardMostMove - 1], "#");
8795         break;
8796     }
8797     if (appData.debugMode) {
8798         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8799     }
8800
8801 }
8802
8803 /* Updates currentMove if not pausing */
8804 void
8805 ShowMove(fromX, fromY, toX, toY)
8806 {
8807     int instant = (gameMode == PlayFromGameFile) ?
8808         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8809     if(appData.noGUI) return;
8810     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8811         if (!instant) {
8812             if (forwardMostMove == currentMove + 1) {
8813                 AnimateMove(boards[forwardMostMove - 1],
8814                             fromX, fromY, toX, toY);
8815             }
8816             if (appData.highlightLastMove) {
8817                 SetHighlights(fromX, fromY, toX, toY);
8818             }
8819         }
8820         currentMove = forwardMostMove;
8821     }
8822
8823     if (instant) return;
8824
8825     DisplayMove(currentMove - 1);
8826     DrawPosition(FALSE, boards[currentMove]);
8827     DisplayBothClocks();
8828     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8829 }
8830
8831 void SendEgtPath(ChessProgramState *cps)
8832 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8833         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8834
8835         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8836
8837         while(*p) {
8838             char c, *q = name+1, *r, *s;
8839
8840             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8841             while(*p && *p != ',') *q++ = *p++;
8842             *q++ = ':'; *q = 0;
8843             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8844                 strcmp(name, ",nalimov:") == 0 ) {
8845                 // take nalimov path from the menu-changeable option first, if it is defined
8846               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8847                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8848             } else
8849             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8850                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8851                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8852                 s = r = StrStr(s, ":") + 1; // beginning of path info
8853                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8854                 c = *r; *r = 0;             // temporarily null-terminate path info
8855                     *--q = 0;               // strip of trailig ':' from name
8856                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8857                 *r = c;
8858                 SendToProgram(buf,cps);     // send egtbpath command for this format
8859             }
8860             if(*p == ',') p++; // read away comma to position for next format name
8861         }
8862 }
8863
8864 void
8865 InitChessProgram(cps, setup)
8866      ChessProgramState *cps;
8867      int setup; /* [HGM] needed to setup FRC opening position */
8868 {
8869     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8870     if (appData.noChessProgram) return;
8871     hintRequested = FALSE;
8872     bookRequested = FALSE;
8873
8874     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8875     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8876     if(cps->memSize) { /* [HGM] memory */
8877       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8878         SendToProgram(buf, cps);
8879     }
8880     SendEgtPath(cps); /* [HGM] EGT */
8881     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8882       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8883         SendToProgram(buf, cps);
8884     }
8885
8886     SendToProgram(cps->initString, cps);
8887     if (gameInfo.variant != VariantNormal &&
8888         gameInfo.variant != VariantLoadable
8889         /* [HGM] also send variant if board size non-standard */
8890         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8891                                             ) {
8892       char *v = VariantName(gameInfo.variant);
8893       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8894         /* [HGM] in protocol 1 we have to assume all variants valid */
8895         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8896         DisplayFatalError(buf, 0, 1);
8897         return;
8898       }
8899
8900       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8901       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8902       if( gameInfo.variant == VariantXiangqi )
8903            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8904       if( gameInfo.variant == VariantShogi )
8905            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8906       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8907            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8908       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8909                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8910            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8911       if( gameInfo.variant == VariantCourier )
8912            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8913       if( gameInfo.variant == VariantSuper )
8914            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8915       if( gameInfo.variant == VariantGreat )
8916            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8917       if( gameInfo.variant == VariantSChess )
8918            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8919
8920       if(overruled) {
8921         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8922                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8923            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8924            if(StrStr(cps->variants, b) == NULL) {
8925                // specific sized variant not known, check if general sizing allowed
8926                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8927                    if(StrStr(cps->variants, "boardsize") == NULL) {
8928                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8929                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8930                        DisplayFatalError(buf, 0, 1);
8931                        return;
8932                    }
8933                    /* [HGM] here we really should compare with the maximum supported board size */
8934                }
8935            }
8936       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8937       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8938       SendToProgram(buf, cps);
8939     }
8940     currentlyInitializedVariant = gameInfo.variant;
8941
8942     /* [HGM] send opening position in FRC to first engine */
8943     if(setup) {
8944           SendToProgram("force\n", cps);
8945           SendBoard(cps, 0);
8946           /* engine is now in force mode! Set flag to wake it up after first move. */
8947           setboardSpoiledMachineBlack = 1;
8948     }
8949
8950     if (cps->sendICS) {
8951       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8952       SendToProgram(buf, cps);
8953     }
8954     cps->maybeThinking = FALSE;
8955     cps->offeredDraw = 0;
8956     if (!appData.icsActive) {
8957         SendTimeControl(cps, movesPerSession, timeControl,
8958                         timeIncrement, appData.searchDepth,
8959                         searchTime);
8960     }
8961     if (appData.showThinking
8962         // [HGM] thinking: four options require thinking output to be sent
8963         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8964                                 ) {
8965         SendToProgram("post\n", cps);
8966     }
8967     SendToProgram("hard\n", cps);
8968     if (!appData.ponderNextMove) {
8969         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8970            it without being sure what state we are in first.  "hard"
8971            is not a toggle, so that one is OK.
8972          */
8973         SendToProgram("easy\n", cps);
8974     }
8975     if (cps->usePing) {
8976       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8977       SendToProgram(buf, cps);
8978     }
8979     cps->initDone = TRUE;
8980 }
8981
8982
8983 void
8984 StartChessProgram(cps)
8985      ChessProgramState *cps;
8986 {
8987     char buf[MSG_SIZ];
8988     int err;
8989
8990     if (appData.noChessProgram) return;
8991     cps->initDone = FALSE;
8992
8993     if (strcmp(cps->host, "localhost") == 0) {
8994         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8995     } else if (*appData.remoteShell == NULLCHAR) {
8996         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8997     } else {
8998         if (*appData.remoteUser == NULLCHAR) {
8999           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9000                     cps->program);
9001         } else {
9002           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9003                     cps->host, appData.remoteUser, cps->program);
9004         }
9005         err = StartChildProcess(buf, "", &cps->pr);
9006     }
9007
9008     if (err != 0) {
9009       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9010         DisplayFatalError(buf, err, 1);
9011         cps->pr = NoProc;
9012         cps->isr = NULL;
9013         return;
9014     }
9015
9016     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9017     if (cps->protocolVersion > 1) {
9018       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9019       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9020       cps->comboCnt = 0;  //                and values of combo boxes
9021       SendToProgram(buf, cps);
9022     } else {
9023       SendToProgram("xboard\n", cps);
9024     }
9025 }
9026
9027
9028 void
9029 TwoMachinesEventIfReady P((void))
9030 {
9031   if (first.lastPing != first.lastPong) {
9032     DisplayMessage("", _("Waiting for first chess program"));
9033     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9034     return;
9035   }
9036   if (second.lastPing != second.lastPong) {
9037     DisplayMessage("", _("Waiting for second chess program"));
9038     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9039     return;
9040   }
9041   ThawUI();
9042   TwoMachinesEvent();
9043 }
9044
9045 void
9046 NextMatchGame P((void))
9047 {
9048     int index; /* [HGM] autoinc: step load index during match */
9049     Reset(FALSE, TRUE);
9050     if (*appData.loadGameFile != NULLCHAR) {
9051         index = appData.loadGameIndex;
9052         if(index < 0) { // [HGM] autoinc
9053             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9054             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9055         }
9056         LoadGameFromFile(appData.loadGameFile,
9057                          index,
9058                          appData.loadGameFile, FALSE);
9059     } else if (*appData.loadPositionFile != NULLCHAR) {
9060         index = appData.loadPositionIndex;
9061         if(index < 0) { // [HGM] autoinc
9062             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9063             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9064         }
9065         LoadPositionFromFile(appData.loadPositionFile,
9066                              index,
9067                              appData.loadPositionFile);
9068     }
9069     TwoMachinesEventIfReady();
9070 }
9071
9072 void UserAdjudicationEvent( int result )
9073 {
9074     ChessMove gameResult = GameIsDrawn;
9075
9076     if( result > 0 ) {
9077         gameResult = WhiteWins;
9078     }
9079     else if( result < 0 ) {
9080         gameResult = BlackWins;
9081     }
9082
9083     if( gameMode == TwoMachinesPlay ) {
9084         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9085     }
9086 }
9087
9088
9089 // [HGM] save: calculate checksum of game to make games easily identifiable
9090 int StringCheckSum(char *s)
9091 {
9092         int i = 0;
9093         if(s==NULL) return 0;
9094         while(*s) i = i*259 + *s++;
9095         return i;
9096 }
9097
9098 int GameCheckSum()
9099 {
9100         int i, sum=0;
9101         for(i=backwardMostMove; i<forwardMostMove; i++) {
9102                 sum += pvInfoList[i].depth;
9103                 sum += StringCheckSum(parseList[i]);
9104                 sum += StringCheckSum(commentList[i]);
9105                 sum *= 261;
9106         }
9107         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9108         return sum + StringCheckSum(commentList[i]);
9109 } // end of save patch
9110
9111 void
9112 GameEnds(result, resultDetails, whosays)
9113      ChessMove result;
9114      char *resultDetails;
9115      int whosays;
9116 {
9117     GameMode nextGameMode;
9118     int isIcsGame;
9119     char buf[MSG_SIZ], popupRequested = 0;
9120
9121     if(endingGame) return; /* [HGM] crash: forbid recursion */
9122     endingGame = 1;
9123     if(twoBoards) { // [HGM] dual: switch back to one board
9124         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9125         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9126     }
9127     if (appData.debugMode) {
9128       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9129               result, resultDetails ? resultDetails : "(null)", whosays);
9130     }
9131
9132     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9133
9134     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9135         /* If we are playing on ICS, the server decides when the
9136            game is over, but the engine can offer to draw, claim
9137            a draw, or resign.
9138          */
9139 #if ZIPPY
9140         if (appData.zippyPlay && first.initDone) {
9141             if (result == GameIsDrawn) {
9142                 /* In case draw still needs to be claimed */
9143                 SendToICS(ics_prefix);
9144                 SendToICS("draw\n");
9145             } else if (StrCaseStr(resultDetails, "resign")) {
9146                 SendToICS(ics_prefix);
9147                 SendToICS("resign\n");
9148             }
9149         }
9150 #endif
9151         endingGame = 0; /* [HGM] crash */
9152         return;
9153     }
9154
9155     /* If we're loading the game from a file, stop */
9156     if (whosays == GE_FILE) {
9157       (void) StopLoadGameTimer();
9158       gameFileFP = NULL;
9159     }
9160
9161     /* Cancel draw offers */
9162     first.offeredDraw = second.offeredDraw = 0;
9163
9164     /* If this is an ICS game, only ICS can really say it's done;
9165        if not, anyone can. */
9166     isIcsGame = (gameMode == IcsPlayingWhite ||
9167                  gameMode == IcsPlayingBlack ||
9168                  gameMode == IcsObserving    ||
9169                  gameMode == IcsExamining);
9170
9171     if (!isIcsGame || whosays == GE_ICS) {
9172         /* OK -- not an ICS game, or ICS said it was done */
9173         StopClocks();
9174         if (!isIcsGame && !appData.noChessProgram)
9175           SetUserThinkingEnables();
9176
9177         /* [HGM] if a machine claims the game end we verify this claim */
9178         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9179             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9180                 char claimer;
9181                 ChessMove trueResult = (ChessMove) -1;
9182
9183                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9184                                             first.twoMachinesColor[0] :
9185                                             second.twoMachinesColor[0] ;
9186
9187                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9188                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9189                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9190                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9191                 } else
9192                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9193                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9194                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9195                 } else
9196                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9197                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9198                 }
9199
9200                 // now verify win claims, but not in drop games, as we don't understand those yet
9201                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9202                                                  || gameInfo.variant == VariantGreat) &&
9203                     (result == WhiteWins && claimer == 'w' ||
9204                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9205                       if (appData.debugMode) {
9206                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9207                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9208                       }
9209                       if(result != trueResult) {
9210                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9211                               result = claimer == 'w' ? BlackWins : WhiteWins;
9212                               resultDetails = buf;
9213                       }
9214                 } else
9215                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9216                     && (forwardMostMove <= backwardMostMove ||
9217                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9218                         (claimer=='b')==(forwardMostMove&1))
9219                                                                                   ) {
9220                       /* [HGM] verify: draws that were not flagged are false claims */
9221                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9222                       result = claimer == 'w' ? BlackWins : WhiteWins;
9223                       resultDetails = buf;
9224                 }
9225                 /* (Claiming a loss is accepted no questions asked!) */
9226             }
9227             /* [HGM] bare: don't allow bare King to win */
9228             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9229                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9230                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9231                && result != GameIsDrawn)
9232             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9233                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9234                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9235                         if(p >= 0 && p <= (int)WhiteKing) k++;
9236                 }
9237                 if (appData.debugMode) {
9238                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9239                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9240                 }
9241                 if(k <= 1) {
9242                         result = GameIsDrawn;
9243                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9244                         resultDetails = buf;
9245                 }
9246             }
9247         }
9248
9249
9250         if(serverMoves != NULL && !loadFlag) { char c = '=';
9251             if(result==WhiteWins) c = '+';
9252             if(result==BlackWins) c = '-';
9253             if(resultDetails != NULL)
9254                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9255         }
9256         if (resultDetails != NULL) {
9257             gameInfo.result = result;
9258             gameInfo.resultDetails = StrSave(resultDetails);
9259
9260             /* display last move only if game was not loaded from file */
9261             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9262                 DisplayMove(currentMove - 1);
9263
9264             if (forwardMostMove != 0) {
9265                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9266                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9267                                                                 ) {
9268                     if (*appData.saveGameFile != NULLCHAR) {
9269                         SaveGameToFile(appData.saveGameFile, TRUE);
9270                     } else if (appData.autoSaveGames) {
9271                         AutoSaveGame();
9272                     }
9273                     if (*appData.savePositionFile != NULLCHAR) {
9274                         SavePositionToFile(appData.savePositionFile);
9275                     }
9276                 }
9277             }
9278
9279             /* Tell program how game ended in case it is learning */
9280             /* [HGM] Moved this to after saving the PGN, just in case */
9281             /* engine died and we got here through time loss. In that */
9282             /* case we will get a fatal error writing the pipe, which */
9283             /* would otherwise lose us the PGN.                       */
9284             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9285             /* output during GameEnds should never be fatal anymore   */
9286             if (gameMode == MachinePlaysWhite ||
9287                 gameMode == MachinePlaysBlack ||
9288                 gameMode == TwoMachinesPlay ||
9289                 gameMode == IcsPlayingWhite ||
9290                 gameMode == IcsPlayingBlack ||
9291                 gameMode == BeginningOfGame) {
9292                 char buf[MSG_SIZ];
9293                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9294                         resultDetails);
9295                 if (first.pr != NoProc) {
9296                     SendToProgram(buf, &first);
9297                 }
9298                 if (second.pr != NoProc &&
9299                     gameMode == TwoMachinesPlay) {
9300                     SendToProgram(buf, &second);
9301                 }
9302             }
9303         }
9304
9305         if (appData.icsActive) {
9306             if (appData.quietPlay &&
9307                 (gameMode == IcsPlayingWhite ||
9308                  gameMode == IcsPlayingBlack)) {
9309                 SendToICS(ics_prefix);
9310                 SendToICS("set shout 1\n");
9311             }
9312             nextGameMode = IcsIdle;
9313             ics_user_moved = FALSE;
9314             /* clean up premove.  It's ugly when the game has ended and the
9315              * premove highlights are still on the board.
9316              */
9317             if (gotPremove) {
9318               gotPremove = FALSE;
9319               ClearPremoveHighlights();
9320               DrawPosition(FALSE, boards[currentMove]);
9321             }
9322             if (whosays == GE_ICS) {
9323                 switch (result) {
9324                 case WhiteWins:
9325                     if (gameMode == IcsPlayingWhite)
9326                         PlayIcsWinSound();
9327                     else if(gameMode == IcsPlayingBlack)
9328                         PlayIcsLossSound();
9329                     break;
9330                 case BlackWins:
9331                     if (gameMode == IcsPlayingBlack)
9332                         PlayIcsWinSound();
9333                     else if(gameMode == IcsPlayingWhite)
9334                         PlayIcsLossSound();
9335                     break;
9336                 case GameIsDrawn:
9337                     PlayIcsDrawSound();
9338                     break;
9339                 default:
9340                     PlayIcsUnfinishedSound();
9341                 }
9342             }
9343         } else if (gameMode == EditGame ||
9344                    gameMode == PlayFromGameFile ||
9345                    gameMode == AnalyzeMode ||
9346                    gameMode == AnalyzeFile) {
9347             nextGameMode = gameMode;
9348         } else {
9349             nextGameMode = EndOfGame;
9350         }
9351         pausing = FALSE;
9352         ModeHighlight();
9353     } else {
9354         nextGameMode = gameMode;
9355     }
9356
9357     if (appData.noChessProgram) {
9358         gameMode = nextGameMode;
9359         ModeHighlight();
9360         endingGame = 0; /* [HGM] crash */
9361         return;
9362     }
9363
9364     if (first.reuse) {
9365         /* Put first chess program into idle state */
9366         if (first.pr != NoProc &&
9367             (gameMode == MachinePlaysWhite ||
9368              gameMode == MachinePlaysBlack ||
9369              gameMode == TwoMachinesPlay ||
9370              gameMode == IcsPlayingWhite ||
9371              gameMode == IcsPlayingBlack ||
9372              gameMode == BeginningOfGame)) {
9373             SendToProgram("force\n", &first);
9374             if (first.usePing) {
9375               char buf[MSG_SIZ];
9376               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9377               SendToProgram(buf, &first);
9378             }
9379         }
9380     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9381         /* Kill off first chess program */
9382         if (first.isr != NULL)
9383           RemoveInputSource(first.isr);
9384         first.isr = NULL;
9385
9386         if (first.pr != NoProc) {
9387             ExitAnalyzeMode();
9388             DoSleep( appData.delayBeforeQuit );
9389             SendToProgram("quit\n", &first);
9390             DoSleep( appData.delayAfterQuit );
9391             DestroyChildProcess(first.pr, first.useSigterm);
9392         }
9393         first.pr = NoProc;
9394     }
9395     if (second.reuse) {
9396         /* Put second chess program into idle state */
9397         if (second.pr != NoProc &&
9398             gameMode == TwoMachinesPlay) {
9399             SendToProgram("force\n", &second);
9400             if (second.usePing) {
9401               char buf[MSG_SIZ];
9402               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9403               SendToProgram(buf, &second);
9404             }
9405         }
9406     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9407         /* Kill off second chess program */
9408         if (second.isr != NULL)
9409           RemoveInputSource(second.isr);
9410         second.isr = NULL;
9411
9412         if (second.pr != NoProc) {
9413             DoSleep( appData.delayBeforeQuit );
9414             SendToProgram("quit\n", &second);
9415             DoSleep( appData.delayAfterQuit );
9416             DestroyChildProcess(second.pr, second.useSigterm);
9417         }
9418         second.pr = NoProc;
9419     }
9420
9421     if (matchMode && gameMode == TwoMachinesPlay) {
9422         switch (result) {
9423         case WhiteWins:
9424           if (first.twoMachinesColor[0] == 'w') {
9425             first.matchWins++;
9426           } else {
9427             second.matchWins++;
9428           }
9429           break;
9430         case BlackWins:
9431           if (first.twoMachinesColor[0] == 'b') {
9432             first.matchWins++;
9433           } else {
9434             second.matchWins++;
9435           }
9436           break;
9437         default:
9438           break;
9439         }
9440         if (matchGame < appData.matchGames) {
9441             char *tmp;
9442             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9443                 tmp = first.twoMachinesColor;
9444                 first.twoMachinesColor = second.twoMachinesColor;
9445                 second.twoMachinesColor = tmp;
9446             }
9447             gameMode = nextGameMode;
9448             matchGame++;
9449             if(appData.matchPause>10000 || appData.matchPause<10)
9450                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9451             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9452             endingGame = 0; /* [HGM] crash */
9453             return;
9454         } else {
9455             gameMode = nextGameMode;
9456             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9457                      first.tidy, second.tidy,
9458                      first.matchWins, second.matchWins,
9459                      appData.matchGames - (first.matchWins + second.matchWins));
9460             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9461             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9462                 first.twoMachinesColor = "black\n";
9463                 second.twoMachinesColor = "white\n";
9464             } else {
9465                 first.twoMachinesColor = "white\n";
9466                 second.twoMachinesColor = "black\n";
9467             }
9468         }
9469     }
9470     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9471         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9472       ExitAnalyzeMode();
9473     gameMode = nextGameMode;
9474     ModeHighlight();
9475     endingGame = 0;  /* [HGM] crash */
9476     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9477       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9478         matchMode = FALSE; appData.matchGames = matchGame = 0;
9479         DisplayNote(buf);
9480       }
9481     }
9482 }
9483
9484 /* Assumes program was just initialized (initString sent).
9485    Leaves program in force mode. */
9486 void
9487 FeedMovesToProgram(cps, upto)
9488      ChessProgramState *cps;
9489      int upto;
9490 {
9491     int i;
9492
9493     if (appData.debugMode)
9494       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9495               startedFromSetupPosition ? "position and " : "",
9496               backwardMostMove, upto, cps->which);
9497     if(currentlyInitializedVariant != gameInfo.variant) {
9498       char buf[MSG_SIZ];
9499         // [HGM] variantswitch: make engine aware of new variant
9500         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9501                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9502         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9503         SendToProgram(buf, cps);
9504         currentlyInitializedVariant = gameInfo.variant;
9505     }
9506     SendToProgram("force\n", cps);
9507     if (startedFromSetupPosition) {
9508         SendBoard(cps, backwardMostMove);
9509     if (appData.debugMode) {
9510         fprintf(debugFP, "feedMoves\n");
9511     }
9512     }
9513     for (i = backwardMostMove; i < upto; i++) {
9514         SendMoveToProgram(i, cps);
9515     }
9516 }
9517
9518
9519 void
9520 ResurrectChessProgram()
9521 {
9522      /* The chess program may have exited.
9523         If so, restart it and feed it all the moves made so far. */
9524
9525     if (appData.noChessProgram || first.pr != NoProc) return;
9526
9527     StartChessProgram(&first);
9528     InitChessProgram(&first, FALSE);
9529     FeedMovesToProgram(&first, currentMove);
9530
9531     if (!first.sendTime) {
9532         /* can't tell gnuchess what its clock should read,
9533            so we bow to its notion. */
9534         ResetClocks();
9535         timeRemaining[0][currentMove] = whiteTimeRemaining;
9536         timeRemaining[1][currentMove] = blackTimeRemaining;
9537     }
9538
9539     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9540                 appData.icsEngineAnalyze) && first.analysisSupport) {
9541       SendToProgram("analyze\n", &first);
9542       first.analyzing = TRUE;
9543     }
9544 }
9545
9546 /*
9547  * Button procedures
9548  */
9549 void
9550 Reset(redraw, init)
9551      int redraw, init;
9552 {
9553     int i;
9554
9555     if (appData.debugMode) {
9556         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9557                 redraw, init, gameMode);
9558     }
9559     CleanupTail(); // [HGM] vari: delete any stored variations
9560     pausing = pauseExamInvalid = FALSE;
9561     startedFromSetupPosition = blackPlaysFirst = FALSE;
9562     firstMove = TRUE;
9563     whiteFlag = blackFlag = FALSE;
9564     userOfferedDraw = FALSE;
9565     hintRequested = bookRequested = FALSE;
9566     first.maybeThinking = FALSE;
9567     second.maybeThinking = FALSE;
9568     first.bookSuspend = FALSE; // [HGM] book
9569     second.bookSuspend = FALSE;
9570     thinkOutput[0] = NULLCHAR;
9571     lastHint[0] = NULLCHAR;
9572     ClearGameInfo(&gameInfo);
9573     gameInfo.variant = StringToVariant(appData.variant);
9574     ics_user_moved = ics_clock_paused = FALSE;
9575     ics_getting_history = H_FALSE;
9576     ics_gamenum = -1;
9577     white_holding[0] = black_holding[0] = NULLCHAR;
9578     ClearProgramStats();
9579     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9580
9581     ResetFrontEnd();
9582     ClearHighlights();
9583     flipView = appData.flipView;
9584     ClearPremoveHighlights();
9585     gotPremove = FALSE;
9586     alarmSounded = FALSE;
9587
9588     GameEnds(EndOfFile, NULL, GE_PLAYER);
9589     if(appData.serverMovesName != NULL) {
9590         /* [HGM] prepare to make moves file for broadcasting */
9591         clock_t t = clock();
9592         if(serverMoves != NULL) fclose(serverMoves);
9593         serverMoves = fopen(appData.serverMovesName, "r");
9594         if(serverMoves != NULL) {
9595             fclose(serverMoves);
9596             /* delay 15 sec before overwriting, so all clients can see end */
9597             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9598         }
9599         serverMoves = fopen(appData.serverMovesName, "w");
9600     }
9601
9602     ExitAnalyzeMode();
9603     gameMode = BeginningOfGame;
9604     ModeHighlight();
9605     if(appData.icsActive) gameInfo.variant = VariantNormal;
9606     currentMove = forwardMostMove = backwardMostMove = 0;
9607     InitPosition(redraw);
9608     for (i = 0; i < MAX_MOVES; i++) {
9609         if (commentList[i] != NULL) {
9610             free(commentList[i]);
9611             commentList[i] = NULL;
9612         }
9613     }
9614     ResetClocks();
9615     timeRemaining[0][0] = whiteTimeRemaining;
9616     timeRemaining[1][0] = blackTimeRemaining;
9617     if (first.pr == NULL) {
9618         StartChessProgram(&first);
9619     }
9620     if (init) {
9621             InitChessProgram(&first, startedFromSetupPosition);
9622     }
9623     DisplayTitle("");
9624     DisplayMessage("", "");
9625     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9626     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9627 }
9628
9629 void
9630 AutoPlayGameLoop()
9631 {
9632     for (;;) {
9633         if (!AutoPlayOneMove())
9634           return;
9635         if (matchMode || appData.timeDelay == 0)
9636           continue;
9637         if (appData.timeDelay < 0)
9638           return;
9639         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9640         break;
9641     }
9642 }
9643
9644
9645 int
9646 AutoPlayOneMove()
9647 {
9648     int fromX, fromY, toX, toY;
9649
9650     if (appData.debugMode) {
9651       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9652     }
9653
9654     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9655       return FALSE;
9656
9657     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9658       pvInfoList[currentMove].depth = programStats.depth;
9659       pvInfoList[currentMove].score = programStats.score;
9660       pvInfoList[currentMove].time  = 0;
9661       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9662     }
9663
9664     if (currentMove >= forwardMostMove) {
9665       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9666       gameMode = EditGame;
9667       ModeHighlight();
9668
9669       /* [AS] Clear current move marker at the end of a game */
9670       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9671
9672       return FALSE;
9673     }
9674
9675     toX = moveList[currentMove][2] - AAA;
9676     toY = moveList[currentMove][3] - ONE;
9677
9678     if (moveList[currentMove][1] == '@') {
9679         if (appData.highlightLastMove) {
9680             SetHighlights(-1, -1, toX, toY);
9681         }
9682     } else {
9683         fromX = moveList[currentMove][0] - AAA;
9684         fromY = moveList[currentMove][1] - ONE;
9685
9686         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9687
9688         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9689
9690         if (appData.highlightLastMove) {
9691             SetHighlights(fromX, fromY, toX, toY);
9692         }
9693     }
9694     DisplayMove(currentMove);
9695     SendMoveToProgram(currentMove++, &first);
9696     DisplayBothClocks();
9697     DrawPosition(FALSE, boards[currentMove]);
9698     // [HGM] PV info: always display, routine tests if empty
9699     DisplayComment(currentMove - 1, commentList[currentMove]);
9700     return TRUE;
9701 }
9702
9703
9704 int
9705 LoadGameOneMove(readAhead)
9706      ChessMove readAhead;
9707 {
9708     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9709     char promoChar = NULLCHAR;
9710     ChessMove moveType;
9711     char move[MSG_SIZ];
9712     char *p, *q;
9713
9714     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9715         gameMode != AnalyzeMode && gameMode != Training) {
9716         gameFileFP = NULL;
9717         return FALSE;
9718     }
9719
9720     yyboardindex = forwardMostMove;
9721     if (readAhead != EndOfFile) {
9722       moveType = readAhead;
9723     } else {
9724       if (gameFileFP == NULL)
9725           return FALSE;
9726       moveType = (ChessMove) Myylex();
9727     }
9728
9729     done = FALSE;
9730     switch (moveType) {
9731       case Comment:
9732         if (appData.debugMode)
9733           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9734         p = yy_text;
9735
9736         /* append the comment but don't display it */
9737         AppendComment(currentMove, p, FALSE);
9738         return TRUE;
9739
9740       case WhiteCapturesEnPassant:
9741       case BlackCapturesEnPassant:
9742       case WhitePromotion:
9743       case BlackPromotion:
9744       case WhiteNonPromotion:
9745       case BlackNonPromotion:
9746       case NormalMove:
9747       case WhiteKingSideCastle:
9748       case WhiteQueenSideCastle:
9749       case BlackKingSideCastle:
9750       case BlackQueenSideCastle:
9751       case WhiteKingSideCastleWild:
9752       case WhiteQueenSideCastleWild:
9753       case BlackKingSideCastleWild:
9754       case BlackQueenSideCastleWild:
9755       /* PUSH Fabien */
9756       case WhiteHSideCastleFR:
9757       case WhiteASideCastleFR:
9758       case BlackHSideCastleFR:
9759       case BlackASideCastleFR:
9760       /* POP Fabien */
9761         if (appData.debugMode)
9762           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9763         fromX = currentMoveString[0] - AAA;
9764         fromY = currentMoveString[1] - ONE;
9765         toX = currentMoveString[2] - AAA;
9766         toY = currentMoveString[3] - ONE;
9767         promoChar = currentMoveString[4];
9768         break;
9769
9770       case WhiteDrop:
9771       case BlackDrop:
9772         if (appData.debugMode)
9773           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9774         fromX = moveType == WhiteDrop ?
9775           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9776         (int) CharToPiece(ToLower(currentMoveString[0]));
9777         fromY = DROP_RANK;
9778         toX = currentMoveString[2] - AAA;
9779         toY = currentMoveString[3] - ONE;
9780         break;
9781
9782       case WhiteWins:
9783       case BlackWins:
9784       case GameIsDrawn:
9785       case GameUnfinished:
9786         if (appData.debugMode)
9787           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9788         p = strchr(yy_text, '{');
9789         if (p == NULL) p = strchr(yy_text, '(');
9790         if (p == NULL) {
9791             p = yy_text;
9792             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9793         } else {
9794             q = strchr(p, *p == '{' ? '}' : ')');
9795             if (q != NULL) *q = NULLCHAR;
9796             p++;
9797         }
9798         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9799         GameEnds(moveType, p, GE_FILE);
9800         done = TRUE;
9801         if (cmailMsgLoaded) {
9802             ClearHighlights();
9803             flipView = WhiteOnMove(currentMove);
9804             if (moveType == GameUnfinished) flipView = !flipView;
9805             if (appData.debugMode)
9806               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9807         }
9808         break;
9809
9810       case EndOfFile:
9811         if (appData.debugMode)
9812           fprintf(debugFP, "Parser hit end of file\n");
9813         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9814           case MT_NONE:
9815           case MT_CHECK:
9816             break;
9817           case MT_CHECKMATE:
9818           case MT_STAINMATE:
9819             if (WhiteOnMove(currentMove)) {
9820                 GameEnds(BlackWins, "Black mates", GE_FILE);
9821             } else {
9822                 GameEnds(WhiteWins, "White mates", GE_FILE);
9823             }
9824             break;
9825           case MT_STALEMATE:
9826             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9827             break;
9828         }
9829         done = TRUE;
9830         break;
9831
9832       case MoveNumberOne:
9833         if (lastLoadGameStart == GNUChessGame) {
9834             /* GNUChessGames have numbers, but they aren't move numbers */
9835             if (appData.debugMode)
9836               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9837                       yy_text, (int) moveType);
9838             return LoadGameOneMove(EndOfFile); /* tail recursion */
9839         }
9840         /* else fall thru */
9841
9842       case XBoardGame:
9843       case GNUChessGame:
9844       case PGNTag:
9845         /* Reached start of next game in file */
9846         if (appData.debugMode)
9847           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9848         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9849           case MT_NONE:
9850           case MT_CHECK:
9851             break;
9852           case MT_CHECKMATE:
9853           case MT_STAINMATE:
9854             if (WhiteOnMove(currentMove)) {
9855                 GameEnds(BlackWins, "Black mates", GE_FILE);
9856             } else {
9857                 GameEnds(WhiteWins, "White mates", GE_FILE);
9858             }
9859             break;
9860           case MT_STALEMATE:
9861             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9862             break;
9863         }
9864         done = TRUE;
9865         break;
9866
9867       case PositionDiagram:     /* should not happen; ignore */
9868       case ElapsedTime:         /* ignore */
9869       case NAG:                 /* ignore */
9870         if (appData.debugMode)
9871           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9872                   yy_text, (int) moveType);
9873         return LoadGameOneMove(EndOfFile); /* tail recursion */
9874
9875       case IllegalMove:
9876         if (appData.testLegality) {
9877             if (appData.debugMode)
9878               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9879             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9880                     (forwardMostMove / 2) + 1,
9881                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9882             DisplayError(move, 0);
9883             done = TRUE;
9884         } else {
9885             if (appData.debugMode)
9886               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9887                       yy_text, currentMoveString);
9888             fromX = currentMoveString[0] - AAA;
9889             fromY = currentMoveString[1] - ONE;
9890             toX = currentMoveString[2] - AAA;
9891             toY = currentMoveString[3] - ONE;
9892             promoChar = currentMoveString[4];
9893         }
9894         break;
9895
9896       case AmbiguousMove:
9897         if (appData.debugMode)
9898           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9899         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9900                 (forwardMostMove / 2) + 1,
9901                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9902         DisplayError(move, 0);
9903         done = TRUE;
9904         break;
9905
9906       default:
9907       case ImpossibleMove:
9908         if (appData.debugMode)
9909           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9910         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9911                 (forwardMostMove / 2) + 1,
9912                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9913         DisplayError(move, 0);
9914         done = TRUE;
9915         break;
9916     }
9917
9918     if (done) {
9919         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9920             DrawPosition(FALSE, boards[currentMove]);
9921             DisplayBothClocks();
9922             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9923               DisplayComment(currentMove - 1, commentList[currentMove]);
9924         }
9925         (void) StopLoadGameTimer();
9926         gameFileFP = NULL;
9927         cmailOldMove = forwardMostMove;
9928         return FALSE;
9929     } else {
9930         /* currentMoveString is set as a side-effect of yylex */
9931         strcat(currentMoveString, "\n");
9932         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9933
9934         thinkOutput[0] = NULLCHAR;
9935         MakeMove(fromX, fromY, toX, toY, promoChar);
9936         currentMove = forwardMostMove;
9937         return TRUE;
9938     }
9939 }
9940
9941 /* Load the nth game from the given file */
9942 int
9943 LoadGameFromFile(filename, n, title, useList)
9944      char *filename;
9945      int n;
9946      char *title;
9947      /*Boolean*/ int useList;
9948 {
9949     FILE *f;
9950     char buf[MSG_SIZ];
9951
9952     if (strcmp(filename, "-") == 0) {
9953         f = stdin;
9954         title = "stdin";
9955     } else {
9956         f = fopen(filename, "rb");
9957         if (f == NULL) {
9958           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9959             DisplayError(buf, errno);
9960             return FALSE;
9961         }
9962     }
9963     if (fseek(f, 0, 0) == -1) {
9964         /* f is not seekable; probably a pipe */
9965         useList = FALSE;
9966     }
9967     if (useList && n == 0) {
9968         int error = GameListBuild(f);
9969         if (error) {
9970             DisplayError(_("Cannot build game list"), error);
9971         } else if (!ListEmpty(&gameList) &&
9972                    ((ListGame *) gameList.tailPred)->number > 1) {
9973             GameListPopUp(f, title);
9974             return TRUE;
9975         }
9976         GameListDestroy();
9977         n = 1;
9978     }
9979     if (n == 0) n = 1;
9980     return LoadGame(f, n, title, FALSE);
9981 }
9982
9983
9984 void
9985 MakeRegisteredMove()
9986 {
9987     int fromX, fromY, toX, toY;
9988     char promoChar;
9989     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9990         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9991           case CMAIL_MOVE:
9992           case CMAIL_DRAW:
9993             if (appData.debugMode)
9994               fprintf(debugFP, "Restoring %s for game %d\n",
9995                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9996
9997             thinkOutput[0] = NULLCHAR;
9998             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9999             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10000             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10001             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10002             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10003             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10004             MakeMove(fromX, fromY, toX, toY, promoChar);
10005             ShowMove(fromX, fromY, toX, toY);
10006
10007             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10008               case MT_NONE:
10009               case MT_CHECK:
10010                 break;
10011
10012               case MT_CHECKMATE:
10013               case MT_STAINMATE:
10014                 if (WhiteOnMove(currentMove)) {
10015                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10016                 } else {
10017                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10018                 }
10019                 break;
10020
10021               case MT_STALEMATE:
10022                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10023                 break;
10024             }
10025
10026             break;
10027
10028           case CMAIL_RESIGN:
10029             if (WhiteOnMove(currentMove)) {
10030                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10031             } else {
10032                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10033             }
10034             break;
10035
10036           case CMAIL_ACCEPT:
10037             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10038             break;
10039
10040           default:
10041             break;
10042         }
10043     }
10044
10045     return;
10046 }
10047
10048 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10049 int
10050 CmailLoadGame(f, gameNumber, title, useList)
10051      FILE *f;
10052      int gameNumber;
10053      char *title;
10054      int useList;
10055 {
10056     int retVal;
10057
10058     if (gameNumber > nCmailGames) {
10059         DisplayError(_("No more games in this message"), 0);
10060         return FALSE;
10061     }
10062     if (f == lastLoadGameFP) {
10063         int offset = gameNumber - lastLoadGameNumber;
10064         if (offset == 0) {
10065             cmailMsg[0] = NULLCHAR;
10066             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10067                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10068                 nCmailMovesRegistered--;
10069             }
10070             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10071             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10072                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10073             }
10074         } else {
10075             if (! RegisterMove()) return FALSE;
10076         }
10077     }
10078
10079     retVal = LoadGame(f, gameNumber, title, useList);
10080
10081     /* Make move registered during previous look at this game, if any */
10082     MakeRegisteredMove();
10083
10084     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10085         commentList[currentMove]
10086           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10087         DisplayComment(currentMove - 1, commentList[currentMove]);
10088     }
10089
10090     return retVal;
10091 }
10092
10093 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10094 int
10095 ReloadGame(offset)
10096      int offset;
10097 {
10098     int gameNumber = lastLoadGameNumber + offset;
10099     if (lastLoadGameFP == NULL) {
10100         DisplayError(_("No game has been loaded yet"), 0);
10101         return FALSE;
10102     }
10103     if (gameNumber <= 0) {
10104         DisplayError(_("Can't back up any further"), 0);
10105         return FALSE;
10106     }
10107     if (cmailMsgLoaded) {
10108         return CmailLoadGame(lastLoadGameFP, gameNumber,
10109                              lastLoadGameTitle, lastLoadGameUseList);
10110     } else {
10111         return LoadGame(lastLoadGameFP, gameNumber,
10112                         lastLoadGameTitle, lastLoadGameUseList);
10113     }
10114 }
10115
10116
10117
10118 /* Load the nth game from open file f */
10119 int
10120 LoadGame(f, gameNumber, title, useList)
10121      FILE *f;
10122      int gameNumber;
10123      char *title;
10124      int useList;
10125 {
10126     ChessMove cm;
10127     char buf[MSG_SIZ];
10128     int gn = gameNumber;
10129     ListGame *lg = NULL;
10130     int numPGNTags = 0;
10131     int err;
10132     GameMode oldGameMode;
10133     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10134
10135     if (appData.debugMode)
10136         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10137
10138     if (gameMode == Training )
10139         SetTrainingModeOff();
10140
10141     oldGameMode = gameMode;
10142     if (gameMode != BeginningOfGame) {
10143       Reset(FALSE, TRUE);
10144     }
10145
10146     gameFileFP = f;
10147     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10148         fclose(lastLoadGameFP);
10149     }
10150
10151     if (useList) {
10152         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10153
10154         if (lg) {
10155             fseek(f, lg->offset, 0);
10156             GameListHighlight(gameNumber);
10157             gn = 1;
10158         }
10159         else {
10160             DisplayError(_("Game number out of range"), 0);
10161             return FALSE;
10162         }
10163     } else {
10164         GameListDestroy();
10165         if (fseek(f, 0, 0) == -1) {
10166             if (f == lastLoadGameFP ?
10167                 gameNumber == lastLoadGameNumber + 1 :
10168                 gameNumber == 1) {
10169                 gn = 1;
10170             } else {
10171                 DisplayError(_("Can't seek on game file"), 0);
10172                 return FALSE;
10173             }
10174         }
10175     }
10176     lastLoadGameFP = f;
10177     lastLoadGameNumber = gameNumber;
10178     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10179     lastLoadGameUseList = useList;
10180
10181     yynewfile(f);
10182
10183     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10184       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10185                 lg->gameInfo.black);
10186             DisplayTitle(buf);
10187     } else if (*title != NULLCHAR) {
10188         if (gameNumber > 1) {
10189           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10190             DisplayTitle(buf);
10191         } else {
10192             DisplayTitle(title);
10193         }
10194     }
10195
10196     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10197         gameMode = PlayFromGameFile;
10198         ModeHighlight();
10199     }
10200
10201     currentMove = forwardMostMove = backwardMostMove = 0;
10202     CopyBoard(boards[0], initialPosition);
10203     StopClocks();
10204
10205     /*
10206      * Skip the first gn-1 games in the file.
10207      * Also skip over anything that precedes an identifiable
10208      * start of game marker, to avoid being confused by
10209      * garbage at the start of the file.  Currently
10210      * recognized start of game markers are the move number "1",
10211      * the pattern "gnuchess .* game", the pattern
10212      * "^[#;%] [^ ]* game file", and a PGN tag block.
10213      * A game that starts with one of the latter two patterns
10214      * will also have a move number 1, possibly
10215      * following a position diagram.
10216      * 5-4-02: Let's try being more lenient and allowing a game to
10217      * start with an unnumbered move.  Does that break anything?
10218      */
10219     cm = lastLoadGameStart = EndOfFile;
10220     while (gn > 0) {
10221         yyboardindex = forwardMostMove;
10222         cm = (ChessMove) Myylex();
10223         switch (cm) {
10224           case EndOfFile:
10225             if (cmailMsgLoaded) {
10226                 nCmailGames = CMAIL_MAX_GAMES - gn;
10227             } else {
10228                 Reset(TRUE, TRUE);
10229                 DisplayError(_("Game not found in file"), 0);
10230             }
10231             return FALSE;
10232
10233           case GNUChessGame:
10234           case XBoardGame:
10235             gn--;
10236             lastLoadGameStart = cm;
10237             break;
10238
10239           case MoveNumberOne:
10240             switch (lastLoadGameStart) {
10241               case GNUChessGame:
10242               case XBoardGame:
10243               case PGNTag:
10244                 break;
10245               case MoveNumberOne:
10246               case EndOfFile:
10247                 gn--;           /* count this game */
10248                 lastLoadGameStart = cm;
10249                 break;
10250               default:
10251                 /* impossible */
10252                 break;
10253             }
10254             break;
10255
10256           case PGNTag:
10257             switch (lastLoadGameStart) {
10258               case GNUChessGame:
10259               case PGNTag:
10260               case MoveNumberOne:
10261               case EndOfFile:
10262                 gn--;           /* count this game */
10263                 lastLoadGameStart = cm;
10264                 break;
10265               case XBoardGame:
10266                 lastLoadGameStart = cm; /* game counted already */
10267                 break;
10268               default:
10269                 /* impossible */
10270                 break;
10271             }
10272             if (gn > 0) {
10273                 do {
10274                     yyboardindex = forwardMostMove;
10275                     cm = (ChessMove) Myylex();
10276                 } while (cm == PGNTag || cm == Comment);
10277             }
10278             break;
10279
10280           case WhiteWins:
10281           case BlackWins:
10282           case GameIsDrawn:
10283             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10284                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10285                     != CMAIL_OLD_RESULT) {
10286                     nCmailResults ++ ;
10287                     cmailResult[  CMAIL_MAX_GAMES
10288                                 - gn - 1] = CMAIL_OLD_RESULT;
10289                 }
10290             }
10291             break;
10292
10293           case NormalMove:
10294             /* Only a NormalMove can be at the start of a game
10295              * without a position diagram. */
10296             if (lastLoadGameStart == EndOfFile ) {
10297               gn--;
10298               lastLoadGameStart = MoveNumberOne;
10299             }
10300             break;
10301
10302           default:
10303             break;
10304         }
10305     }
10306
10307     if (appData.debugMode)
10308       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10309
10310     if (cm == XBoardGame) {
10311         /* Skip any header junk before position diagram and/or move 1 */
10312         for (;;) {
10313             yyboardindex = forwardMostMove;
10314             cm = (ChessMove) Myylex();
10315
10316             if (cm == EndOfFile ||
10317                 cm == GNUChessGame || cm == XBoardGame) {
10318                 /* Empty game; pretend end-of-file and handle later */
10319                 cm = EndOfFile;
10320                 break;
10321             }
10322
10323             if (cm == MoveNumberOne || cm == PositionDiagram ||
10324                 cm == PGNTag || cm == Comment)
10325               break;
10326         }
10327     } else if (cm == GNUChessGame) {
10328         if (gameInfo.event != NULL) {
10329             free(gameInfo.event);
10330         }
10331         gameInfo.event = StrSave(yy_text);
10332     }
10333
10334     startedFromSetupPosition = FALSE;
10335     while (cm == PGNTag) {
10336         if (appData.debugMode)
10337           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10338         err = ParsePGNTag(yy_text, &gameInfo);
10339         if (!err) numPGNTags++;
10340
10341         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10342         if(gameInfo.variant != oldVariant) {
10343             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10344             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10345             InitPosition(TRUE);
10346             oldVariant = gameInfo.variant;
10347             if (appData.debugMode)
10348               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10349         }
10350
10351
10352         if (gameInfo.fen != NULL) {
10353           Board initial_position;
10354           startedFromSetupPosition = TRUE;
10355           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10356             Reset(TRUE, TRUE);
10357             DisplayError(_("Bad FEN position in file"), 0);
10358             return FALSE;
10359           }
10360           CopyBoard(boards[0], initial_position);
10361           if (blackPlaysFirst) {
10362             currentMove = forwardMostMove = backwardMostMove = 1;
10363             CopyBoard(boards[1], initial_position);
10364             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10365             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10366             timeRemaining[0][1] = whiteTimeRemaining;
10367             timeRemaining[1][1] = blackTimeRemaining;
10368             if (commentList[0] != NULL) {
10369               commentList[1] = commentList[0];
10370               commentList[0] = NULL;
10371             }
10372           } else {
10373             currentMove = forwardMostMove = backwardMostMove = 0;
10374           }
10375           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10376           {   int i;
10377               initialRulePlies = FENrulePlies;
10378               for( i=0; i< nrCastlingRights; i++ )
10379                   initialRights[i] = initial_position[CASTLING][i];
10380           }
10381           yyboardindex = forwardMostMove;
10382           free(gameInfo.fen);
10383           gameInfo.fen = NULL;
10384         }
10385
10386         yyboardindex = forwardMostMove;
10387         cm = (ChessMove) Myylex();
10388
10389         /* Handle comments interspersed among the tags */
10390         while (cm == Comment) {
10391             char *p;
10392             if (appData.debugMode)
10393               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10394             p = yy_text;
10395             AppendComment(currentMove, p, FALSE);
10396             yyboardindex = forwardMostMove;
10397             cm = (ChessMove) Myylex();
10398         }
10399     }
10400
10401     /* don't rely on existence of Event tag since if game was
10402      * pasted from clipboard the Event tag may not exist
10403      */
10404     if (numPGNTags > 0){
10405         char *tags;
10406         if (gameInfo.variant == VariantNormal) {
10407           VariantClass v = StringToVariant(gameInfo.event);
10408           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10409           if(v < VariantShogi) gameInfo.variant = v;
10410         }
10411         if (!matchMode) {
10412           if( appData.autoDisplayTags ) {
10413             tags = PGNTags(&gameInfo);
10414             TagsPopUp(tags, CmailMsg());
10415             free(tags);
10416           }
10417         }
10418     } else {
10419         /* Make something up, but don't display it now */
10420         SetGameInfo();
10421         TagsPopDown();
10422     }
10423
10424     if (cm == PositionDiagram) {
10425         int i, j;
10426         char *p;
10427         Board initial_position;
10428
10429         if (appData.debugMode)
10430           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10431
10432         if (!startedFromSetupPosition) {
10433             p = yy_text;
10434             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10435               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10436                 switch (*p) {
10437                   case '[':
10438                   case '-':
10439                   case ' ':
10440                   case '\t':
10441                   case '\n':
10442                   case '\r':
10443                     break;
10444                   default:
10445                     initial_position[i][j++] = CharToPiece(*p);
10446                     break;
10447                 }
10448             while (*p == ' ' || *p == '\t' ||
10449                    *p == '\n' || *p == '\r') p++;
10450
10451             if (strncmp(p, "black", strlen("black"))==0)
10452               blackPlaysFirst = TRUE;
10453             else
10454               blackPlaysFirst = FALSE;
10455             startedFromSetupPosition = TRUE;
10456
10457             CopyBoard(boards[0], initial_position);
10458             if (blackPlaysFirst) {
10459                 currentMove = forwardMostMove = backwardMostMove = 1;
10460                 CopyBoard(boards[1], initial_position);
10461                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10462                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10463                 timeRemaining[0][1] = whiteTimeRemaining;
10464                 timeRemaining[1][1] = blackTimeRemaining;
10465                 if (commentList[0] != NULL) {
10466                     commentList[1] = commentList[0];
10467                     commentList[0] = NULL;
10468                 }
10469             } else {
10470                 currentMove = forwardMostMove = backwardMostMove = 0;
10471             }
10472         }
10473         yyboardindex = forwardMostMove;
10474         cm = (ChessMove) Myylex();
10475     }
10476
10477     if (first.pr == NoProc) {
10478         StartChessProgram(&first);
10479     }
10480     InitChessProgram(&first, FALSE);
10481     SendToProgram("force\n", &first);
10482     if (startedFromSetupPosition) {
10483         SendBoard(&first, forwardMostMove);
10484     if (appData.debugMode) {
10485         fprintf(debugFP, "Load Game\n");
10486     }
10487         DisplayBothClocks();
10488     }
10489
10490     /* [HGM] server: flag to write setup moves in broadcast file as one */
10491     loadFlag = appData.suppressLoadMoves;
10492
10493     while (cm == Comment) {
10494         char *p;
10495         if (appData.debugMode)
10496           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10497         p = yy_text;
10498         AppendComment(currentMove, p, FALSE);
10499         yyboardindex = forwardMostMove;
10500         cm = (ChessMove) Myylex();
10501     }
10502
10503     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10504         cm == WhiteWins || cm == BlackWins ||
10505         cm == GameIsDrawn || cm == GameUnfinished) {
10506         DisplayMessage("", _("No moves in game"));
10507         if (cmailMsgLoaded) {
10508             if (appData.debugMode)
10509               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10510             ClearHighlights();
10511             flipView = FALSE;
10512         }
10513         DrawPosition(FALSE, boards[currentMove]);
10514         DisplayBothClocks();
10515         gameMode = EditGame;
10516         ModeHighlight();
10517         gameFileFP = NULL;
10518         cmailOldMove = 0;
10519         return TRUE;
10520     }
10521
10522     // [HGM] PV info: routine tests if comment empty
10523     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10524         DisplayComment(currentMove - 1, commentList[currentMove]);
10525     }
10526     if (!matchMode && appData.timeDelay != 0)
10527       DrawPosition(FALSE, boards[currentMove]);
10528
10529     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10530       programStats.ok_to_send = 1;
10531     }
10532
10533     /* if the first token after the PGN tags is a move
10534      * and not move number 1, retrieve it from the parser
10535      */
10536     if (cm != MoveNumberOne)
10537         LoadGameOneMove(cm);
10538
10539     /* load the remaining moves from the file */
10540     while (LoadGameOneMove(EndOfFile)) {
10541       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10542       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10543     }
10544
10545     /* rewind to the start of the game */
10546     currentMove = backwardMostMove;
10547
10548     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10549
10550     if (oldGameMode == AnalyzeFile ||
10551         oldGameMode == AnalyzeMode) {
10552       AnalyzeFileEvent();
10553     }
10554
10555     if (matchMode || appData.timeDelay == 0) {
10556       ToEndEvent();
10557       gameMode = EditGame;
10558       ModeHighlight();
10559     } else if (appData.timeDelay > 0) {
10560       AutoPlayGameLoop();
10561     }
10562
10563     if (appData.debugMode)
10564         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10565
10566     loadFlag = 0; /* [HGM] true game starts */
10567     return TRUE;
10568 }
10569
10570 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10571 int
10572 ReloadPosition(offset)
10573      int offset;
10574 {
10575     int positionNumber = lastLoadPositionNumber + offset;
10576     if (lastLoadPositionFP == NULL) {
10577         DisplayError(_("No position has been loaded yet"), 0);
10578         return FALSE;
10579     }
10580     if (positionNumber <= 0) {
10581         DisplayError(_("Can't back up any further"), 0);
10582         return FALSE;
10583     }
10584     return LoadPosition(lastLoadPositionFP, positionNumber,
10585                         lastLoadPositionTitle);
10586 }
10587
10588 /* Load the nth position from the given file */
10589 int
10590 LoadPositionFromFile(filename, n, title)
10591      char *filename;
10592      int n;
10593      char *title;
10594 {
10595     FILE *f;
10596     char buf[MSG_SIZ];
10597
10598     if (strcmp(filename, "-") == 0) {
10599         return LoadPosition(stdin, n, "stdin");
10600     } else {
10601         f = fopen(filename, "rb");
10602         if (f == NULL) {
10603             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10604             DisplayError(buf, errno);
10605             return FALSE;
10606         } else {
10607             return LoadPosition(f, n, title);
10608         }
10609     }
10610 }
10611
10612 /* Load the nth position from the given open file, and close it */
10613 int
10614 LoadPosition(f, positionNumber, title)
10615      FILE *f;
10616      int positionNumber;
10617      char *title;
10618 {
10619     char *p, line[MSG_SIZ];
10620     Board initial_position;
10621     int i, j, fenMode, pn;
10622
10623     if (gameMode == Training )
10624         SetTrainingModeOff();
10625
10626     if (gameMode != BeginningOfGame) {
10627         Reset(FALSE, TRUE);
10628     }
10629     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10630         fclose(lastLoadPositionFP);
10631     }
10632     if (positionNumber == 0) positionNumber = 1;
10633     lastLoadPositionFP = f;
10634     lastLoadPositionNumber = positionNumber;
10635     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10636     if (first.pr == NoProc) {
10637       StartChessProgram(&first);
10638       InitChessProgram(&first, FALSE);
10639     }
10640     pn = positionNumber;
10641     if (positionNumber < 0) {
10642         /* Negative position number means to seek to that byte offset */
10643         if (fseek(f, -positionNumber, 0) == -1) {
10644             DisplayError(_("Can't seek on position file"), 0);
10645             return FALSE;
10646         };
10647         pn = 1;
10648     } else {
10649         if (fseek(f, 0, 0) == -1) {
10650             if (f == lastLoadPositionFP ?
10651                 positionNumber == lastLoadPositionNumber + 1 :
10652                 positionNumber == 1) {
10653                 pn = 1;
10654             } else {
10655                 DisplayError(_("Can't seek on position file"), 0);
10656                 return FALSE;
10657             }
10658         }
10659     }
10660     /* See if this file is FEN or old-style xboard */
10661     if (fgets(line, MSG_SIZ, f) == NULL) {
10662         DisplayError(_("Position not found in file"), 0);
10663         return FALSE;
10664     }
10665     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10666     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10667
10668     if (pn >= 2) {
10669         if (fenMode || line[0] == '#') pn--;
10670         while (pn > 0) {
10671             /* skip positions before number pn */
10672             if (fgets(line, MSG_SIZ, f) == NULL) {
10673                 Reset(TRUE, TRUE);
10674                 DisplayError(_("Position not found in file"), 0);
10675                 return FALSE;
10676             }
10677             if (fenMode || line[0] == '#') pn--;
10678         }
10679     }
10680
10681     if (fenMode) {
10682         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10683             DisplayError(_("Bad FEN position in file"), 0);
10684             return FALSE;
10685         }
10686     } else {
10687         (void) fgets(line, MSG_SIZ, f);
10688         (void) fgets(line, MSG_SIZ, f);
10689
10690         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10691             (void) fgets(line, MSG_SIZ, f);
10692             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10693                 if (*p == ' ')
10694                   continue;
10695                 initial_position[i][j++] = CharToPiece(*p);
10696             }
10697         }
10698
10699         blackPlaysFirst = FALSE;
10700         if (!feof(f)) {
10701             (void) fgets(line, MSG_SIZ, f);
10702             if (strncmp(line, "black", strlen("black"))==0)
10703               blackPlaysFirst = TRUE;
10704         }
10705     }
10706     startedFromSetupPosition = TRUE;
10707
10708     SendToProgram("force\n", &first);
10709     CopyBoard(boards[0], initial_position);
10710     if (blackPlaysFirst) {
10711         currentMove = forwardMostMove = backwardMostMove = 1;
10712         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10713         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10714         CopyBoard(boards[1], initial_position);
10715         DisplayMessage("", _("Black to play"));
10716     } else {
10717         currentMove = forwardMostMove = backwardMostMove = 0;
10718         DisplayMessage("", _("White to play"));
10719     }
10720     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10721     SendBoard(&first, forwardMostMove);
10722     if (appData.debugMode) {
10723 int i, j;
10724   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10725   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10726         fprintf(debugFP, "Load Position\n");
10727     }
10728
10729     if (positionNumber > 1) {
10730       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10731         DisplayTitle(line);
10732     } else {
10733         DisplayTitle(title);
10734     }
10735     gameMode = EditGame;
10736     ModeHighlight();
10737     ResetClocks();
10738     timeRemaining[0][1] = whiteTimeRemaining;
10739     timeRemaining[1][1] = blackTimeRemaining;
10740     DrawPosition(FALSE, boards[currentMove]);
10741
10742     return TRUE;
10743 }
10744
10745
10746 void
10747 CopyPlayerNameIntoFileName(dest, src)
10748      char **dest, *src;
10749 {
10750     while (*src != NULLCHAR && *src != ',') {
10751         if (*src == ' ') {
10752             *(*dest)++ = '_';
10753             src++;
10754         } else {
10755             *(*dest)++ = *src++;
10756         }
10757     }
10758 }
10759
10760 char *DefaultFileName(ext)
10761      char *ext;
10762 {
10763     static char def[MSG_SIZ];
10764     char *p;
10765
10766     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10767         p = def;
10768         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10769         *p++ = '-';
10770         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10771         *p++ = '.';
10772         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10773     } else {
10774         def[0] = NULLCHAR;
10775     }
10776     return def;
10777 }
10778
10779 /* Save the current game to the given file */
10780 int
10781 SaveGameToFile(filename, append)
10782      char *filename;
10783      int append;
10784 {
10785     FILE *f;
10786     char buf[MSG_SIZ];
10787
10788     if (strcmp(filename, "-") == 0) {
10789         return SaveGame(stdout, 0, NULL);
10790     } else {
10791         f = fopen(filename, append ? "a" : "w");
10792         if (f == NULL) {
10793             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10794             DisplayError(buf, errno);
10795             return FALSE;
10796         } else {
10797             return SaveGame(f, 0, NULL);
10798         }
10799     }
10800 }
10801
10802 char *
10803 SavePart(str)
10804      char *str;
10805 {
10806     static char buf[MSG_SIZ];
10807     char *p;
10808
10809     p = strchr(str, ' ');
10810     if (p == NULL) return str;
10811     strncpy(buf, str, p - str);
10812     buf[p - str] = NULLCHAR;
10813     return buf;
10814 }
10815
10816 #define PGN_MAX_LINE 75
10817
10818 #define PGN_SIDE_WHITE  0
10819 #define PGN_SIDE_BLACK  1
10820
10821 /* [AS] */
10822 static int FindFirstMoveOutOfBook( int side )
10823 {
10824     int result = -1;
10825
10826     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10827         int index = backwardMostMove;
10828         int has_book_hit = 0;
10829
10830         if( (index % 2) != side ) {
10831             index++;
10832         }
10833
10834         while( index < forwardMostMove ) {
10835             /* Check to see if engine is in book */
10836             int depth = pvInfoList[index].depth;
10837             int score = pvInfoList[index].score;
10838             int in_book = 0;
10839
10840             if( depth <= 2 ) {
10841                 in_book = 1;
10842             }
10843             else if( score == 0 && depth == 63 ) {
10844                 in_book = 1; /* Zappa */
10845             }
10846             else if( score == 2 && depth == 99 ) {
10847                 in_book = 1; /* Abrok */
10848             }
10849
10850             has_book_hit += in_book;
10851
10852             if( ! in_book ) {
10853                 result = index;
10854
10855                 break;
10856             }
10857
10858             index += 2;
10859         }
10860     }
10861
10862     return result;
10863 }
10864
10865 /* [AS] */
10866 void GetOutOfBookInfo( char * buf )
10867 {
10868     int oob[2];
10869     int i;
10870     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10871
10872     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10873     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10874
10875     *buf = '\0';
10876
10877     if( oob[0] >= 0 || oob[1] >= 0 ) {
10878         for( i=0; i<2; i++ ) {
10879             int idx = oob[i];
10880
10881             if( idx >= 0 ) {
10882                 if( i > 0 && oob[0] >= 0 ) {
10883                     strcat( buf, "   " );
10884                 }
10885
10886                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10887                 sprintf( buf+strlen(buf), "%s%.2f",
10888                     pvInfoList[idx].score >= 0 ? "+" : "",
10889                     pvInfoList[idx].score / 100.0 );
10890             }
10891         }
10892     }
10893 }
10894
10895 /* Save game in PGN style and close the file */
10896 int
10897 SaveGamePGN(f)
10898      FILE *f;
10899 {
10900     int i, offset, linelen, newblock;
10901     time_t tm;
10902 //    char *movetext;
10903     char numtext[32];
10904     int movelen, numlen, blank;
10905     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10906
10907     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10908
10909     tm = time((time_t *) NULL);
10910
10911     PrintPGNTags(f, &gameInfo);
10912
10913     if (backwardMostMove > 0 || startedFromSetupPosition) {
10914         char *fen = PositionToFEN(backwardMostMove, NULL);
10915         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10916         fprintf(f, "\n{--------------\n");
10917         PrintPosition(f, backwardMostMove);
10918         fprintf(f, "--------------}\n");
10919         free(fen);
10920     }
10921     else {
10922         /* [AS] Out of book annotation */
10923         if( appData.saveOutOfBookInfo ) {
10924             char buf[64];
10925
10926             GetOutOfBookInfo( buf );
10927
10928             if( buf[0] != '\0' ) {
10929                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10930             }
10931         }
10932
10933         fprintf(f, "\n");
10934     }
10935
10936     i = backwardMostMove;
10937     linelen = 0;
10938     newblock = TRUE;
10939
10940     while (i < forwardMostMove) {
10941         /* Print comments preceding this move */
10942         if (commentList[i] != NULL) {
10943             if (linelen > 0) fprintf(f, "\n");
10944             fprintf(f, "%s", commentList[i]);
10945             linelen = 0;
10946             newblock = TRUE;
10947         }
10948
10949         /* Format move number */
10950         if ((i % 2) == 0)
10951           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10952         else
10953           if (newblock)
10954             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10955           else
10956             numtext[0] = NULLCHAR;
10957
10958         numlen = strlen(numtext);
10959         newblock = FALSE;
10960
10961         /* Print move number */
10962         blank = linelen > 0 && numlen > 0;
10963         if (linelen + (blank ? 1 : 0) + numlen > 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", numtext);
10973         linelen += numlen;
10974
10975         /* Get move */
10976         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10977         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10978
10979         /* Print move */
10980         blank = linelen > 0 && movelen > 0;
10981         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10982             fprintf(f, "\n");
10983             linelen = 0;
10984             blank = 0;
10985         }
10986         if (blank) {
10987             fprintf(f, " ");
10988             linelen++;
10989         }
10990         fprintf(f, "%s", move_buffer);
10991         linelen += movelen;
10992
10993         /* [AS] Add PV info if present */
10994         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10995             /* [HGM] add time */
10996             char buf[MSG_SIZ]; int seconds;
10997
10998             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10999
11000             if( seconds <= 0)
11001               buf[0] = 0;
11002             else
11003               if( seconds < 30 )
11004                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11005               else
11006                 {
11007                   seconds = (seconds + 4)/10; // round to full seconds
11008                   if( seconds < 60 )
11009                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11010                   else
11011                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11012                 }
11013
11014             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11015                       pvInfoList[i].score >= 0 ? "+" : "",
11016                       pvInfoList[i].score / 100.0,
11017                       pvInfoList[i].depth,
11018                       buf );
11019
11020             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11021
11022             /* Print score/depth */
11023             blank = linelen > 0 && movelen > 0;
11024             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11025                 fprintf(f, "\n");
11026                 linelen = 0;
11027                 blank = 0;
11028             }
11029             if (blank) {
11030                 fprintf(f, " ");
11031                 linelen++;
11032             }
11033             fprintf(f, "%s", move_buffer);
11034             linelen += movelen;
11035         }
11036
11037         i++;
11038     }
11039
11040     /* Start a new line */
11041     if (linelen > 0) fprintf(f, "\n");
11042
11043     /* Print comments after last move */
11044     if (commentList[i] != NULL) {
11045         fprintf(f, "%s\n", commentList[i]);
11046     }
11047
11048     /* Print result */
11049     if (gameInfo.resultDetails != NULL &&
11050         gameInfo.resultDetails[0] != NULLCHAR) {
11051         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11052                 PGNResult(gameInfo.result));
11053     } else {
11054         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11055     }
11056
11057     fclose(f);
11058     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11059     return TRUE;
11060 }
11061
11062 /* Save game in old style and close the file */
11063 int
11064 SaveGameOldStyle(f)
11065      FILE *f;
11066 {
11067     int i, offset;
11068     time_t tm;
11069
11070     tm = time((time_t *) NULL);
11071
11072     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11073     PrintOpponents(f);
11074
11075     if (backwardMostMove > 0 || startedFromSetupPosition) {
11076         fprintf(f, "\n[--------------\n");
11077         PrintPosition(f, backwardMostMove);
11078         fprintf(f, "--------------]\n");
11079     } else {
11080         fprintf(f, "\n");
11081     }
11082
11083     i = backwardMostMove;
11084     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11085
11086     while (i < forwardMostMove) {
11087         if (commentList[i] != NULL) {
11088             fprintf(f, "[%s]\n", commentList[i]);
11089         }
11090
11091         if ((i % 2) == 1) {
11092             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11093             i++;
11094         } else {
11095             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11096             i++;
11097             if (commentList[i] != NULL) {
11098                 fprintf(f, "\n");
11099                 continue;
11100             }
11101             if (i >= forwardMostMove) {
11102                 fprintf(f, "\n");
11103                 break;
11104             }
11105             fprintf(f, "%s\n", parseList[i]);
11106             i++;
11107         }
11108     }
11109
11110     if (commentList[i] != NULL) {
11111         fprintf(f, "[%s]\n", commentList[i]);
11112     }
11113
11114     /* This isn't really the old style, but it's close enough */
11115     if (gameInfo.resultDetails != NULL &&
11116         gameInfo.resultDetails[0] != NULLCHAR) {
11117         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11118                 gameInfo.resultDetails);
11119     } else {
11120         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11121     }
11122
11123     fclose(f);
11124     return TRUE;
11125 }
11126
11127 /* Save the current game to open file f and close the file */
11128 int
11129 SaveGame(f, dummy, dummy2)
11130      FILE *f;
11131      int dummy;
11132      char *dummy2;
11133 {
11134     if (gameMode == EditPosition) EditPositionDone(TRUE);
11135     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11136     if (appData.oldSaveStyle)
11137       return SaveGameOldStyle(f);
11138     else
11139       return SaveGamePGN(f);
11140 }
11141
11142 /* Save the current position to the given file */
11143 int
11144 SavePositionToFile(filename)
11145      char *filename;
11146 {
11147     FILE *f;
11148     char buf[MSG_SIZ];
11149
11150     if (strcmp(filename, "-") == 0) {
11151         return SavePosition(stdout, 0, NULL);
11152     } else {
11153         f = fopen(filename, "a");
11154         if (f == NULL) {
11155             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11156             DisplayError(buf, errno);
11157             return FALSE;
11158         } else {
11159             SavePosition(f, 0, NULL);
11160             return TRUE;
11161         }
11162     }
11163 }
11164
11165 /* Save the current position to the given open file and close the file */
11166 int
11167 SavePosition(f, dummy, dummy2)
11168      FILE *f;
11169      int dummy;
11170      char *dummy2;
11171 {
11172     time_t tm;
11173     char *fen;
11174
11175     if (gameMode == EditPosition) EditPositionDone(TRUE);
11176     if (appData.oldSaveStyle) {
11177         tm = time((time_t *) NULL);
11178
11179         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11180         PrintOpponents(f);
11181         fprintf(f, "[--------------\n");
11182         PrintPosition(f, currentMove);
11183         fprintf(f, "--------------]\n");
11184     } else {
11185         fen = PositionToFEN(currentMove, NULL);
11186         fprintf(f, "%s\n", fen);
11187         free(fen);
11188     }
11189     fclose(f);
11190     return TRUE;
11191 }
11192
11193 void
11194 ReloadCmailMsgEvent(unregister)
11195      int unregister;
11196 {
11197 #if !WIN32
11198     static char *inFilename = NULL;
11199     static char *outFilename;
11200     int i;
11201     struct stat inbuf, outbuf;
11202     int status;
11203
11204     /* Any registered moves are unregistered if unregister is set, */
11205     /* i.e. invoked by the signal handler */
11206     if (unregister) {
11207         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11208             cmailMoveRegistered[i] = FALSE;
11209             if (cmailCommentList[i] != NULL) {
11210                 free(cmailCommentList[i]);
11211                 cmailCommentList[i] = NULL;
11212             }
11213         }
11214         nCmailMovesRegistered = 0;
11215     }
11216
11217     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11218         cmailResult[i] = CMAIL_NOT_RESULT;
11219     }
11220     nCmailResults = 0;
11221
11222     if (inFilename == NULL) {
11223         /* Because the filenames are static they only get malloced once  */
11224         /* and they never get freed                                      */
11225         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11226         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11227
11228         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11229         sprintf(outFilename, "%s.out", appData.cmailGameName);
11230     }
11231
11232     status = stat(outFilename, &outbuf);
11233     if (status < 0) {
11234         cmailMailedMove = FALSE;
11235     } else {
11236         status = stat(inFilename, &inbuf);
11237         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11238     }
11239
11240     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11241        counts the games, notes how each one terminated, etc.
11242
11243        It would be nice to remove this kludge and instead gather all
11244        the information while building the game list.  (And to keep it
11245        in the game list nodes instead of having a bunch of fixed-size
11246        parallel arrays.)  Note this will require getting each game's
11247        termination from the PGN tags, as the game list builder does
11248        not process the game moves.  --mann
11249        */
11250     cmailMsgLoaded = TRUE;
11251     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11252
11253     /* Load first game in the file or popup game menu */
11254     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11255
11256 #endif /* !WIN32 */
11257     return;
11258 }
11259
11260 int
11261 RegisterMove()
11262 {
11263     FILE *f;
11264     char string[MSG_SIZ];
11265
11266     if (   cmailMailedMove
11267         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11268         return TRUE;            /* Allow free viewing  */
11269     }
11270
11271     /* Unregister move to ensure that we don't leave RegisterMove        */
11272     /* with the move registered when the conditions for registering no   */
11273     /* longer hold                                                       */
11274     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11275         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11276         nCmailMovesRegistered --;
11277
11278         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11279           {
11280               free(cmailCommentList[lastLoadGameNumber - 1]);
11281               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11282           }
11283     }
11284
11285     if (cmailOldMove == -1) {
11286         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11287         return FALSE;
11288     }
11289
11290     if (currentMove > cmailOldMove + 1) {
11291         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11292         return FALSE;
11293     }
11294
11295     if (currentMove < cmailOldMove) {
11296         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11297         return FALSE;
11298     }
11299
11300     if (forwardMostMove > currentMove) {
11301         /* Silently truncate extra moves */
11302         TruncateGame();
11303     }
11304
11305     if (   (currentMove == cmailOldMove + 1)
11306         || (   (currentMove == cmailOldMove)
11307             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11308                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11309         if (gameInfo.result != GameUnfinished) {
11310             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11311         }
11312
11313         if (commentList[currentMove] != NULL) {
11314             cmailCommentList[lastLoadGameNumber - 1]
11315               = StrSave(commentList[currentMove]);
11316         }
11317         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11318
11319         if (appData.debugMode)
11320           fprintf(debugFP, "Saving %s for game %d\n",
11321                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11322
11323         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11324
11325         f = fopen(string, "w");
11326         if (appData.oldSaveStyle) {
11327             SaveGameOldStyle(f); /* also closes the file */
11328
11329             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11330             f = fopen(string, "w");
11331             SavePosition(f, 0, NULL); /* also closes the file */
11332         } else {
11333             fprintf(f, "{--------------\n");
11334             PrintPosition(f, currentMove);
11335             fprintf(f, "--------------}\n\n");
11336
11337             SaveGame(f, 0, NULL); /* also closes the file*/
11338         }
11339
11340         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11341         nCmailMovesRegistered ++;
11342     } else if (nCmailGames == 1) {
11343         DisplayError(_("You have not made a move yet"), 0);
11344         return FALSE;
11345     }
11346
11347     return TRUE;
11348 }
11349
11350 void
11351 MailMoveEvent()
11352 {
11353 #if !WIN32
11354     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11355     FILE *commandOutput;
11356     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11357     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11358     int nBuffers;
11359     int i;
11360     int archived;
11361     char *arcDir;
11362
11363     if (! cmailMsgLoaded) {
11364         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11365         return;
11366     }
11367
11368     if (nCmailGames == nCmailResults) {
11369         DisplayError(_("No unfinished games"), 0);
11370         return;
11371     }
11372
11373 #if CMAIL_PROHIBIT_REMAIL
11374     if (cmailMailedMove) {
11375       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);
11376         DisplayError(msg, 0);
11377         return;
11378     }
11379 #endif
11380
11381     if (! (cmailMailedMove || RegisterMove())) return;
11382
11383     if (   cmailMailedMove
11384         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11385       snprintf(string, MSG_SIZ, partCommandString,
11386                appData.debugMode ? " -v" : "", appData.cmailGameName);
11387         commandOutput = popen(string, "r");
11388
11389         if (commandOutput == NULL) {
11390             DisplayError(_("Failed to invoke cmail"), 0);
11391         } else {
11392             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11393                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11394             }
11395             if (nBuffers > 1) {
11396                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11397                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11398                 nBytes = MSG_SIZ - 1;
11399             } else {
11400                 (void) memcpy(msg, buffer, nBytes);
11401             }
11402             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11403
11404             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11405                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11406
11407                 archived = TRUE;
11408                 for (i = 0; i < nCmailGames; i ++) {
11409                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11410                         archived = FALSE;
11411                     }
11412                 }
11413                 if (   archived
11414                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11415                         != NULL)) {
11416                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11417                            arcDir,
11418                            appData.cmailGameName,
11419                            gameInfo.date);
11420                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11421                     cmailMsgLoaded = FALSE;
11422                 }
11423             }
11424
11425             DisplayInformation(msg);
11426             pclose(commandOutput);
11427         }
11428     } else {
11429         if ((*cmailMsg) != '\0') {
11430             DisplayInformation(cmailMsg);
11431         }
11432     }
11433
11434     return;
11435 #endif /* !WIN32 */
11436 }
11437
11438 char *
11439 CmailMsg()
11440 {
11441 #if WIN32
11442     return NULL;
11443 #else
11444     int  prependComma = 0;
11445     char number[5];
11446     char string[MSG_SIZ];       /* Space for game-list */
11447     int  i;
11448
11449     if (!cmailMsgLoaded) return "";
11450
11451     if (cmailMailedMove) {
11452       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11453     } else {
11454         /* Create a list of games left */
11455       snprintf(string, MSG_SIZ, "[");
11456         for (i = 0; i < nCmailGames; i ++) {
11457             if (! (   cmailMoveRegistered[i]
11458                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11459                 if (prependComma) {
11460                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11461                 } else {
11462                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11463                     prependComma = 1;
11464                 }
11465
11466                 strcat(string, number);
11467             }
11468         }
11469         strcat(string, "]");
11470
11471         if (nCmailMovesRegistered + nCmailResults == 0) {
11472             switch (nCmailGames) {
11473               case 1:
11474                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11475                 break;
11476
11477               case 2:
11478                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11479                 break;
11480
11481               default:
11482                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11483                          nCmailGames);
11484                 break;
11485             }
11486         } else {
11487             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11488               case 1:
11489                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11490                          string);
11491                 break;
11492
11493               case 0:
11494                 if (nCmailResults == nCmailGames) {
11495                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11496                 } else {
11497                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11498                 }
11499                 break;
11500
11501               default:
11502                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11503                          string);
11504             }
11505         }
11506     }
11507     return cmailMsg;
11508 #endif /* WIN32 */
11509 }
11510
11511 void
11512 ResetGameEvent()
11513 {
11514     if (gameMode == Training)
11515       SetTrainingModeOff();
11516
11517     Reset(TRUE, TRUE);
11518     cmailMsgLoaded = FALSE;
11519     if (appData.icsActive) {
11520       SendToICS(ics_prefix);
11521       SendToICS("refresh\n");
11522     }
11523 }
11524
11525 void
11526 ExitEvent(status)
11527      int status;
11528 {
11529     exiting++;
11530     if (exiting > 2) {
11531       /* Give up on clean exit */
11532       exit(status);
11533     }
11534     if (exiting > 1) {
11535       /* Keep trying for clean exit */
11536       return;
11537     }
11538
11539     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11540
11541     if (telnetISR != NULL) {
11542       RemoveInputSource(telnetISR);
11543     }
11544     if (icsPR != NoProc) {
11545       DestroyChildProcess(icsPR, TRUE);
11546     }
11547
11548     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11549     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11550
11551     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11552     /* make sure this other one finishes before killing it!                  */
11553     if(endingGame) { int count = 0;
11554         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11555         while(endingGame && count++ < 10) DoSleep(1);
11556         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11557     }
11558
11559     /* Kill off chess programs */
11560     if (first.pr != NoProc) {
11561         ExitAnalyzeMode();
11562
11563         DoSleep( appData.delayBeforeQuit );
11564         SendToProgram("quit\n", &first);
11565         DoSleep( appData.delayAfterQuit );
11566         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11567     }
11568     if (second.pr != NoProc) {
11569         DoSleep( appData.delayBeforeQuit );
11570         SendToProgram("quit\n", &second);
11571         DoSleep( appData.delayAfterQuit );
11572         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11573     }
11574     if (first.isr != NULL) {
11575         RemoveInputSource(first.isr);
11576     }
11577     if (second.isr != NULL) {
11578         RemoveInputSource(second.isr);
11579     }
11580
11581     ShutDownFrontEnd();
11582     exit(status);
11583 }
11584
11585 void
11586 PauseEvent()
11587 {
11588     if (appData.debugMode)
11589         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11590     if (pausing) {
11591         pausing = FALSE;
11592         ModeHighlight();
11593         if (gameMode == MachinePlaysWhite ||
11594             gameMode == MachinePlaysBlack) {
11595             StartClocks();
11596         } else {
11597             DisplayBothClocks();
11598         }
11599         if (gameMode == PlayFromGameFile) {
11600             if (appData.timeDelay >= 0)
11601                 AutoPlayGameLoop();
11602         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11603             Reset(FALSE, TRUE);
11604             SendToICS(ics_prefix);
11605             SendToICS("refresh\n");
11606         } else if (currentMove < forwardMostMove) {
11607             ForwardInner(forwardMostMove);
11608         }
11609         pauseExamInvalid = FALSE;
11610     } else {
11611         switch (gameMode) {
11612           default:
11613             return;
11614           case IcsExamining:
11615             pauseExamForwardMostMove = forwardMostMove;
11616             pauseExamInvalid = FALSE;
11617             /* fall through */
11618           case IcsObserving:
11619           case IcsPlayingWhite:
11620           case IcsPlayingBlack:
11621             pausing = TRUE;
11622             ModeHighlight();
11623             return;
11624           case PlayFromGameFile:
11625             (void) StopLoadGameTimer();
11626             pausing = TRUE;
11627             ModeHighlight();
11628             break;
11629           case BeginningOfGame:
11630             if (appData.icsActive) return;
11631             /* else fall through */
11632           case MachinePlaysWhite:
11633           case MachinePlaysBlack:
11634           case TwoMachinesPlay:
11635             if (forwardMostMove == 0)
11636               return;           /* don't pause if no one has moved */
11637             if ((gameMode == MachinePlaysWhite &&
11638                  !WhiteOnMove(forwardMostMove)) ||
11639                 (gameMode == MachinePlaysBlack &&
11640                  WhiteOnMove(forwardMostMove))) {
11641                 StopClocks();
11642             }
11643             pausing = TRUE;
11644             ModeHighlight();
11645             break;
11646         }
11647     }
11648 }
11649
11650 void
11651 EditCommentEvent()
11652 {
11653     char title[MSG_SIZ];
11654
11655     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11656       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11657     } else {
11658       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11659                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11660                parseList[currentMove - 1]);
11661     }
11662
11663     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11664 }
11665
11666
11667 void
11668 EditTagsEvent()
11669 {
11670     char *tags = PGNTags(&gameInfo);
11671     EditTagsPopUp(tags, NULL);
11672     free(tags);
11673 }
11674
11675 void
11676 AnalyzeModeEvent()
11677 {
11678     if (appData.noChessProgram || gameMode == AnalyzeMode)
11679       return;
11680
11681     if (gameMode != AnalyzeFile) {
11682         if (!appData.icsEngineAnalyze) {
11683                EditGameEvent();
11684                if (gameMode != EditGame) return;
11685         }
11686         ResurrectChessProgram();
11687         SendToProgram("analyze\n", &first);
11688         first.analyzing = TRUE;
11689         /*first.maybeThinking = TRUE;*/
11690         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11691         EngineOutputPopUp();
11692     }
11693     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11694     pausing = FALSE;
11695     ModeHighlight();
11696     SetGameInfo();
11697
11698     StartAnalysisClock();
11699     GetTimeMark(&lastNodeCountTime);
11700     lastNodeCount = 0;
11701 }
11702
11703 void
11704 AnalyzeFileEvent()
11705 {
11706     if (appData.noChessProgram || gameMode == AnalyzeFile)
11707       return;
11708
11709     if (gameMode != AnalyzeMode) {
11710         EditGameEvent();
11711         if (gameMode != EditGame) return;
11712         ResurrectChessProgram();
11713         SendToProgram("analyze\n", &first);
11714         first.analyzing = TRUE;
11715         /*first.maybeThinking = TRUE;*/
11716         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11717         EngineOutputPopUp();
11718     }
11719     gameMode = AnalyzeFile;
11720     pausing = FALSE;
11721     ModeHighlight();
11722     SetGameInfo();
11723
11724     StartAnalysisClock();
11725     GetTimeMark(&lastNodeCountTime);
11726     lastNodeCount = 0;
11727 }
11728
11729 void
11730 MachineWhiteEvent()
11731 {
11732     char buf[MSG_SIZ];
11733     char *bookHit = NULL;
11734
11735     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11736       return;
11737
11738
11739     if (gameMode == PlayFromGameFile ||
11740         gameMode == TwoMachinesPlay  ||
11741         gameMode == Training         ||
11742         gameMode == AnalyzeMode      ||
11743         gameMode == EndOfGame)
11744         EditGameEvent();
11745
11746     if (gameMode == EditPosition)
11747         EditPositionDone(TRUE);
11748
11749     if (!WhiteOnMove(currentMove)) {
11750         DisplayError(_("It is not White's turn"), 0);
11751         return;
11752     }
11753
11754     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11755       ExitAnalyzeMode();
11756
11757     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11758         gameMode == AnalyzeFile)
11759         TruncateGame();
11760
11761     ResurrectChessProgram();    /* in case it isn't running */
11762     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11763         gameMode = MachinePlaysWhite;
11764         ResetClocks();
11765     } else
11766     gameMode = MachinePlaysWhite;
11767     pausing = FALSE;
11768     ModeHighlight();
11769     SetGameInfo();
11770     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11771     DisplayTitle(buf);
11772     if (first.sendName) {
11773       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11774       SendToProgram(buf, &first);
11775     }
11776     if (first.sendTime) {
11777       if (first.useColors) {
11778         SendToProgram("black\n", &first); /*gnu kludge*/
11779       }
11780       SendTimeRemaining(&first, TRUE);
11781     }
11782     if (first.useColors) {
11783       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11784     }
11785     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11786     SetMachineThinkingEnables();
11787     first.maybeThinking = TRUE;
11788     StartClocks();
11789     firstMove = FALSE;
11790
11791     if (appData.autoFlipView && !flipView) {
11792       flipView = !flipView;
11793       DrawPosition(FALSE, NULL);
11794       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11795     }
11796
11797     if(bookHit) { // [HGM] book: simulate book reply
11798         static char bookMove[MSG_SIZ]; // a bit generous?
11799
11800         programStats.nodes = programStats.depth = programStats.time =
11801         programStats.score = programStats.got_only_move = 0;
11802         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11803
11804         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11805         strcat(bookMove, bookHit);
11806         HandleMachineMove(bookMove, &first);
11807     }
11808 }
11809
11810 void
11811 MachineBlackEvent()
11812 {
11813   char buf[MSG_SIZ];
11814   char *bookHit = NULL;
11815
11816     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11817         return;
11818
11819
11820     if (gameMode == PlayFromGameFile ||
11821         gameMode == TwoMachinesPlay  ||
11822         gameMode == Training         ||
11823         gameMode == AnalyzeMode      ||
11824         gameMode == EndOfGame)
11825         EditGameEvent();
11826
11827     if (gameMode == EditPosition)
11828         EditPositionDone(TRUE);
11829
11830     if (WhiteOnMove(currentMove)) {
11831         DisplayError(_("It is not Black's turn"), 0);
11832         return;
11833     }
11834
11835     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11836       ExitAnalyzeMode();
11837
11838     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11839         gameMode == AnalyzeFile)
11840         TruncateGame();
11841
11842     ResurrectChessProgram();    /* in case it isn't running */
11843     gameMode = MachinePlaysBlack;
11844     pausing = FALSE;
11845     ModeHighlight();
11846     SetGameInfo();
11847     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11848     DisplayTitle(buf);
11849     if (first.sendName) {
11850       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11851       SendToProgram(buf, &first);
11852     }
11853     if (first.sendTime) {
11854       if (first.useColors) {
11855         SendToProgram("white\n", &first); /*gnu kludge*/
11856       }
11857       SendTimeRemaining(&first, FALSE);
11858     }
11859     if (first.useColors) {
11860       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11861     }
11862     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11863     SetMachineThinkingEnables();
11864     first.maybeThinking = TRUE;
11865     StartClocks();
11866
11867     if (appData.autoFlipView && flipView) {
11868       flipView = !flipView;
11869       DrawPosition(FALSE, NULL);
11870       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11871     }
11872     if(bookHit) { // [HGM] book: simulate book reply
11873         static char bookMove[MSG_SIZ]; // a bit generous?
11874
11875         programStats.nodes = programStats.depth = programStats.time =
11876         programStats.score = programStats.got_only_move = 0;
11877         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11878
11879         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11880         strcat(bookMove, bookHit);
11881         HandleMachineMove(bookMove, &first);
11882     }
11883 }
11884
11885
11886 void
11887 DisplayTwoMachinesTitle()
11888 {
11889     char buf[MSG_SIZ];
11890     if (appData.matchGames > 0) {
11891         if (first.twoMachinesColor[0] == 'w') {
11892           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11893                    gameInfo.white, gameInfo.black,
11894                    first.matchWins, second.matchWins,
11895                    matchGame - 1 - (first.matchWins + second.matchWins));
11896         } else {
11897           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11898                    gameInfo.white, gameInfo.black,
11899                    second.matchWins, first.matchWins,
11900                    matchGame - 1 - (first.matchWins + second.matchWins));
11901         }
11902     } else {
11903       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11904     }
11905     DisplayTitle(buf);
11906 }
11907
11908 void
11909 SettingsMenuIfReady()
11910 {
11911   if (second.lastPing != second.lastPong) {
11912     DisplayMessage("", _("Waiting for second chess program"));
11913     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11914     return;
11915   }
11916   ThawUI();
11917   DisplayMessage("", "");
11918   SettingsPopUp(&second);
11919 }
11920
11921 int
11922 WaitForSecond(DelayedEventCallback retry)
11923 {
11924     if (second.pr == NULL) {
11925         StartChessProgram(&second);
11926         if (second.protocolVersion == 1) {
11927           retry();
11928         } else {
11929           /* kludge: allow timeout for initial "feature" command */
11930           FreezeUI();
11931           DisplayMessage("", _("Starting second chess program"));
11932           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11933         }
11934         return 1;
11935     }
11936     return 0;
11937 }
11938
11939 void
11940 TwoMachinesEvent P((void))
11941 {
11942     int i;
11943     char buf[MSG_SIZ];
11944     ChessProgramState *onmove;
11945     char *bookHit = NULL;
11946
11947     if (appData.noChessProgram) return;
11948
11949     switch (gameMode) {
11950       case TwoMachinesPlay:
11951         return;
11952       case MachinePlaysWhite:
11953       case MachinePlaysBlack:
11954         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11955             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11956             return;
11957         }
11958         /* fall through */
11959       case BeginningOfGame:
11960       case PlayFromGameFile:
11961       case EndOfGame:
11962         EditGameEvent();
11963         if (gameMode != EditGame) return;
11964         break;
11965       case EditPosition:
11966         EditPositionDone(TRUE);
11967         break;
11968       case AnalyzeMode:
11969       case AnalyzeFile:
11970         ExitAnalyzeMode();
11971         break;
11972       case EditGame:
11973       default:
11974         break;
11975     }
11976
11977 //    forwardMostMove = currentMove;
11978     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11979     ResurrectChessProgram();    /* in case first program isn't running */
11980
11981     if(WaitForSecond(TwoMachinesEventIfReady)) return;
11982     DisplayMessage("", "");
11983     InitChessProgram(&second, FALSE);
11984     SendToProgram("force\n", &second);
11985     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11986       ScheduleDelayedEvent(TwoMachinesEvent, 10);
11987       return;
11988     }
11989     if (startedFromSetupPosition) {
11990         SendBoard(&second, backwardMostMove);
11991     if (appData.debugMode) {
11992         fprintf(debugFP, "Two Machines\n");
11993     }
11994     }
11995     for (i = backwardMostMove; i < forwardMostMove; i++) {
11996         SendMoveToProgram(i, &second);
11997     }
11998
11999     gameMode = TwoMachinesPlay;
12000     pausing = FALSE;
12001     ModeHighlight();
12002     SetGameInfo();
12003     DisplayTwoMachinesTitle();
12004     firstMove = TRUE;
12005     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12006         onmove = &first;
12007     } else {
12008         onmove = &second;
12009     }
12010
12011     SendToProgram(first.computerString, &first);
12012     if (first.sendName) {
12013       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12014       SendToProgram(buf, &first);
12015     }
12016     SendToProgram(second.computerString, &second);
12017     if (second.sendName) {
12018       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12019       SendToProgram(buf, &second);
12020     }
12021
12022     ResetClocks();
12023     if (!first.sendTime || !second.sendTime) {
12024         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12025         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12026     }
12027     if (onmove->sendTime) {
12028       if (onmove->useColors) {
12029         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12030       }
12031       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12032     }
12033     if (onmove->useColors) {
12034       SendToProgram(onmove->twoMachinesColor, onmove);
12035     }
12036     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12037 //    SendToProgram("go\n", onmove);
12038     onmove->maybeThinking = TRUE;
12039     SetMachineThinkingEnables();
12040
12041     StartClocks();
12042
12043     if(bookHit) { // [HGM] book: simulate book reply
12044         static char bookMove[MSG_SIZ]; // a bit generous?
12045
12046         programStats.nodes = programStats.depth = programStats.time =
12047         programStats.score = programStats.got_only_move = 0;
12048         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12049
12050         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12051         strcat(bookMove, bookHit);
12052         savedMessage = bookMove; // args for deferred call
12053         savedState = onmove;
12054         ScheduleDelayedEvent(DeferredBookMove, 1);
12055     }
12056 }
12057
12058 void
12059 TrainingEvent()
12060 {
12061     if (gameMode == Training) {
12062       SetTrainingModeOff();
12063       gameMode = PlayFromGameFile;
12064       DisplayMessage("", _("Training mode off"));
12065     } else {
12066       gameMode = Training;
12067       animateTraining = appData.animate;
12068
12069       /* make sure we are not already at the end of the game */
12070       if (currentMove < forwardMostMove) {
12071         SetTrainingModeOn();
12072         DisplayMessage("", _("Training mode on"));
12073       } else {
12074         gameMode = PlayFromGameFile;
12075         DisplayError(_("Already at end of game"), 0);
12076       }
12077     }
12078     ModeHighlight();
12079 }
12080
12081 void
12082 IcsClientEvent()
12083 {
12084     if (!appData.icsActive) return;
12085     switch (gameMode) {
12086       case IcsPlayingWhite:
12087       case IcsPlayingBlack:
12088       case IcsObserving:
12089       case IcsIdle:
12090       case BeginningOfGame:
12091       case IcsExamining:
12092         return;
12093
12094       case EditGame:
12095         break;
12096
12097       case EditPosition:
12098         EditPositionDone(TRUE);
12099         break;
12100
12101       case AnalyzeMode:
12102       case AnalyzeFile:
12103         ExitAnalyzeMode();
12104         break;
12105
12106       default:
12107         EditGameEvent();
12108         break;
12109     }
12110
12111     gameMode = IcsIdle;
12112     ModeHighlight();
12113     return;
12114 }
12115
12116
12117 void
12118 EditGameEvent()
12119 {
12120     int i;
12121
12122     switch (gameMode) {
12123       case Training:
12124         SetTrainingModeOff();
12125         break;
12126       case MachinePlaysWhite:
12127       case MachinePlaysBlack:
12128       case BeginningOfGame:
12129         SendToProgram("force\n", &first);
12130         SetUserThinkingEnables();
12131         break;
12132       case PlayFromGameFile:
12133         (void) StopLoadGameTimer();
12134         if (gameFileFP != NULL) {
12135             gameFileFP = NULL;
12136         }
12137         break;
12138       case EditPosition:
12139         EditPositionDone(TRUE);
12140         break;
12141       case AnalyzeMode:
12142       case AnalyzeFile:
12143         ExitAnalyzeMode();
12144         SendToProgram("force\n", &first);
12145         break;
12146       case TwoMachinesPlay:
12147         GameEnds(EndOfFile, NULL, GE_PLAYER);
12148         ResurrectChessProgram();
12149         SetUserThinkingEnables();
12150         break;
12151       case EndOfGame:
12152         ResurrectChessProgram();
12153         break;
12154       case IcsPlayingBlack:
12155       case IcsPlayingWhite:
12156         DisplayError(_("Warning: You are still playing a game"), 0);
12157         break;
12158       case IcsObserving:
12159         DisplayError(_("Warning: You are still observing a game"), 0);
12160         break;
12161       case IcsExamining:
12162         DisplayError(_("Warning: You are still examining a game"), 0);
12163         break;
12164       case IcsIdle:
12165         break;
12166       case EditGame:
12167       default:
12168         return;
12169     }
12170
12171     pausing = FALSE;
12172     StopClocks();
12173     first.offeredDraw = second.offeredDraw = 0;
12174
12175     if (gameMode == PlayFromGameFile) {
12176         whiteTimeRemaining = timeRemaining[0][currentMove];
12177         blackTimeRemaining = timeRemaining[1][currentMove];
12178         DisplayTitle("");
12179     }
12180
12181     if (gameMode == MachinePlaysWhite ||
12182         gameMode == MachinePlaysBlack ||
12183         gameMode == TwoMachinesPlay ||
12184         gameMode == EndOfGame) {
12185         i = forwardMostMove;
12186         while (i > currentMove) {
12187             SendToProgram("undo\n", &first);
12188             i--;
12189         }
12190         whiteTimeRemaining = timeRemaining[0][currentMove];
12191         blackTimeRemaining = timeRemaining[1][currentMove];
12192         DisplayBothClocks();
12193         if (whiteFlag || blackFlag) {
12194             whiteFlag = blackFlag = 0;
12195         }
12196         DisplayTitle("");
12197     }
12198
12199     gameMode = EditGame;
12200     ModeHighlight();
12201     SetGameInfo();
12202 }
12203
12204
12205 void
12206 EditPositionEvent()
12207 {
12208     if (gameMode == EditPosition) {
12209         EditGameEvent();
12210         return;
12211     }
12212
12213     EditGameEvent();
12214     if (gameMode != EditGame) return;
12215
12216     gameMode = EditPosition;
12217     ModeHighlight();
12218     SetGameInfo();
12219     if (currentMove > 0)
12220       CopyBoard(boards[0], boards[currentMove]);
12221
12222     blackPlaysFirst = !WhiteOnMove(currentMove);
12223     ResetClocks();
12224     currentMove = forwardMostMove = backwardMostMove = 0;
12225     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12226     DisplayMove(-1);
12227 }
12228
12229 void
12230 ExitAnalyzeMode()
12231 {
12232     /* [DM] icsEngineAnalyze - possible call from other functions */
12233     if (appData.icsEngineAnalyze) {
12234         appData.icsEngineAnalyze = FALSE;
12235
12236         DisplayMessage("",_("Close ICS engine analyze..."));
12237     }
12238     if (first.analysisSupport && first.analyzing) {
12239       SendToProgram("exit\n", &first);
12240       first.analyzing = FALSE;
12241     }
12242     thinkOutput[0] = NULLCHAR;
12243 }
12244
12245 void
12246 EditPositionDone(Boolean fakeRights)
12247 {
12248     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12249
12250     startedFromSetupPosition = TRUE;
12251     InitChessProgram(&first, FALSE);
12252     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12253       boards[0][EP_STATUS] = EP_NONE;
12254       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12255     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12256         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12257         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12258       } else boards[0][CASTLING][2] = NoRights;
12259     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12260         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12261         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12262       } else boards[0][CASTLING][5] = NoRights;
12263     }
12264     SendToProgram("force\n", &first);
12265     if (blackPlaysFirst) {
12266         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12267         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12268         currentMove = forwardMostMove = backwardMostMove = 1;
12269         CopyBoard(boards[1], boards[0]);
12270     } else {
12271         currentMove = forwardMostMove = backwardMostMove = 0;
12272     }
12273     SendBoard(&first, forwardMostMove);
12274     if (appData.debugMode) {
12275         fprintf(debugFP, "EditPosDone\n");
12276     }
12277     DisplayTitle("");
12278     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12279     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12280     gameMode = EditGame;
12281     ModeHighlight();
12282     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12283     ClearHighlights(); /* [AS] */
12284 }
12285
12286 /* Pause for `ms' milliseconds */
12287 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12288 void
12289 TimeDelay(ms)
12290      long ms;
12291 {
12292     TimeMark m1, m2;
12293
12294     GetTimeMark(&m1);
12295     do {
12296         GetTimeMark(&m2);
12297     } while (SubtractTimeMarks(&m2, &m1) < ms);
12298 }
12299
12300 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12301 void
12302 SendMultiLineToICS(buf)
12303      char *buf;
12304 {
12305     char temp[MSG_SIZ+1], *p;
12306     int len;
12307
12308     len = strlen(buf);
12309     if (len > MSG_SIZ)
12310       len = MSG_SIZ;
12311
12312     strncpy(temp, buf, len);
12313     temp[len] = 0;
12314
12315     p = temp;
12316     while (*p) {
12317         if (*p == '\n' || *p == '\r')
12318           *p = ' ';
12319         ++p;
12320     }
12321
12322     strcat(temp, "\n");
12323     SendToICS(temp);
12324     SendToPlayer(temp, strlen(temp));
12325 }
12326
12327 void
12328 SetWhiteToPlayEvent()
12329 {
12330     if (gameMode == EditPosition) {
12331         blackPlaysFirst = FALSE;
12332         DisplayBothClocks();    /* works because currentMove is 0 */
12333     } else if (gameMode == IcsExamining) {
12334         SendToICS(ics_prefix);
12335         SendToICS("tomove white\n");
12336     }
12337 }
12338
12339 void
12340 SetBlackToPlayEvent()
12341 {
12342     if (gameMode == EditPosition) {
12343         blackPlaysFirst = TRUE;
12344         currentMove = 1;        /* kludge */
12345         DisplayBothClocks();
12346         currentMove = 0;
12347     } else if (gameMode == IcsExamining) {
12348         SendToICS(ics_prefix);
12349         SendToICS("tomove black\n");
12350     }
12351 }
12352
12353 void
12354 EditPositionMenuEvent(selection, x, y)
12355      ChessSquare selection;
12356      int x, y;
12357 {
12358     char buf[MSG_SIZ];
12359     ChessSquare piece = boards[0][y][x];
12360
12361     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12362
12363     switch (selection) {
12364       case ClearBoard:
12365         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12366             SendToICS(ics_prefix);
12367             SendToICS("bsetup clear\n");
12368         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12369             SendToICS(ics_prefix);
12370             SendToICS("clearboard\n");
12371         } else {
12372             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12373                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12374                 for (y = 0; y < BOARD_HEIGHT; y++) {
12375                     if (gameMode == IcsExamining) {
12376                         if (boards[currentMove][y][x] != EmptySquare) {
12377                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12378                                     AAA + x, ONE + y);
12379                             SendToICS(buf);
12380                         }
12381                     } else {
12382                         boards[0][y][x] = p;
12383                     }
12384                 }
12385             }
12386         }
12387         if (gameMode == EditPosition) {
12388             DrawPosition(FALSE, boards[0]);
12389         }
12390         break;
12391
12392       case WhitePlay:
12393         SetWhiteToPlayEvent();
12394         break;
12395
12396       case BlackPlay:
12397         SetBlackToPlayEvent();
12398         break;
12399
12400       case EmptySquare:
12401         if (gameMode == IcsExamining) {
12402             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12403             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12404             SendToICS(buf);
12405         } else {
12406             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12407                 if(x == BOARD_LEFT-2) {
12408                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12409                     boards[0][y][1] = 0;
12410                 } else
12411                 if(x == BOARD_RGHT+1) {
12412                     if(y >= gameInfo.holdingsSize) break;
12413                     boards[0][y][BOARD_WIDTH-2] = 0;
12414                 } else break;
12415             }
12416             boards[0][y][x] = EmptySquare;
12417             DrawPosition(FALSE, boards[0]);
12418         }
12419         break;
12420
12421       case PromotePiece:
12422         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12423            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12424             selection = (ChessSquare) (PROMOTED piece);
12425         } else if(piece == EmptySquare) selection = WhiteSilver;
12426         else selection = (ChessSquare)((int)piece - 1);
12427         goto defaultlabel;
12428
12429       case DemotePiece:
12430         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12431            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12432             selection = (ChessSquare) (DEMOTED piece);
12433         } else if(piece == EmptySquare) selection = BlackSilver;
12434         else selection = (ChessSquare)((int)piece + 1);
12435         goto defaultlabel;
12436
12437       case WhiteQueen:
12438       case BlackQueen:
12439         if(gameInfo.variant == VariantShatranj ||
12440            gameInfo.variant == VariantXiangqi  ||
12441            gameInfo.variant == VariantCourier  ||
12442            gameInfo.variant == VariantMakruk     )
12443             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12444         goto defaultlabel;
12445
12446       case WhiteKing:
12447       case BlackKing:
12448         if(gameInfo.variant == VariantXiangqi)
12449             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12450         if(gameInfo.variant == VariantKnightmate)
12451             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12452       default:
12453         defaultlabel:
12454         if (gameMode == IcsExamining) {
12455             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12456             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12457                      PieceToChar(selection), AAA + x, ONE + y);
12458             SendToICS(buf);
12459         } else {
12460             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12461                 int n;
12462                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12463                     n = PieceToNumber(selection - BlackPawn);
12464                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12465                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12466                     boards[0][BOARD_HEIGHT-1-n][1]++;
12467                 } else
12468                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12469                     n = PieceToNumber(selection);
12470                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12471                     boards[0][n][BOARD_WIDTH-1] = selection;
12472                     boards[0][n][BOARD_WIDTH-2]++;
12473                 }
12474             } else
12475             boards[0][y][x] = selection;
12476             DrawPosition(TRUE, boards[0]);
12477         }
12478         break;
12479     }
12480 }
12481
12482
12483 void
12484 DropMenuEvent(selection, x, y)
12485      ChessSquare selection;
12486      int x, y;
12487 {
12488     ChessMove moveType;
12489
12490     switch (gameMode) {
12491       case IcsPlayingWhite:
12492       case MachinePlaysBlack:
12493         if (!WhiteOnMove(currentMove)) {
12494             DisplayMoveError(_("It is Black's turn"));
12495             return;
12496         }
12497         moveType = WhiteDrop;
12498         break;
12499       case IcsPlayingBlack:
12500       case MachinePlaysWhite:
12501         if (WhiteOnMove(currentMove)) {
12502             DisplayMoveError(_("It is White's turn"));
12503             return;
12504         }
12505         moveType = BlackDrop;
12506         break;
12507       case EditGame:
12508         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12509         break;
12510       default:
12511         return;
12512     }
12513
12514     if (moveType == BlackDrop && selection < BlackPawn) {
12515       selection = (ChessSquare) ((int) selection
12516                                  + (int) BlackPawn - (int) WhitePawn);
12517     }
12518     if (boards[currentMove][y][x] != EmptySquare) {
12519         DisplayMoveError(_("That square is occupied"));
12520         return;
12521     }
12522
12523     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12524 }
12525
12526 void
12527 AcceptEvent()
12528 {
12529     /* Accept a pending offer of any kind from opponent */
12530
12531     if (appData.icsActive) {
12532         SendToICS(ics_prefix);
12533         SendToICS("accept\n");
12534     } else if (cmailMsgLoaded) {
12535         if (currentMove == cmailOldMove &&
12536             commentList[cmailOldMove] != NULL &&
12537             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12538                    "Black offers a draw" : "White offers a draw")) {
12539             TruncateGame();
12540             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12541             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12542         } else {
12543             DisplayError(_("There is no pending offer on this move"), 0);
12544             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12545         }
12546     } else {
12547         /* Not used for offers from chess program */
12548     }
12549 }
12550
12551 void
12552 DeclineEvent()
12553 {
12554     /* Decline a pending offer of any kind from opponent */
12555
12556     if (appData.icsActive) {
12557         SendToICS(ics_prefix);
12558         SendToICS("decline\n");
12559     } else if (cmailMsgLoaded) {
12560         if (currentMove == cmailOldMove &&
12561             commentList[cmailOldMove] != NULL &&
12562             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12563                    "Black offers a draw" : "White offers a draw")) {
12564 #ifdef NOTDEF
12565             AppendComment(cmailOldMove, "Draw declined", TRUE);
12566             DisplayComment(cmailOldMove - 1, "Draw declined");
12567 #endif /*NOTDEF*/
12568         } else {
12569             DisplayError(_("There is no pending offer on this move"), 0);
12570         }
12571     } else {
12572         /* Not used for offers from chess program */
12573     }
12574 }
12575
12576 void
12577 RematchEvent()
12578 {
12579     /* Issue ICS rematch command */
12580     if (appData.icsActive) {
12581         SendToICS(ics_prefix);
12582         SendToICS("rematch\n");
12583     }
12584 }
12585
12586 void
12587 CallFlagEvent()
12588 {
12589     /* Call your opponent's flag (claim a win on time) */
12590     if (appData.icsActive) {
12591         SendToICS(ics_prefix);
12592         SendToICS("flag\n");
12593     } else {
12594         switch (gameMode) {
12595           default:
12596             return;
12597           case MachinePlaysWhite:
12598             if (whiteFlag) {
12599                 if (blackFlag)
12600                   GameEnds(GameIsDrawn, "Both players ran out of time",
12601                            GE_PLAYER);
12602                 else
12603                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12604             } else {
12605                 DisplayError(_("Your opponent is not out of time"), 0);
12606             }
12607             break;
12608           case MachinePlaysBlack:
12609             if (blackFlag) {
12610                 if (whiteFlag)
12611                   GameEnds(GameIsDrawn, "Both players ran out of time",
12612                            GE_PLAYER);
12613                 else
12614                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12615             } else {
12616                 DisplayError(_("Your opponent is not out of time"), 0);
12617             }
12618             break;
12619         }
12620     }
12621 }
12622
12623 void
12624 DrawEvent()
12625 {
12626     /* Offer draw or accept pending draw offer from opponent */
12627
12628     if (appData.icsActive) {
12629         /* Note: tournament rules require draw offers to be
12630            made after you make your move but before you punch
12631            your clock.  Currently ICS doesn't let you do that;
12632            instead, you immediately punch your clock after making
12633            a move, but you can offer a draw at any time. */
12634
12635         SendToICS(ics_prefix);
12636         SendToICS("draw\n");
12637         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12638     } else if (cmailMsgLoaded) {
12639         if (currentMove == cmailOldMove &&
12640             commentList[cmailOldMove] != NULL &&
12641             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12642                    "Black offers a draw" : "White offers a draw")) {
12643             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12644             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12645         } else if (currentMove == cmailOldMove + 1) {
12646             char *offer = WhiteOnMove(cmailOldMove) ?
12647               "White offers a draw" : "Black offers a draw";
12648             AppendComment(currentMove, offer, TRUE);
12649             DisplayComment(currentMove - 1, offer);
12650             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12651         } else {
12652             DisplayError(_("You must make your move before offering a draw"), 0);
12653             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12654         }
12655     } else if (first.offeredDraw) {
12656         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12657     } else {
12658         if (first.sendDrawOffers) {
12659             SendToProgram("draw\n", &first);
12660             userOfferedDraw = TRUE;
12661         }
12662     }
12663 }
12664
12665 void
12666 AdjournEvent()
12667 {
12668     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12669
12670     if (appData.icsActive) {
12671         SendToICS(ics_prefix);
12672         SendToICS("adjourn\n");
12673     } else {
12674         /* Currently GNU Chess doesn't offer or accept Adjourns */
12675     }
12676 }
12677
12678
12679 void
12680 AbortEvent()
12681 {
12682     /* Offer Abort or accept pending Abort offer from opponent */
12683
12684     if (appData.icsActive) {
12685         SendToICS(ics_prefix);
12686         SendToICS("abort\n");
12687     } else {
12688         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12689     }
12690 }
12691
12692 void
12693 ResignEvent()
12694 {
12695     /* Resign.  You can do this even if it's not your turn. */
12696
12697     if (appData.icsActive) {
12698         SendToICS(ics_prefix);
12699         SendToICS("resign\n");
12700     } else {
12701         switch (gameMode) {
12702           case MachinePlaysWhite:
12703             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12704             break;
12705           case MachinePlaysBlack:
12706             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12707             break;
12708           case EditGame:
12709             if (cmailMsgLoaded) {
12710                 TruncateGame();
12711                 if (WhiteOnMove(cmailOldMove)) {
12712                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12713                 } else {
12714                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12715                 }
12716                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12717             }
12718             break;
12719           default:
12720             break;
12721         }
12722     }
12723 }
12724
12725
12726 void
12727 StopObservingEvent()
12728 {
12729     /* Stop observing current games */
12730     SendToICS(ics_prefix);
12731     SendToICS("unobserve\n");
12732 }
12733
12734 void
12735 StopExaminingEvent()
12736 {
12737     /* Stop observing current game */
12738     SendToICS(ics_prefix);
12739     SendToICS("unexamine\n");
12740 }
12741
12742 void
12743 ForwardInner(target)
12744      int target;
12745 {
12746     int limit;
12747
12748     if (appData.debugMode)
12749         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12750                 target, currentMove, forwardMostMove);
12751
12752     if (gameMode == EditPosition)
12753       return;
12754
12755     if (gameMode == PlayFromGameFile && !pausing)
12756       PauseEvent();
12757
12758     if (gameMode == IcsExamining && pausing)
12759       limit = pauseExamForwardMostMove;
12760     else
12761       limit = forwardMostMove;
12762
12763     if (target > limit) target = limit;
12764
12765     if (target > 0 && moveList[target - 1][0]) {
12766         int fromX, fromY, toX, toY;
12767         toX = moveList[target - 1][2] - AAA;
12768         toY = moveList[target - 1][3] - ONE;
12769         if (moveList[target - 1][1] == '@') {
12770             if (appData.highlightLastMove) {
12771                 SetHighlights(-1, -1, toX, toY);
12772             }
12773         } else {
12774             fromX = moveList[target - 1][0] - AAA;
12775             fromY = moveList[target - 1][1] - ONE;
12776             if (target == currentMove + 1) {
12777                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12778             }
12779             if (appData.highlightLastMove) {
12780                 SetHighlights(fromX, fromY, toX, toY);
12781             }
12782         }
12783     }
12784     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12785         gameMode == Training || gameMode == PlayFromGameFile ||
12786         gameMode == AnalyzeFile) {
12787         while (currentMove < target) {
12788             SendMoveToProgram(currentMove++, &first);
12789         }
12790     } else {
12791         currentMove = target;
12792     }
12793
12794     if (gameMode == EditGame || gameMode == EndOfGame) {
12795         whiteTimeRemaining = timeRemaining[0][currentMove];
12796         blackTimeRemaining = timeRemaining[1][currentMove];
12797     }
12798     DisplayBothClocks();
12799     DisplayMove(currentMove - 1);
12800     DrawPosition(FALSE, boards[currentMove]);
12801     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12802     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12803         DisplayComment(currentMove - 1, commentList[currentMove]);
12804     }
12805 }
12806
12807
12808 void
12809 ForwardEvent()
12810 {
12811     if (gameMode == IcsExamining && !pausing) {
12812         SendToICS(ics_prefix);
12813         SendToICS("forward\n");
12814     } else {
12815         ForwardInner(currentMove + 1);
12816     }
12817 }
12818
12819 void
12820 ToEndEvent()
12821 {
12822     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12823         /* to optimze, we temporarily turn off analysis mode while we feed
12824          * the remaining moves to the engine. Otherwise we get analysis output
12825          * after each move.
12826          */
12827         if (first.analysisSupport) {
12828           SendToProgram("exit\nforce\n", &first);
12829           first.analyzing = FALSE;
12830         }
12831     }
12832
12833     if (gameMode == IcsExamining && !pausing) {
12834         SendToICS(ics_prefix);
12835         SendToICS("forward 999999\n");
12836     } else {
12837         ForwardInner(forwardMostMove);
12838     }
12839
12840     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12841         /* we have fed all the moves, so reactivate analysis mode */
12842         SendToProgram("analyze\n", &first);
12843         first.analyzing = TRUE;
12844         /*first.maybeThinking = TRUE;*/
12845         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12846     }
12847 }
12848
12849 void
12850 BackwardInner(target)
12851      int target;
12852 {
12853     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12854
12855     if (appData.debugMode)
12856         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12857                 target, currentMove, forwardMostMove);
12858
12859     if (gameMode == EditPosition) return;
12860     if (currentMove <= backwardMostMove) {
12861         ClearHighlights();
12862         DrawPosition(full_redraw, boards[currentMove]);
12863         return;
12864     }
12865     if (gameMode == PlayFromGameFile && !pausing)
12866       PauseEvent();
12867
12868     if (moveList[target][0]) {
12869         int fromX, fromY, toX, toY;
12870         toX = moveList[target][2] - AAA;
12871         toY = moveList[target][3] - ONE;
12872         if (moveList[target][1] == '@') {
12873             if (appData.highlightLastMove) {
12874                 SetHighlights(-1, -1, toX, toY);
12875             }
12876         } else {
12877             fromX = moveList[target][0] - AAA;
12878             fromY = moveList[target][1] - ONE;
12879             if (target == currentMove - 1) {
12880                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12881             }
12882             if (appData.highlightLastMove) {
12883                 SetHighlights(fromX, fromY, toX, toY);
12884             }
12885         }
12886     }
12887     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12888         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12889         while (currentMove > target) {
12890             SendToProgram("undo\n", &first);
12891             currentMove--;
12892         }
12893     } else {
12894         currentMove = target;
12895     }
12896
12897     if (gameMode == EditGame || gameMode == EndOfGame) {
12898         whiteTimeRemaining = timeRemaining[0][currentMove];
12899         blackTimeRemaining = timeRemaining[1][currentMove];
12900     }
12901     DisplayBothClocks();
12902     DisplayMove(currentMove - 1);
12903     DrawPosition(full_redraw, boards[currentMove]);
12904     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12905     // [HGM] PV info: routine tests if comment empty
12906     DisplayComment(currentMove - 1, commentList[currentMove]);
12907 }
12908
12909 void
12910 BackwardEvent()
12911 {
12912     if (gameMode == IcsExamining && !pausing) {
12913         SendToICS(ics_prefix);
12914         SendToICS("backward\n");
12915     } else {
12916         BackwardInner(currentMove - 1);
12917     }
12918 }
12919
12920 void
12921 ToStartEvent()
12922 {
12923     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12924         /* to optimize, we temporarily turn off analysis mode while we undo
12925          * all the moves. Otherwise we get analysis output after each undo.
12926          */
12927         if (first.analysisSupport) {
12928           SendToProgram("exit\nforce\n", &first);
12929           first.analyzing = FALSE;
12930         }
12931     }
12932
12933     if (gameMode == IcsExamining && !pausing) {
12934         SendToICS(ics_prefix);
12935         SendToICS("backward 999999\n");
12936     } else {
12937         BackwardInner(backwardMostMove);
12938     }
12939
12940     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12941         /* we have fed all the moves, so reactivate analysis mode */
12942         SendToProgram("analyze\n", &first);
12943         first.analyzing = TRUE;
12944         /*first.maybeThinking = TRUE;*/
12945         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12946     }
12947 }
12948
12949 void
12950 ToNrEvent(int to)
12951 {
12952   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12953   if (to >= forwardMostMove) to = forwardMostMove;
12954   if (to <= backwardMostMove) to = backwardMostMove;
12955   if (to < currentMove) {
12956     BackwardInner(to);
12957   } else {
12958     ForwardInner(to);
12959   }
12960 }
12961
12962 void
12963 RevertEvent(Boolean annotate)
12964 {
12965     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12966         return;
12967     }
12968     if (gameMode != IcsExamining) {
12969         DisplayError(_("You are not examining a game"), 0);
12970         return;
12971     }
12972     if (pausing) {
12973         DisplayError(_("You can't revert while pausing"), 0);
12974         return;
12975     }
12976     SendToICS(ics_prefix);
12977     SendToICS("revert\n");
12978 }
12979
12980 void
12981 RetractMoveEvent()
12982 {
12983     switch (gameMode) {
12984       case MachinePlaysWhite:
12985       case MachinePlaysBlack:
12986         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12987             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12988             return;
12989         }
12990         if (forwardMostMove < 2) return;
12991         currentMove = forwardMostMove = forwardMostMove - 2;
12992         whiteTimeRemaining = timeRemaining[0][currentMove];
12993         blackTimeRemaining = timeRemaining[1][currentMove];
12994         DisplayBothClocks();
12995         DisplayMove(currentMove - 1);
12996         ClearHighlights();/*!! could figure this out*/
12997         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12998         SendToProgram("remove\n", &first);
12999         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13000         break;
13001
13002       case BeginningOfGame:
13003       default:
13004         break;
13005
13006       case IcsPlayingWhite:
13007       case IcsPlayingBlack:
13008         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13009             SendToICS(ics_prefix);
13010             SendToICS("takeback 2\n");
13011         } else {
13012             SendToICS(ics_prefix);
13013             SendToICS("takeback 1\n");
13014         }
13015         break;
13016     }
13017 }
13018
13019 void
13020 MoveNowEvent()
13021 {
13022     ChessProgramState *cps;
13023
13024     switch (gameMode) {
13025       case MachinePlaysWhite:
13026         if (!WhiteOnMove(forwardMostMove)) {
13027             DisplayError(_("It is your turn"), 0);
13028             return;
13029         }
13030         cps = &first;
13031         break;
13032       case MachinePlaysBlack:
13033         if (WhiteOnMove(forwardMostMove)) {
13034             DisplayError(_("It is your turn"), 0);
13035             return;
13036         }
13037         cps = &first;
13038         break;
13039       case TwoMachinesPlay:
13040         if (WhiteOnMove(forwardMostMove) ==
13041             (first.twoMachinesColor[0] == 'w')) {
13042             cps = &first;
13043         } else {
13044             cps = &second;
13045         }
13046         break;
13047       case BeginningOfGame:
13048       default:
13049         return;
13050     }
13051     SendToProgram("?\n", cps);
13052 }
13053
13054 void
13055 TruncateGameEvent()
13056 {
13057     EditGameEvent();
13058     if (gameMode != EditGame) return;
13059     TruncateGame();
13060 }
13061
13062 void
13063 TruncateGame()
13064 {
13065     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13066     if (forwardMostMove > currentMove) {
13067         if (gameInfo.resultDetails != NULL) {
13068             free(gameInfo.resultDetails);
13069             gameInfo.resultDetails = NULL;
13070             gameInfo.result = GameUnfinished;
13071         }
13072         forwardMostMove = currentMove;
13073         HistorySet(parseList, backwardMostMove, forwardMostMove,
13074                    currentMove-1);
13075     }
13076 }
13077
13078 void
13079 HintEvent()
13080 {
13081     if (appData.noChessProgram) return;
13082     switch (gameMode) {
13083       case MachinePlaysWhite:
13084         if (WhiteOnMove(forwardMostMove)) {
13085             DisplayError(_("Wait until your turn"), 0);
13086             return;
13087         }
13088         break;
13089       case BeginningOfGame:
13090       case MachinePlaysBlack:
13091         if (!WhiteOnMove(forwardMostMove)) {
13092             DisplayError(_("Wait until your turn"), 0);
13093             return;
13094         }
13095         break;
13096       default:
13097         DisplayError(_("No hint available"), 0);
13098         return;
13099     }
13100     SendToProgram("hint\n", &first);
13101     hintRequested = TRUE;
13102 }
13103
13104 void
13105 BookEvent()
13106 {
13107     if (appData.noChessProgram) return;
13108     switch (gameMode) {
13109       case MachinePlaysWhite:
13110         if (WhiteOnMove(forwardMostMove)) {
13111             DisplayError(_("Wait until your turn"), 0);
13112             return;
13113         }
13114         break;
13115       case BeginningOfGame:
13116       case MachinePlaysBlack:
13117         if (!WhiteOnMove(forwardMostMove)) {
13118             DisplayError(_("Wait until your turn"), 0);
13119             return;
13120         }
13121         break;
13122       case EditPosition:
13123         EditPositionDone(TRUE);
13124         break;
13125       case TwoMachinesPlay:
13126         return;
13127       default:
13128         break;
13129     }
13130     SendToProgram("bk\n", &first);
13131     bookOutput[0] = NULLCHAR;
13132     bookRequested = TRUE;
13133 }
13134
13135 void
13136 AboutGameEvent()
13137 {
13138     char *tags = PGNTags(&gameInfo);
13139     TagsPopUp(tags, CmailMsg());
13140     free(tags);
13141 }
13142
13143 /* end button procedures */
13144
13145 void
13146 PrintPosition(fp, move)
13147      FILE *fp;
13148      int move;
13149 {
13150     int i, j;
13151
13152     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13153         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13154             char c = PieceToChar(boards[move][i][j]);
13155             fputc(c == 'x' ? '.' : c, fp);
13156             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13157         }
13158     }
13159     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13160       fprintf(fp, "white to play\n");
13161     else
13162       fprintf(fp, "black to play\n");
13163 }
13164
13165 void
13166 PrintOpponents(fp)
13167      FILE *fp;
13168 {
13169     if (gameInfo.white != NULL) {
13170         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13171     } else {
13172         fprintf(fp, "\n");
13173     }
13174 }
13175
13176 /* Find last component of program's own name, using some heuristics */
13177 void
13178 TidyProgramName(prog, host, buf)
13179      char *prog, *host, buf[MSG_SIZ];
13180 {
13181     char *p, *q;
13182     int local = (strcmp(host, "localhost") == 0);
13183     while (!local && (p = strchr(prog, ';')) != NULL) {
13184         p++;
13185         while (*p == ' ') p++;
13186         prog = p;
13187     }
13188     if (*prog == '"' || *prog == '\'') {
13189         q = strchr(prog + 1, *prog);
13190     } else {
13191         q = strchr(prog, ' ');
13192     }
13193     if (q == NULL) q = prog + strlen(prog);
13194     p = q;
13195     while (p >= prog && *p != '/' && *p != '\\') p--;
13196     p++;
13197     if(p == prog && *p == '"') p++;
13198     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13199     memcpy(buf, p, q - p);
13200     buf[q - p] = NULLCHAR;
13201     if (!local) {
13202         strcat(buf, "@");
13203         strcat(buf, host);
13204     }
13205 }
13206
13207 char *
13208 TimeControlTagValue()
13209 {
13210     char buf[MSG_SIZ];
13211     if (!appData.clockMode) {
13212       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13213     } else if (movesPerSession > 0) {
13214       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13215     } else if (timeIncrement == 0) {
13216       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13217     } else {
13218       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13219     }
13220     return StrSave(buf);
13221 }
13222
13223 void
13224 SetGameInfo()
13225 {
13226     /* This routine is used only for certain modes */
13227     VariantClass v = gameInfo.variant;
13228     ChessMove r = GameUnfinished;
13229     char *p = NULL;
13230
13231     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13232         r = gameInfo.result;
13233         p = gameInfo.resultDetails;
13234         gameInfo.resultDetails = NULL;
13235     }
13236     ClearGameInfo(&gameInfo);
13237     gameInfo.variant = v;
13238
13239     switch (gameMode) {
13240       case MachinePlaysWhite:
13241         gameInfo.event = StrSave( appData.pgnEventHeader );
13242         gameInfo.site = StrSave(HostName());
13243         gameInfo.date = PGNDate();
13244         gameInfo.round = StrSave("-");
13245         gameInfo.white = StrSave(first.tidy);
13246         gameInfo.black = StrSave(UserName());
13247         gameInfo.timeControl = TimeControlTagValue();
13248         break;
13249
13250       case MachinePlaysBlack:
13251         gameInfo.event = StrSave( appData.pgnEventHeader );
13252         gameInfo.site = StrSave(HostName());
13253         gameInfo.date = PGNDate();
13254         gameInfo.round = StrSave("-");
13255         gameInfo.white = StrSave(UserName());
13256         gameInfo.black = StrSave(first.tidy);
13257         gameInfo.timeControl = TimeControlTagValue();
13258         break;
13259
13260       case TwoMachinesPlay:
13261         gameInfo.event = StrSave( appData.pgnEventHeader );
13262         gameInfo.site = StrSave(HostName());
13263         gameInfo.date = PGNDate();
13264         if (matchGame > 0) {
13265             char buf[MSG_SIZ];
13266             snprintf(buf, MSG_SIZ, "%d", matchGame);
13267             gameInfo.round = StrSave(buf);
13268         } else {
13269             gameInfo.round = StrSave("-");
13270         }
13271         if (first.twoMachinesColor[0] == 'w') {
13272             gameInfo.white = StrSave(first.tidy);
13273             gameInfo.black = StrSave(second.tidy);
13274         } else {
13275             gameInfo.white = StrSave(second.tidy);
13276             gameInfo.black = StrSave(first.tidy);
13277         }
13278         gameInfo.timeControl = TimeControlTagValue();
13279         break;
13280
13281       case EditGame:
13282         gameInfo.event = StrSave("Edited game");
13283         gameInfo.site = StrSave(HostName());
13284         gameInfo.date = PGNDate();
13285         gameInfo.round = StrSave("-");
13286         gameInfo.white = StrSave("-");
13287         gameInfo.black = StrSave("-");
13288         gameInfo.result = r;
13289         gameInfo.resultDetails = p;
13290         break;
13291
13292       case EditPosition:
13293         gameInfo.event = StrSave("Edited position");
13294         gameInfo.site = StrSave(HostName());
13295         gameInfo.date = PGNDate();
13296         gameInfo.round = StrSave("-");
13297         gameInfo.white = StrSave("-");
13298         gameInfo.black = StrSave("-");
13299         break;
13300
13301       case IcsPlayingWhite:
13302       case IcsPlayingBlack:
13303       case IcsObserving:
13304       case IcsExamining:
13305         break;
13306
13307       case PlayFromGameFile:
13308         gameInfo.event = StrSave("Game from non-PGN file");
13309         gameInfo.site = StrSave(HostName());
13310         gameInfo.date = PGNDate();
13311         gameInfo.round = StrSave("-");
13312         gameInfo.white = StrSave("?");
13313         gameInfo.black = StrSave("?");
13314         break;
13315
13316       default:
13317         break;
13318     }
13319 }
13320
13321 void
13322 ReplaceComment(index, text)
13323      int index;
13324      char *text;
13325 {
13326     int len;
13327     char *p;
13328     float score;
13329
13330     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13331        pvInfoList[index-1].depth == len &&
13332        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13333        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13334     while (*text == '\n') text++;
13335     len = strlen(text);
13336     while (len > 0 && text[len - 1] == '\n') len--;
13337
13338     if (commentList[index] != NULL)
13339       free(commentList[index]);
13340
13341     if (len == 0) {
13342         commentList[index] = NULL;
13343         return;
13344     }
13345   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13346       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13347       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13348     commentList[index] = (char *) malloc(len + 2);
13349     strncpy(commentList[index], text, len);
13350     commentList[index][len] = '\n';
13351     commentList[index][len + 1] = NULLCHAR;
13352   } else {
13353     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13354     char *p;
13355     commentList[index] = (char *) malloc(len + 7);
13356     safeStrCpy(commentList[index], "{\n", 3);
13357     safeStrCpy(commentList[index]+2, text, len+1);
13358     commentList[index][len+2] = NULLCHAR;
13359     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13360     strcat(commentList[index], "\n}\n");
13361   }
13362 }
13363
13364 void
13365 CrushCRs(text)
13366      char *text;
13367 {
13368   char *p = text;
13369   char *q = text;
13370   char ch;
13371
13372   do {
13373     ch = *p++;
13374     if (ch == '\r') continue;
13375     *q++ = ch;
13376   } while (ch != '\0');
13377 }
13378
13379 void
13380 AppendComment(index, text, addBraces)
13381      int index;
13382      char *text;
13383      Boolean addBraces; // [HGM] braces: tells if we should add {}
13384 {
13385     int oldlen, len;
13386     char *old;
13387
13388 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13389     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13390
13391     CrushCRs(text);
13392     while (*text == '\n') text++;
13393     len = strlen(text);
13394     while (len > 0 && text[len - 1] == '\n') len--;
13395
13396     if (len == 0) return;
13397
13398     if (commentList[index] != NULL) {
13399         old = commentList[index];
13400         oldlen = strlen(old);
13401         while(commentList[index][oldlen-1] ==  '\n')
13402           commentList[index][--oldlen] = NULLCHAR;
13403         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13404         safeStrCpy(commentList[index], old, oldlen + len + 6);
13405         free(old);
13406         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13407         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13408           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13409           while (*text == '\n') { text++; len--; }
13410           commentList[index][--oldlen] = NULLCHAR;
13411       }
13412         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13413         else          strcat(commentList[index], "\n");
13414         strcat(commentList[index], text);
13415         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13416         else          strcat(commentList[index], "\n");
13417     } else {
13418         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13419         if(addBraces)
13420           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13421         else commentList[index][0] = NULLCHAR;
13422         strcat(commentList[index], text);
13423         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13424         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13425     }
13426 }
13427
13428 static char * FindStr( char * text, char * sub_text )
13429 {
13430     char * result = strstr( text, sub_text );
13431
13432     if( result != NULL ) {
13433         result += strlen( sub_text );
13434     }
13435
13436     return result;
13437 }
13438
13439 /* [AS] Try to extract PV info from PGN comment */
13440 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13441 char *GetInfoFromComment( int index, char * text )
13442 {
13443     char * sep = text, *p;
13444
13445     if( text != NULL && index > 0 ) {
13446         int score = 0;
13447         int depth = 0;
13448         int time = -1, sec = 0, deci;
13449         char * s_eval = FindStr( text, "[%eval " );
13450         char * s_emt = FindStr( text, "[%emt " );
13451
13452         if( s_eval != NULL || s_emt != NULL ) {
13453             /* New style */
13454             char delim;
13455
13456             if( s_eval != NULL ) {
13457                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13458                     return text;
13459                 }
13460
13461                 if( delim != ']' ) {
13462                     return text;
13463                 }
13464             }
13465
13466             if( s_emt != NULL ) {
13467             }
13468                 return text;
13469         }
13470         else {
13471             /* We expect something like: [+|-]nnn.nn/dd */
13472             int score_lo = 0;
13473
13474             if(*text != '{') return text; // [HGM] braces: must be normal comment
13475
13476             sep = strchr( text, '/' );
13477             if( sep == NULL || sep < (text+4) ) {
13478                 return text;
13479             }
13480
13481             p = text;
13482             if(p[1] == '(') { // comment starts with PV
13483                p = strchr(p, ')'); // locate end of PV
13484                if(p == NULL || sep < p+5) return text;
13485                // at this point we have something like "{(.*) +0.23/6 ..."
13486                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13487                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13488                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13489             }
13490             time = -1; sec = -1; deci = -1;
13491             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13492                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13493                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13494                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13495                 return text;
13496             }
13497
13498             if( score_lo < 0 || score_lo >= 100 ) {
13499                 return text;
13500             }
13501
13502             if(sec >= 0) time = 600*time + 10*sec; else
13503             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13504
13505             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13506
13507             /* [HGM] PV time: now locate end of PV info */
13508             while( *++sep >= '0' && *sep <= '9'); // strip depth
13509             if(time >= 0)
13510             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13511             if(sec >= 0)
13512             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13513             if(deci >= 0)
13514             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13515             while(*sep == ' ') sep++;
13516         }
13517
13518         if( depth <= 0 ) {
13519             return text;
13520         }
13521
13522         if( time < 0 ) {
13523             time = -1;
13524         }
13525
13526         pvInfoList[index-1].depth = depth;
13527         pvInfoList[index-1].score = score;
13528         pvInfoList[index-1].time  = 10*time; // centi-sec
13529         if(*sep == '}') *sep = 0; else *--sep = '{';
13530         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13531     }
13532     return sep;
13533 }
13534
13535 void
13536 SendToProgram(message, cps)
13537      char *message;
13538      ChessProgramState *cps;
13539 {
13540     int count, outCount, error;
13541     char buf[MSG_SIZ];
13542
13543     if (cps->pr == NULL) return;
13544     Attention(cps);
13545
13546     if (appData.debugMode) {
13547         TimeMark now;
13548         GetTimeMark(&now);
13549         fprintf(debugFP, "%ld >%-6s: %s",
13550                 SubtractTimeMarks(&now, &programStartTime),
13551                 cps->which, message);
13552     }
13553
13554     count = strlen(message);
13555     outCount = OutputToProcess(cps->pr, message, count, &error);
13556     if (outCount < count && !exiting
13557                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13558       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13559         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13560             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13561                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13562                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13563             } else {
13564                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13565             }
13566             gameInfo.resultDetails = StrSave(buf);
13567         }
13568         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13569     }
13570 }
13571
13572 void
13573 ReceiveFromProgram(isr, closure, message, count, error)
13574      InputSourceRef isr;
13575      VOIDSTAR closure;
13576      char *message;
13577      int count;
13578      int error;
13579 {
13580     char *end_str;
13581     char buf[MSG_SIZ];
13582     ChessProgramState *cps = (ChessProgramState *)closure;
13583
13584     if (isr != cps->isr) return; /* Killed intentionally */
13585     if (count <= 0) {
13586         if (count == 0) {
13587             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13588                     cps->which, cps->program);
13589         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13590                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13591                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13592                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13593                 } else {
13594                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13595                 }
13596                 gameInfo.resultDetails = StrSave(buf);
13597             }
13598             RemoveInputSource(cps->isr);
13599             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13600         } else {
13601             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13602                     cps->which, cps->program);
13603             RemoveInputSource(cps->isr);
13604
13605             /* [AS] Program is misbehaving badly... kill it */
13606             if( count == -2 ) {
13607                 DestroyChildProcess( cps->pr, 9 );
13608                 cps->pr = NoProc;
13609             }
13610
13611             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13612         }
13613         return;
13614     }
13615
13616     if ((end_str = strchr(message, '\r')) != NULL)
13617       *end_str = NULLCHAR;
13618     if ((end_str = strchr(message, '\n')) != NULL)
13619       *end_str = NULLCHAR;
13620
13621     if (appData.debugMode) {
13622         TimeMark now; int print = 1;
13623         char *quote = ""; char c; int i;
13624
13625         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13626                 char start = message[0];
13627                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13628                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13629                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13630                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13631                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13632                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13633                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13634                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13635                    sscanf(message, "hint: %c", &c)!=1 && 
13636                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13637                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13638                     print = (appData.engineComments >= 2);
13639                 }
13640                 message[0] = start; // restore original message
13641         }
13642         if(print) {
13643                 GetTimeMark(&now);
13644                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13645                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13646                         quote,
13647                         message);
13648         }
13649     }
13650
13651     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13652     if (appData.icsEngineAnalyze) {
13653         if (strstr(message, "whisper") != NULL ||
13654              strstr(message, "kibitz") != NULL ||
13655             strstr(message, "tellics") != NULL) return;
13656     }
13657
13658     HandleMachineMove(message, cps);
13659 }
13660
13661
13662 void
13663 SendTimeControl(cps, mps, tc, inc, sd, st)
13664      ChessProgramState *cps;
13665      int mps, inc, sd, st;
13666      long tc;
13667 {
13668     char buf[MSG_SIZ];
13669     int seconds;
13670
13671     if( timeControl_2 > 0 ) {
13672         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13673             tc = timeControl_2;
13674         }
13675     }
13676     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13677     inc /= cps->timeOdds;
13678     st  /= cps->timeOdds;
13679
13680     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13681
13682     if (st > 0) {
13683       /* Set exact time per move, normally using st command */
13684       if (cps->stKludge) {
13685         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13686         seconds = st % 60;
13687         if (seconds == 0) {
13688           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13689         } else {
13690           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13691         }
13692       } else {
13693         snprintf(buf, MSG_SIZ, "st %d\n", st);
13694       }
13695     } else {
13696       /* Set conventional or incremental time control, using level command */
13697       if (seconds == 0) {
13698         /* Note old gnuchess bug -- minutes:seconds used to not work.
13699            Fixed in later versions, but still avoid :seconds
13700            when seconds is 0. */
13701         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13702       } else {
13703         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13704                  seconds, inc/1000.);
13705       }
13706     }
13707     SendToProgram(buf, cps);
13708
13709     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13710     /* Orthogonally, limit search to given depth */
13711     if (sd > 0) {
13712       if (cps->sdKludge) {
13713         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13714       } else {
13715         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13716       }
13717       SendToProgram(buf, cps);
13718     }
13719
13720     if(cps->nps > 0) { /* [HGM] nps */
13721         if(cps->supportsNPS == FALSE)
13722           cps->nps = -1; // don't use if engine explicitly says not supported!
13723         else {
13724           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13725           SendToProgram(buf, cps);
13726         }
13727     }
13728 }
13729
13730 ChessProgramState *WhitePlayer()
13731 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13732 {
13733     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13734        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13735         return &second;
13736     return &first;
13737 }
13738
13739 void
13740 SendTimeRemaining(cps, machineWhite)
13741      ChessProgramState *cps;
13742      int /*boolean*/ machineWhite;
13743 {
13744     char message[MSG_SIZ];
13745     long time, otime;
13746
13747     /* Note: this routine must be called when the clocks are stopped
13748        or when they have *just* been set or switched; otherwise
13749        it will be off by the time since the current tick started.
13750     */
13751     if (machineWhite) {
13752         time = whiteTimeRemaining / 10;
13753         otime = blackTimeRemaining / 10;
13754     } else {
13755         time = blackTimeRemaining / 10;
13756         otime = whiteTimeRemaining / 10;
13757     }
13758     /* [HGM] translate opponent's time by time-odds factor */
13759     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13760     if (appData.debugMode) {
13761         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13762     }
13763
13764     if (time <= 0) time = 1;
13765     if (otime <= 0) otime = 1;
13766
13767     snprintf(message, MSG_SIZ, "time %ld\n", time);
13768     SendToProgram(message, cps);
13769
13770     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13771     SendToProgram(message, cps);
13772 }
13773
13774 int
13775 BoolFeature(p, name, loc, cps)
13776      char **p;
13777      char *name;
13778      int *loc;
13779      ChessProgramState *cps;
13780 {
13781   char buf[MSG_SIZ];
13782   int len = strlen(name);
13783   int val;
13784
13785   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13786     (*p) += len + 1;
13787     sscanf(*p, "%d", &val);
13788     *loc = (val != 0);
13789     while (**p && **p != ' ')
13790       (*p)++;
13791     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13792     SendToProgram(buf, cps);
13793     return TRUE;
13794   }
13795   return FALSE;
13796 }
13797
13798 int
13799 IntFeature(p, name, loc, cps)
13800      char **p;
13801      char *name;
13802      int *loc;
13803      ChessProgramState *cps;
13804 {
13805   char buf[MSG_SIZ];
13806   int len = strlen(name);
13807   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13808     (*p) += len + 1;
13809     sscanf(*p, "%d", loc);
13810     while (**p && **p != ' ') (*p)++;
13811     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13812     SendToProgram(buf, cps);
13813     return TRUE;
13814   }
13815   return FALSE;
13816 }
13817
13818 int
13819 StringFeature(p, name, loc, cps)
13820      char **p;
13821      char *name;
13822      char loc[];
13823      ChessProgramState *cps;
13824 {
13825   char buf[MSG_SIZ];
13826   int len = strlen(name);
13827   if (strncmp((*p), name, len) == 0
13828       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13829     (*p) += len + 2;
13830     sscanf(*p, "%[^\"]", loc);
13831     while (**p && **p != '\"') (*p)++;
13832     if (**p == '\"') (*p)++;
13833     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13834     SendToProgram(buf, cps);
13835     return TRUE;
13836   }
13837   return FALSE;
13838 }
13839
13840 int
13841 ParseOption(Option *opt, ChessProgramState *cps)
13842 // [HGM] options: process the string that defines an engine option, and determine
13843 // name, type, default value, and allowed value range
13844 {
13845         char *p, *q, buf[MSG_SIZ];
13846         int n, min = (-1)<<31, max = 1<<31, def;
13847
13848         if(p = strstr(opt->name, " -spin ")) {
13849             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13850             if(max < min) max = min; // enforce consistency
13851             if(def < min) def = min;
13852             if(def > max) def = max;
13853             opt->value = def;
13854             opt->min = min;
13855             opt->max = max;
13856             opt->type = Spin;
13857         } else if((p = strstr(opt->name, " -slider "))) {
13858             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13859             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13860             if(max < min) max = min; // enforce consistency
13861             if(def < min) def = min;
13862             if(def > max) def = max;
13863             opt->value = def;
13864             opt->min = min;
13865             opt->max = max;
13866             opt->type = Spin; // Slider;
13867         } else if((p = strstr(opt->name, " -string "))) {
13868             opt->textValue = p+9;
13869             opt->type = TextBox;
13870         } else if((p = strstr(opt->name, " -file "))) {
13871             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13872             opt->textValue = p+7;
13873             opt->type = TextBox; // FileName;
13874         } else if((p = strstr(opt->name, " -path "))) {
13875             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13876             opt->textValue = p+7;
13877             opt->type = TextBox; // PathName;
13878         } else if(p = strstr(opt->name, " -check ")) {
13879             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13880             opt->value = (def != 0);
13881             opt->type = CheckBox;
13882         } else if(p = strstr(opt->name, " -combo ")) {
13883             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13884             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13885             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13886             opt->value = n = 0;
13887             while(q = StrStr(q, " /// ")) {
13888                 n++; *q = 0;    // count choices, and null-terminate each of them
13889                 q += 5;
13890                 if(*q == '*') { // remember default, which is marked with * prefix
13891                     q++;
13892                     opt->value = n;
13893                 }
13894                 cps->comboList[cps->comboCnt++] = q;
13895             }
13896             cps->comboList[cps->comboCnt++] = NULL;
13897             opt->max = n + 1;
13898             opt->type = ComboBox;
13899         } else if(p = strstr(opt->name, " -button")) {
13900             opt->type = Button;
13901         } else if(p = strstr(opt->name, " -save")) {
13902             opt->type = SaveButton;
13903         } else return FALSE;
13904         *p = 0; // terminate option name
13905         // now look if the command-line options define a setting for this engine option.
13906         if(cps->optionSettings && cps->optionSettings[0])
13907             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13908         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13909           snprintf(buf, MSG_SIZ, "option %s", p);
13910                 if(p = strstr(buf, ",")) *p = 0;
13911                 if(q = strchr(buf, '=')) switch(opt->type) {
13912                     case ComboBox:
13913                         for(n=0; n<opt->max; n++)
13914                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13915                         break;
13916                     case TextBox:
13917                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13918                         break;
13919                     case Spin:
13920                     case CheckBox:
13921                         opt->value = atoi(q+1);
13922                     default:
13923                         break;
13924                 }
13925                 strcat(buf, "\n");
13926                 SendToProgram(buf, cps);
13927         }
13928         return TRUE;
13929 }
13930
13931 void
13932 FeatureDone(cps, val)
13933      ChessProgramState* cps;
13934      int val;
13935 {
13936   DelayedEventCallback cb = GetDelayedEvent();
13937   if ((cb == InitBackEnd3 && cps == &first) ||
13938       (cb == SettingsMenuIfReady && cps == &second) ||
13939       (cb == TwoMachinesEventIfReady && cps == &second)) {
13940     CancelDelayedEvent();
13941     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13942   }
13943   cps->initDone = val;
13944 }
13945
13946 /* Parse feature command from engine */
13947 void
13948 ParseFeatures(args, cps)
13949      char* args;
13950      ChessProgramState *cps;
13951 {
13952   char *p = args;
13953   char *q;
13954   int val;
13955   char buf[MSG_SIZ];
13956
13957   for (;;) {
13958     while (*p == ' ') p++;
13959     if (*p == NULLCHAR) return;
13960
13961     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13962     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13963     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13964     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13965     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13966     if (BoolFeature(&p, "reuse", &val, cps)) {
13967       /* Engine can disable reuse, but can't enable it if user said no */
13968       if (!val) cps->reuse = FALSE;
13969       continue;
13970     }
13971     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13972     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13973       if (gameMode == TwoMachinesPlay) {
13974         DisplayTwoMachinesTitle();
13975       } else {
13976         DisplayTitle("");
13977       }
13978       continue;
13979     }
13980     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13981     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13982     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13983     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13984     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13985     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13986     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13987     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13988     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13989     if (IntFeature(&p, "done", &val, cps)) {
13990       FeatureDone(cps, val);
13991       continue;
13992     }
13993     /* Added by Tord: */
13994     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13995     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13996     /* End of additions by Tord */
13997
13998     /* [HGM] added features: */
13999     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14000     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14001     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14002     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14003     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14004     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14005     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14006         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14007           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14008             SendToProgram(buf, cps);
14009             continue;
14010         }
14011         if(cps->nrOptions >= MAX_OPTIONS) {
14012             cps->nrOptions--;
14013             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
14014             DisplayError(buf, 0);
14015         }
14016         continue;
14017     }
14018     /* End of additions by HGM */
14019
14020     /* unknown feature: complain and skip */
14021     q = p;
14022     while (*q && *q != '=') q++;
14023     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14024     SendToProgram(buf, cps);
14025     p = q;
14026     if (*p == '=') {
14027       p++;
14028       if (*p == '\"') {
14029         p++;
14030         while (*p && *p != '\"') p++;
14031         if (*p == '\"') p++;
14032       } else {
14033         while (*p && *p != ' ') p++;
14034       }
14035     }
14036   }
14037
14038 }
14039
14040 void
14041 PeriodicUpdatesEvent(newState)
14042      int newState;
14043 {
14044     if (newState == appData.periodicUpdates)
14045       return;
14046
14047     appData.periodicUpdates=newState;
14048
14049     /* Display type changes, so update it now */
14050 //    DisplayAnalysis();
14051
14052     /* Get the ball rolling again... */
14053     if (newState) {
14054         AnalysisPeriodicEvent(1);
14055         StartAnalysisClock();
14056     }
14057 }
14058
14059 void
14060 PonderNextMoveEvent(newState)
14061      int newState;
14062 {
14063     if (newState == appData.ponderNextMove) return;
14064     if (gameMode == EditPosition) EditPositionDone(TRUE);
14065     if (newState) {
14066         SendToProgram("hard\n", &first);
14067         if (gameMode == TwoMachinesPlay) {
14068             SendToProgram("hard\n", &second);
14069         }
14070     } else {
14071         SendToProgram("easy\n", &first);
14072         thinkOutput[0] = NULLCHAR;
14073         if (gameMode == TwoMachinesPlay) {
14074             SendToProgram("easy\n", &second);
14075         }
14076     }
14077     appData.ponderNextMove = newState;
14078 }
14079
14080 void
14081 NewSettingEvent(option, feature, command, value)
14082      char *command;
14083      int option, value, *feature;
14084 {
14085     char buf[MSG_SIZ];
14086
14087     if (gameMode == EditPosition) EditPositionDone(TRUE);
14088     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14089     if(feature == NULL || *feature) SendToProgram(buf, &first);
14090     if (gameMode == TwoMachinesPlay) {
14091         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14092     }
14093 }
14094
14095 void
14096 ShowThinkingEvent()
14097 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14098 {
14099     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14100     int newState = appData.showThinking
14101         // [HGM] thinking: other features now need thinking output as well
14102         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14103
14104     if (oldState == newState) return;
14105     oldState = newState;
14106     if (gameMode == EditPosition) EditPositionDone(TRUE);
14107     if (oldState) {
14108         SendToProgram("post\n", &first);
14109         if (gameMode == TwoMachinesPlay) {
14110             SendToProgram("post\n", &second);
14111         }
14112     } else {
14113         SendToProgram("nopost\n", &first);
14114         thinkOutput[0] = NULLCHAR;
14115         if (gameMode == TwoMachinesPlay) {
14116             SendToProgram("nopost\n", &second);
14117         }
14118     }
14119 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14120 }
14121
14122 void
14123 AskQuestionEvent(title, question, replyPrefix, which)
14124      char *title; char *question; char *replyPrefix; char *which;
14125 {
14126   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14127   if (pr == NoProc) return;
14128   AskQuestion(title, question, replyPrefix, pr);
14129 }
14130
14131 void
14132 DisplayMove(moveNumber)
14133      int moveNumber;
14134 {
14135     char message[MSG_SIZ];
14136     char res[MSG_SIZ];
14137     char cpThinkOutput[MSG_SIZ];
14138
14139     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14140
14141     if (moveNumber == forwardMostMove - 1 ||
14142         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14143
14144         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14145
14146         if (strchr(cpThinkOutput, '\n')) {
14147             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14148         }
14149     } else {
14150         *cpThinkOutput = NULLCHAR;
14151     }
14152
14153     /* [AS] Hide thinking from human user */
14154     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14155         *cpThinkOutput = NULLCHAR;
14156         if( thinkOutput[0] != NULLCHAR ) {
14157             int i;
14158
14159             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14160                 cpThinkOutput[i] = '.';
14161             }
14162             cpThinkOutput[i] = NULLCHAR;
14163             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14164         }
14165     }
14166
14167     if (moveNumber == forwardMostMove - 1 &&
14168         gameInfo.resultDetails != NULL) {
14169         if (gameInfo.resultDetails[0] == NULLCHAR) {
14170           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14171         } else {
14172           snprintf(res, MSG_SIZ, " {%s} %s",
14173                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14174         }
14175     } else {
14176         res[0] = NULLCHAR;
14177     }
14178
14179     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14180         DisplayMessage(res, cpThinkOutput);
14181     } else {
14182       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14183                 WhiteOnMove(moveNumber) ? " " : ".. ",
14184                 parseList[moveNumber], res);
14185         DisplayMessage(message, cpThinkOutput);
14186     }
14187 }
14188
14189 void
14190 DisplayComment(moveNumber, text)
14191      int moveNumber;
14192      char *text;
14193 {
14194     char title[MSG_SIZ];
14195     char buf[8000]; // comment can be long!
14196     int score, depth;
14197
14198     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14199       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14200     } else {
14201       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14202               WhiteOnMove(moveNumber) ? " " : ".. ",
14203               parseList[moveNumber]);
14204     }
14205     // [HGM] PV info: display PV info together with (or as) comment
14206     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14207       if(text == NULL) text = "";
14208       score = pvInfoList[moveNumber].score;
14209       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14210               depth, (pvInfoList[moveNumber].time+50)/100, text);
14211       text = buf;
14212     }
14213     if (text != NULL && (appData.autoDisplayComment || commentUp))
14214         CommentPopUp(title, text);
14215 }
14216
14217 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14218  * might be busy thinking or pondering.  It can be omitted if your
14219  * gnuchess is configured to stop thinking immediately on any user
14220  * input.  However, that gnuchess feature depends on the FIONREAD
14221  * ioctl, which does not work properly on some flavors of Unix.
14222  */
14223 void
14224 Attention(cps)
14225      ChessProgramState *cps;
14226 {
14227 #if ATTENTION
14228     if (!cps->useSigint) return;
14229     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14230     switch (gameMode) {
14231       case MachinePlaysWhite:
14232       case MachinePlaysBlack:
14233       case TwoMachinesPlay:
14234       case IcsPlayingWhite:
14235       case IcsPlayingBlack:
14236       case AnalyzeMode:
14237       case AnalyzeFile:
14238         /* Skip if we know it isn't thinking */
14239         if (!cps->maybeThinking) return;
14240         if (appData.debugMode)
14241           fprintf(debugFP, "Interrupting %s\n", cps->which);
14242         InterruptChildProcess(cps->pr);
14243         cps->maybeThinking = FALSE;
14244         break;
14245       default:
14246         break;
14247     }
14248 #endif /*ATTENTION*/
14249 }
14250
14251 int
14252 CheckFlags()
14253 {
14254     if (whiteTimeRemaining <= 0) {
14255         if (!whiteFlag) {
14256             whiteFlag = TRUE;
14257             if (appData.icsActive) {
14258                 if (appData.autoCallFlag &&
14259                     gameMode == IcsPlayingBlack && !blackFlag) {
14260                   SendToICS(ics_prefix);
14261                   SendToICS("flag\n");
14262                 }
14263             } else {
14264                 if (blackFlag) {
14265                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14266                 } else {
14267                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14268                     if (appData.autoCallFlag) {
14269                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14270                         return TRUE;
14271                     }
14272                 }
14273             }
14274         }
14275     }
14276     if (blackTimeRemaining <= 0) {
14277         if (!blackFlag) {
14278             blackFlag = TRUE;
14279             if (appData.icsActive) {
14280                 if (appData.autoCallFlag &&
14281                     gameMode == IcsPlayingWhite && !whiteFlag) {
14282                   SendToICS(ics_prefix);
14283                   SendToICS("flag\n");
14284                 }
14285             } else {
14286                 if (whiteFlag) {
14287                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14288                 } else {
14289                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14290                     if (appData.autoCallFlag) {
14291                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14292                         return TRUE;
14293                     }
14294                 }
14295             }
14296         }
14297     }
14298     return FALSE;
14299 }
14300
14301 void
14302 CheckTimeControl()
14303 {
14304     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14305         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14306
14307     /*
14308      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14309      */
14310     if ( !WhiteOnMove(forwardMostMove) ) {
14311         /* White made time control */
14312         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14313         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14314         /* [HGM] time odds: correct new time quota for time odds! */
14315                                             / WhitePlayer()->timeOdds;
14316         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14317     } else {
14318         lastBlack -= blackTimeRemaining;
14319         /* Black made time control */
14320         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14321                                             / WhitePlayer()->other->timeOdds;
14322         lastWhite = whiteTimeRemaining;
14323     }
14324 }
14325
14326 void
14327 DisplayBothClocks()
14328 {
14329     int wom = gameMode == EditPosition ?
14330       !blackPlaysFirst : WhiteOnMove(currentMove);
14331     DisplayWhiteClock(whiteTimeRemaining, wom);
14332     DisplayBlackClock(blackTimeRemaining, !wom);
14333 }
14334
14335
14336 /* Timekeeping seems to be a portability nightmare.  I think everyone
14337    has ftime(), but I'm really not sure, so I'm including some ifdefs
14338    to use other calls if you don't.  Clocks will be less accurate if
14339    you have neither ftime nor gettimeofday.
14340 */
14341
14342 /* VS 2008 requires the #include outside of the function */
14343 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14344 #include <sys/timeb.h>
14345 #endif
14346
14347 /* Get the current time as a TimeMark */
14348 void
14349 GetTimeMark(tm)
14350      TimeMark *tm;
14351 {
14352 #if HAVE_GETTIMEOFDAY
14353
14354     struct timeval timeVal;
14355     struct timezone timeZone;
14356
14357     gettimeofday(&timeVal, &timeZone);
14358     tm->sec = (long) timeVal.tv_sec;
14359     tm->ms = (int) (timeVal.tv_usec / 1000L);
14360
14361 #else /*!HAVE_GETTIMEOFDAY*/
14362 #if HAVE_FTIME
14363
14364 // include <sys/timeb.h> / moved to just above start of function
14365     struct timeb timeB;
14366
14367     ftime(&timeB);
14368     tm->sec = (long) timeB.time;
14369     tm->ms = (int) timeB.millitm;
14370
14371 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14372     tm->sec = (long) time(NULL);
14373     tm->ms = 0;
14374 #endif
14375 #endif
14376 }
14377
14378 /* Return the difference in milliseconds between two
14379    time marks.  We assume the difference will fit in a long!
14380 */
14381 long
14382 SubtractTimeMarks(tm2, tm1)
14383      TimeMark *tm2, *tm1;
14384 {
14385     return 1000L*(tm2->sec - tm1->sec) +
14386            (long) (tm2->ms - tm1->ms);
14387 }
14388
14389
14390 /*
14391  * Code to manage the game clocks.
14392  *
14393  * In tournament play, black starts the clock and then white makes a move.
14394  * We give the human user a slight advantage if he is playing white---the
14395  * clocks don't run until he makes his first move, so it takes zero time.
14396  * Also, we don't account for network lag, so we could get out of sync
14397  * with GNU Chess's clock -- but then, referees are always right.
14398  */
14399
14400 static TimeMark tickStartTM;
14401 static long intendedTickLength;
14402
14403 long
14404 NextTickLength(timeRemaining)
14405      long timeRemaining;
14406 {
14407     long nominalTickLength, nextTickLength;
14408
14409     if (timeRemaining > 0L && timeRemaining <= 10000L)
14410       nominalTickLength = 100L;
14411     else
14412       nominalTickLength = 1000L;
14413     nextTickLength = timeRemaining % nominalTickLength;
14414     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14415
14416     return nextTickLength;
14417 }
14418
14419 /* Adjust clock one minute up or down */
14420 void
14421 AdjustClock(Boolean which, int dir)
14422 {
14423     if(which) blackTimeRemaining += 60000*dir;
14424     else      whiteTimeRemaining += 60000*dir;
14425     DisplayBothClocks();
14426 }
14427
14428 /* Stop clocks and reset to a fresh time control */
14429 void
14430 ResetClocks()
14431 {
14432     (void) StopClockTimer();
14433     if (appData.icsActive) {
14434         whiteTimeRemaining = blackTimeRemaining = 0;
14435     } else if (searchTime) {
14436         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14437         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14438     } else { /* [HGM] correct new time quote for time odds */
14439         whiteTC = blackTC = fullTimeControlString;
14440         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14441         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14442     }
14443     if (whiteFlag || blackFlag) {
14444         DisplayTitle("");
14445         whiteFlag = blackFlag = FALSE;
14446     }
14447     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14448     DisplayBothClocks();
14449 }
14450
14451 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14452
14453 /* Decrement running clock by amount of time that has passed */
14454 void
14455 DecrementClocks()
14456 {
14457     long timeRemaining;
14458     long lastTickLength, fudge;
14459     TimeMark now;
14460
14461     if (!appData.clockMode) return;
14462     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14463
14464     GetTimeMark(&now);
14465
14466     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14467
14468     /* Fudge if we woke up a little too soon */
14469     fudge = intendedTickLength - lastTickLength;
14470     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14471
14472     if (WhiteOnMove(forwardMostMove)) {
14473         if(whiteNPS >= 0) lastTickLength = 0;
14474         timeRemaining = whiteTimeRemaining -= lastTickLength;
14475         if(timeRemaining < 0 && !appData.icsActive) {
14476             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14477             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14478                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14479                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14480             }
14481         }
14482         DisplayWhiteClock(whiteTimeRemaining - fudge,
14483                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14484     } else {
14485         if(blackNPS >= 0) lastTickLength = 0;
14486         timeRemaining = blackTimeRemaining -= lastTickLength;
14487         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14488             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14489             if(suddenDeath) {
14490                 blackStartMove = forwardMostMove;
14491                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14492             }
14493         }
14494         DisplayBlackClock(blackTimeRemaining - fudge,
14495                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14496     }
14497     if (CheckFlags()) return;
14498
14499     tickStartTM = now;
14500     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14501     StartClockTimer(intendedTickLength);
14502
14503     /* if the time remaining has fallen below the alarm threshold, sound the
14504      * alarm. if the alarm has sounded and (due to a takeback or time control
14505      * with increment) the time remaining has increased to a level above the
14506      * threshold, reset the alarm so it can sound again.
14507      */
14508
14509     if (appData.icsActive && appData.icsAlarm) {
14510
14511         /* make sure we are dealing with the user's clock */
14512         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14513                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14514            )) return;
14515
14516         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14517             alarmSounded = FALSE;
14518         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14519             PlayAlarmSound();
14520             alarmSounded = TRUE;
14521         }
14522     }
14523 }
14524
14525
14526 /* A player has just moved, so stop the previously running
14527    clock and (if in clock mode) start the other one.
14528    We redisplay both clocks in case we're in ICS mode, because
14529    ICS gives us an update to both clocks after every move.
14530    Note that this routine is called *after* forwardMostMove
14531    is updated, so the last fractional tick must be subtracted
14532    from the color that is *not* on move now.
14533 */
14534 void
14535 SwitchClocks(int newMoveNr)
14536 {
14537     long lastTickLength;
14538     TimeMark now;
14539     int flagged = FALSE;
14540
14541     GetTimeMark(&now);
14542
14543     if (StopClockTimer() && appData.clockMode) {
14544         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14545         if (!WhiteOnMove(forwardMostMove)) {
14546             if(blackNPS >= 0) lastTickLength = 0;
14547             blackTimeRemaining -= lastTickLength;
14548            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14549 //         if(pvInfoList[forwardMostMove].time == -1)
14550                  pvInfoList[forwardMostMove].time =               // use GUI time
14551                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14552         } else {
14553            if(whiteNPS >= 0) lastTickLength = 0;
14554            whiteTimeRemaining -= lastTickLength;
14555            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14556 //         if(pvInfoList[forwardMostMove].time == -1)
14557                  pvInfoList[forwardMostMove].time =
14558                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14559         }
14560         flagged = CheckFlags();
14561     }
14562     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14563     CheckTimeControl();
14564
14565     if (flagged || !appData.clockMode) return;
14566
14567     switch (gameMode) {
14568       case MachinePlaysBlack:
14569       case MachinePlaysWhite:
14570       case BeginningOfGame:
14571         if (pausing) return;
14572         break;
14573
14574       case EditGame:
14575       case PlayFromGameFile:
14576       case IcsExamining:
14577         return;
14578
14579       default:
14580         break;
14581     }
14582
14583     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14584         if(WhiteOnMove(forwardMostMove))
14585              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14586         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14587     }
14588
14589     tickStartTM = now;
14590     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14591       whiteTimeRemaining : blackTimeRemaining);
14592     StartClockTimer(intendedTickLength);
14593 }
14594
14595
14596 /* Stop both clocks */
14597 void
14598 StopClocks()
14599 {
14600     long lastTickLength;
14601     TimeMark now;
14602
14603     if (!StopClockTimer()) return;
14604     if (!appData.clockMode) return;
14605
14606     GetTimeMark(&now);
14607
14608     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14609     if (WhiteOnMove(forwardMostMove)) {
14610         if(whiteNPS >= 0) lastTickLength = 0;
14611         whiteTimeRemaining -= lastTickLength;
14612         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14613     } else {
14614         if(blackNPS >= 0) lastTickLength = 0;
14615         blackTimeRemaining -= lastTickLength;
14616         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14617     }
14618     CheckFlags();
14619 }
14620
14621 /* Start clock of player on move.  Time may have been reset, so
14622    if clock is already running, stop and restart it. */
14623 void
14624 StartClocks()
14625 {
14626     (void) StopClockTimer(); /* in case it was running already */
14627     DisplayBothClocks();
14628     if (CheckFlags()) return;
14629
14630     if (!appData.clockMode) return;
14631     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14632
14633     GetTimeMark(&tickStartTM);
14634     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14635       whiteTimeRemaining : blackTimeRemaining);
14636
14637    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14638     whiteNPS = blackNPS = -1;
14639     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14640        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14641         whiteNPS = first.nps;
14642     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14643        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14644         blackNPS = first.nps;
14645     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14646         whiteNPS = second.nps;
14647     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14648         blackNPS = second.nps;
14649     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14650
14651     StartClockTimer(intendedTickLength);
14652 }
14653
14654 char *
14655 TimeString(ms)
14656      long ms;
14657 {
14658     long second, minute, hour, day;
14659     char *sign = "";
14660     static char buf[32];
14661
14662     if (ms > 0 && ms <= 9900) {
14663       /* convert milliseconds to tenths, rounding up */
14664       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14665
14666       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14667       return buf;
14668     }
14669
14670     /* convert milliseconds to seconds, rounding up */
14671     /* use floating point to avoid strangeness of integer division
14672        with negative dividends on many machines */
14673     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14674
14675     if (second < 0) {
14676         sign = "-";
14677         second = -second;
14678     }
14679
14680     day = second / (60 * 60 * 24);
14681     second = second % (60 * 60 * 24);
14682     hour = second / (60 * 60);
14683     second = second % (60 * 60);
14684     minute = second / 60;
14685     second = second % 60;
14686
14687     if (day > 0)
14688       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14689               sign, day, hour, minute, second);
14690     else if (hour > 0)
14691       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14692     else
14693       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14694
14695     return buf;
14696 }
14697
14698
14699 /*
14700  * This is necessary because some C libraries aren't ANSI C compliant yet.
14701  */
14702 char *
14703 StrStr(string, match)
14704      char *string, *match;
14705 {
14706     int i, length;
14707
14708     length = strlen(match);
14709
14710     for (i = strlen(string) - length; i >= 0; i--, string++)
14711       if (!strncmp(match, string, length))
14712         return string;
14713
14714     return NULL;
14715 }
14716
14717 char *
14718 StrCaseStr(string, match)
14719      char *string, *match;
14720 {
14721     int i, j, length;
14722
14723     length = strlen(match);
14724
14725     for (i = strlen(string) - length; i >= 0; i--, string++) {
14726         for (j = 0; j < length; j++) {
14727             if (ToLower(match[j]) != ToLower(string[j]))
14728               break;
14729         }
14730         if (j == length) return string;
14731     }
14732
14733     return NULL;
14734 }
14735
14736 #ifndef _amigados
14737 int
14738 StrCaseCmp(s1, s2)
14739      char *s1, *s2;
14740 {
14741     char c1, c2;
14742
14743     for (;;) {
14744         c1 = ToLower(*s1++);
14745         c2 = ToLower(*s2++);
14746         if (c1 > c2) return 1;
14747         if (c1 < c2) return -1;
14748         if (c1 == NULLCHAR) return 0;
14749     }
14750 }
14751
14752
14753 int
14754 ToLower(c)
14755      int c;
14756 {
14757     return isupper(c) ? tolower(c) : c;
14758 }
14759
14760
14761 int
14762 ToUpper(c)
14763      int c;
14764 {
14765     return islower(c) ? toupper(c) : c;
14766 }
14767 #endif /* !_amigados    */
14768
14769 char *
14770 StrSave(s)
14771      char *s;
14772 {
14773   char *ret;
14774
14775   if ((ret = (char *) malloc(strlen(s) + 1)))
14776     {
14777       safeStrCpy(ret, s, strlen(s)+1);
14778     }
14779   return ret;
14780 }
14781
14782 char *
14783 StrSavePtr(s, savePtr)
14784      char *s, **savePtr;
14785 {
14786     if (*savePtr) {
14787         free(*savePtr);
14788     }
14789     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14790       safeStrCpy(*savePtr, s, strlen(s)+1);
14791     }
14792     return(*savePtr);
14793 }
14794
14795 char *
14796 PGNDate()
14797 {
14798     time_t clock;
14799     struct tm *tm;
14800     char buf[MSG_SIZ];
14801
14802     clock = time((time_t *)NULL);
14803     tm = localtime(&clock);
14804     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14805             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14806     return StrSave(buf);
14807 }
14808
14809
14810 char *
14811 PositionToFEN(move, overrideCastling)
14812      int move;
14813      char *overrideCastling;
14814 {
14815     int i, j, fromX, fromY, toX, toY;
14816     int whiteToPlay;
14817     char buf[128];
14818     char *p, *q;
14819     int emptycount;
14820     ChessSquare piece;
14821
14822     whiteToPlay = (gameMode == EditPosition) ?
14823       !blackPlaysFirst : (move % 2 == 0);
14824     p = buf;
14825
14826     /* Piece placement data */
14827     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14828         emptycount = 0;
14829         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14830             if (boards[move][i][j] == EmptySquare) {
14831                 emptycount++;
14832             } else { ChessSquare piece = boards[move][i][j];
14833                 if (emptycount > 0) {
14834                     if(emptycount<10) /* [HGM] can be >= 10 */
14835                         *p++ = '0' + emptycount;
14836                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14837                     emptycount = 0;
14838                 }
14839                 if(PieceToChar(piece) == '+') {
14840                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14841                     *p++ = '+';
14842                     piece = (ChessSquare)(DEMOTED piece);
14843                 }
14844                 *p++ = PieceToChar(piece);
14845                 if(p[-1] == '~') {
14846                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14847                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14848                     *p++ = '~';
14849                 }
14850             }
14851         }
14852         if (emptycount > 0) {
14853             if(emptycount<10) /* [HGM] can be >= 10 */
14854                 *p++ = '0' + emptycount;
14855             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14856             emptycount = 0;
14857         }
14858         *p++ = '/';
14859     }
14860     *(p - 1) = ' ';
14861
14862     /* [HGM] print Crazyhouse or Shogi holdings */
14863     if( gameInfo.holdingsWidth ) {
14864         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14865         q = p;
14866         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14867             piece = boards[move][i][BOARD_WIDTH-1];
14868             if( piece != EmptySquare )
14869               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14870                   *p++ = PieceToChar(piece);
14871         }
14872         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14873             piece = boards[move][BOARD_HEIGHT-i-1][0];
14874             if( piece != EmptySquare )
14875               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14876                   *p++ = PieceToChar(piece);
14877         }
14878
14879         if( q == p ) *p++ = '-';
14880         *p++ = ']';
14881         *p++ = ' ';
14882     }
14883
14884     /* Active color */
14885     *p++ = whiteToPlay ? 'w' : 'b';
14886     *p++ = ' ';
14887
14888   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14889     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14890   } else {
14891   if(nrCastlingRights) {
14892      q = p;
14893      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14894        /* [HGM] write directly from rights */
14895            if(boards[move][CASTLING][2] != NoRights &&
14896               boards[move][CASTLING][0] != NoRights   )
14897                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14898            if(boards[move][CASTLING][2] != NoRights &&
14899               boards[move][CASTLING][1] != NoRights   )
14900                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14901            if(boards[move][CASTLING][5] != NoRights &&
14902               boards[move][CASTLING][3] != NoRights   )
14903                 *p++ = boards[move][CASTLING][3] + AAA;
14904            if(boards[move][CASTLING][5] != NoRights &&
14905               boards[move][CASTLING][4] != NoRights   )
14906                 *p++ = boards[move][CASTLING][4] + AAA;
14907      } else {
14908
14909         /* [HGM] write true castling rights */
14910         if( nrCastlingRights == 6 ) {
14911             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14912                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14913             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14914                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14915             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14916                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14917             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14918                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14919         }
14920      }
14921      if (q == p) *p++ = '-'; /* No castling rights */
14922      *p++ = ' ';
14923   }
14924
14925   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14926      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14927     /* En passant target square */
14928     if (move > backwardMostMove) {
14929         fromX = moveList[move - 1][0] - AAA;
14930         fromY = moveList[move - 1][1] - ONE;
14931         toX = moveList[move - 1][2] - AAA;
14932         toY = moveList[move - 1][3] - ONE;
14933         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14934             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14935             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14936             fromX == toX) {
14937             /* 2-square pawn move just happened */
14938             *p++ = toX + AAA;
14939             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14940         } else {
14941             *p++ = '-';
14942         }
14943     } else if(move == backwardMostMove) {
14944         // [HGM] perhaps we should always do it like this, and forget the above?
14945         if((signed char)boards[move][EP_STATUS] >= 0) {
14946             *p++ = boards[move][EP_STATUS] + AAA;
14947             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14948         } else {
14949             *p++ = '-';
14950         }
14951     } else {
14952         *p++ = '-';
14953     }
14954     *p++ = ' ';
14955   }
14956   }
14957
14958     /* [HGM] find reversible plies */
14959     {   int i = 0, j=move;
14960
14961         if (appData.debugMode) { int k;
14962             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14963             for(k=backwardMostMove; k<=forwardMostMove; k++)
14964                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14965
14966         }
14967
14968         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14969         if( j == backwardMostMove ) i += initialRulePlies;
14970         sprintf(p, "%d ", i);
14971         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14972     }
14973     /* Fullmove number */
14974     sprintf(p, "%d", (move / 2) + 1);
14975
14976     return StrSave(buf);
14977 }
14978
14979 Boolean
14980 ParseFEN(board, blackPlaysFirst, fen)
14981     Board board;
14982      int *blackPlaysFirst;
14983      char *fen;
14984 {
14985     int i, j;
14986     char *p, c;
14987     int emptycount;
14988     ChessSquare piece;
14989
14990     p = fen;
14991
14992     /* [HGM] by default clear Crazyhouse holdings, if present */
14993     if(gameInfo.holdingsWidth) {
14994        for(i=0; i<BOARD_HEIGHT; i++) {
14995            board[i][0]             = EmptySquare; /* black holdings */
14996            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14997            board[i][1]             = (ChessSquare) 0; /* black counts */
14998            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14999        }
15000     }
15001
15002     /* Piece placement data */
15003     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15004         j = 0;
15005         for (;;) {
15006             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15007                 if (*p == '/') p++;
15008                 emptycount = gameInfo.boardWidth - j;
15009                 while (emptycount--)
15010                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15011                 break;
15012 #if(BOARD_FILES >= 10)
15013             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15014                 p++; emptycount=10;
15015                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15016                 while (emptycount--)
15017                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15018 #endif
15019             } else if (isdigit(*p)) {
15020                 emptycount = *p++ - '0';
15021                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15022                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15023                 while (emptycount--)
15024                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15025             } else if (*p == '+' || isalpha(*p)) {
15026                 if (j >= gameInfo.boardWidth) return FALSE;
15027                 if(*p=='+') {
15028                     piece = CharToPiece(*++p);
15029                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15030                     piece = (ChessSquare) (PROMOTED piece ); p++;
15031                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15032                 } else piece = CharToPiece(*p++);
15033
15034                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15035                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15036                     piece = (ChessSquare) (PROMOTED piece);
15037                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15038                     p++;
15039                 }
15040                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15041             } else {
15042                 return FALSE;
15043             }
15044         }
15045     }
15046     while (*p == '/' || *p == ' ') p++;
15047
15048     /* [HGM] look for Crazyhouse holdings here */
15049     while(*p==' ') p++;
15050     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15051         if(*p == '[') p++;
15052         if(*p == '-' ) p++; /* empty holdings */ else {
15053             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15054             /* if we would allow FEN reading to set board size, we would   */
15055             /* have to add holdings and shift the board read so far here   */
15056             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15057                 p++;
15058                 if((int) piece >= (int) BlackPawn ) {
15059                     i = (int)piece - (int)BlackPawn;
15060                     i = PieceToNumber((ChessSquare)i);
15061                     if( i >= gameInfo.holdingsSize ) return FALSE;
15062                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15063                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15064                 } else {
15065                     i = (int)piece - (int)WhitePawn;
15066                     i = PieceToNumber((ChessSquare)i);
15067                     if( i >= gameInfo.holdingsSize ) return FALSE;
15068                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15069                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15070                 }
15071             }
15072         }
15073         if(*p == ']') p++;
15074     }
15075
15076     while(*p == ' ') p++;
15077
15078     /* Active color */
15079     c = *p++;
15080     if(appData.colorNickNames) {
15081       if( c == appData.colorNickNames[0] ) c = 'w'; else
15082       if( c == appData.colorNickNames[1] ) c = 'b';
15083     }
15084     switch (c) {
15085       case 'w':
15086         *blackPlaysFirst = FALSE;
15087         break;
15088       case 'b':
15089         *blackPlaysFirst = TRUE;
15090         break;
15091       default:
15092         return FALSE;
15093     }
15094
15095     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15096     /* return the extra info in global variiables             */
15097
15098     /* set defaults in case FEN is incomplete */
15099     board[EP_STATUS] = EP_UNKNOWN;
15100     for(i=0; i<nrCastlingRights; i++ ) {
15101         board[CASTLING][i] =
15102             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15103     }   /* assume possible unless obviously impossible */
15104     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15105     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15106     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15107                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15108     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15109     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15110     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15111                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15112     FENrulePlies = 0;
15113
15114     while(*p==' ') p++;
15115     if(nrCastlingRights) {
15116       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15117           /* castling indicator present, so default becomes no castlings */
15118           for(i=0; i<nrCastlingRights; i++ ) {
15119                  board[CASTLING][i] = NoRights;
15120           }
15121       }
15122       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15123              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15124              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15125              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15126         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15127
15128         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15129             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15130             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15131         }
15132         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15133             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15134         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15135                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15136         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15137                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15138         switch(c) {
15139           case'K':
15140               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15141               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15142               board[CASTLING][2] = whiteKingFile;
15143               break;
15144           case'Q':
15145               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15146               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15147               board[CASTLING][2] = whiteKingFile;
15148               break;
15149           case'k':
15150               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15151               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15152               board[CASTLING][5] = blackKingFile;
15153               break;
15154           case'q':
15155               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15156               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15157               board[CASTLING][5] = blackKingFile;
15158           case '-':
15159               break;
15160           default: /* FRC castlings */
15161               if(c >= 'a') { /* black rights */
15162                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15163                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15164                   if(i == BOARD_RGHT) break;
15165                   board[CASTLING][5] = i;
15166                   c -= AAA;
15167                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15168                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15169                   if(c > i)
15170                       board[CASTLING][3] = c;
15171                   else
15172                       board[CASTLING][4] = c;
15173               } else { /* white rights */
15174                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15175                     if(board[0][i] == WhiteKing) break;
15176                   if(i == BOARD_RGHT) break;
15177                   board[CASTLING][2] = i;
15178                   c -= AAA - 'a' + 'A';
15179                   if(board[0][c] >= WhiteKing) break;
15180                   if(c > i)
15181                       board[CASTLING][0] = c;
15182                   else
15183                       board[CASTLING][1] = c;
15184               }
15185         }
15186       }
15187       for(i=0; i<nrCastlingRights; i++)
15188         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15189     if (appData.debugMode) {
15190         fprintf(debugFP, "FEN castling rights:");
15191         for(i=0; i<nrCastlingRights; i++)
15192         fprintf(debugFP, " %d", board[CASTLING][i]);
15193         fprintf(debugFP, "\n");
15194     }
15195
15196       while(*p==' ') p++;
15197     }
15198
15199     /* read e.p. field in games that know e.p. capture */
15200     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15201        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15202       if(*p=='-') {
15203         p++; board[EP_STATUS] = EP_NONE;
15204       } else {
15205          char c = *p++ - AAA;
15206
15207          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15208          if(*p >= '0' && *p <='9') p++;
15209          board[EP_STATUS] = c;
15210       }
15211     }
15212
15213
15214     if(sscanf(p, "%d", &i) == 1) {
15215         FENrulePlies = i; /* 50-move ply counter */
15216         /* (The move number is still ignored)    */
15217     }
15218
15219     return TRUE;
15220 }
15221
15222 void
15223 EditPositionPasteFEN(char *fen)
15224 {
15225   if (fen != NULL) {
15226     Board initial_position;
15227
15228     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15229       DisplayError(_("Bad FEN position in clipboard"), 0);
15230       return ;
15231     } else {
15232       int savedBlackPlaysFirst = blackPlaysFirst;
15233       EditPositionEvent();
15234       blackPlaysFirst = savedBlackPlaysFirst;
15235       CopyBoard(boards[0], initial_position);
15236       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15237       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15238       DisplayBothClocks();
15239       DrawPosition(FALSE, boards[currentMove]);
15240     }
15241   }
15242 }
15243
15244 static char cseq[12] = "\\   ";
15245
15246 Boolean set_cont_sequence(char *new_seq)
15247 {
15248     int len;
15249     Boolean ret;
15250
15251     // handle bad attempts to set the sequence
15252         if (!new_seq)
15253                 return 0; // acceptable error - no debug
15254
15255     len = strlen(new_seq);
15256     ret = (len > 0) && (len < sizeof(cseq));
15257     if (ret)
15258       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15259     else if (appData.debugMode)
15260       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15261     return ret;
15262 }
15263
15264 /*
15265     reformat a source message so words don't cross the width boundary.  internal
15266     newlines are not removed.  returns the wrapped size (no null character unless
15267     included in source message).  If dest is NULL, only calculate the size required
15268     for the dest buffer.  lp argument indicats line position upon entry, and it's
15269     passed back upon exit.
15270 */
15271 int wrap(char *dest, char *src, int count, int width, int *lp)
15272 {
15273     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15274
15275     cseq_len = strlen(cseq);
15276     old_line = line = *lp;
15277     ansi = len = clen = 0;
15278
15279     for (i=0; i < count; i++)
15280     {
15281         if (src[i] == '\033')
15282             ansi = 1;
15283
15284         // if we hit the width, back up
15285         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15286         {
15287             // store i & len in case the word is too long
15288             old_i = i, old_len = len;
15289
15290             // find the end of the last word
15291             while (i && src[i] != ' ' && src[i] != '\n')
15292             {
15293                 i--;
15294                 len--;
15295             }
15296
15297             // word too long?  restore i & len before splitting it
15298             if ((old_i-i+clen) >= width)
15299             {
15300                 i = old_i;
15301                 len = old_len;
15302             }
15303
15304             // extra space?
15305             if (i && src[i-1] == ' ')
15306                 len--;
15307
15308             if (src[i] != ' ' && src[i] != '\n')
15309             {
15310                 i--;
15311                 if (len)
15312                     len--;
15313             }
15314
15315             // now append the newline and continuation sequence
15316             if (dest)
15317                 dest[len] = '\n';
15318             len++;
15319             if (dest)
15320                 strncpy(dest+len, cseq, cseq_len);
15321             len += cseq_len;
15322             line = cseq_len;
15323             clen = cseq_len;
15324             continue;
15325         }
15326
15327         if (dest)
15328             dest[len] = src[i];
15329         len++;
15330         if (!ansi)
15331             line++;
15332         if (src[i] == '\n')
15333             line = 0;
15334         if (src[i] == 'm')
15335             ansi = 0;
15336     }
15337     if (dest && appData.debugMode)
15338     {
15339         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15340             count, width, line, len, *lp);
15341         show_bytes(debugFP, src, count);
15342         fprintf(debugFP, "\ndest: ");
15343         show_bytes(debugFP, dest, len);
15344         fprintf(debugFP, "\n");
15345     }
15346     *lp = dest ? line : old_line;
15347
15348     return len;
15349 }
15350
15351 // [HGM] vari: routines for shelving variations
15352
15353 void
15354 PushTail(int firstMove, int lastMove)
15355 {
15356         int i, j, nrMoves = lastMove - firstMove;
15357
15358         if(appData.icsActive) { // only in local mode
15359                 forwardMostMove = currentMove; // mimic old ICS behavior
15360                 return;
15361         }
15362         if(storedGames >= MAX_VARIATIONS-1) return;
15363
15364         // push current tail of game on stack
15365         savedResult[storedGames] = gameInfo.result;
15366         savedDetails[storedGames] = gameInfo.resultDetails;
15367         gameInfo.resultDetails = NULL;
15368         savedFirst[storedGames] = firstMove;
15369         savedLast [storedGames] = lastMove;
15370         savedFramePtr[storedGames] = framePtr;
15371         framePtr -= nrMoves; // reserve space for the boards
15372         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15373             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15374             for(j=0; j<MOVE_LEN; j++)
15375                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15376             for(j=0; j<2*MOVE_LEN; j++)
15377                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15378             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15379             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15380             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15381             pvInfoList[firstMove+i-1].depth = 0;
15382             commentList[framePtr+i] = commentList[firstMove+i];
15383             commentList[firstMove+i] = NULL;
15384         }
15385
15386         storedGames++;
15387         forwardMostMove = firstMove; // truncate game so we can start variation
15388         if(storedGames == 1) GreyRevert(FALSE);
15389 }
15390
15391 Boolean
15392 PopTail(Boolean annotate)
15393 {
15394         int i, j, nrMoves;
15395         char buf[8000], moveBuf[20];
15396
15397         if(appData.icsActive) return FALSE; // only in local mode
15398         if(!storedGames) return FALSE; // sanity
15399         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15400
15401         storedGames--;
15402         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15403         nrMoves = savedLast[storedGames] - currentMove;
15404         if(annotate) {
15405                 int cnt = 10;
15406                 if(!WhiteOnMove(currentMove))
15407                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15408                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15409                 for(i=currentMove; i<forwardMostMove; i++) {
15410                         if(WhiteOnMove(i))
15411                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15412                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15413                         strcat(buf, moveBuf);
15414                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15415                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15416                 }
15417                 strcat(buf, ")");
15418         }
15419         for(i=1; i<=nrMoves; i++) { // copy last variation back
15420             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15421             for(j=0; j<MOVE_LEN; j++)
15422                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15423             for(j=0; j<2*MOVE_LEN; j++)
15424                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15425             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15426             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15427             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15428             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15429             commentList[currentMove+i] = commentList[framePtr+i];
15430             commentList[framePtr+i] = NULL;
15431         }
15432         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15433         framePtr = savedFramePtr[storedGames];
15434         gameInfo.result = savedResult[storedGames];
15435         if(gameInfo.resultDetails != NULL) {
15436             free(gameInfo.resultDetails);
15437       }
15438         gameInfo.resultDetails = savedDetails[storedGames];
15439         forwardMostMove = currentMove + nrMoves;
15440         if(storedGames == 0) GreyRevert(TRUE);
15441         return TRUE;
15442 }
15443
15444 void
15445 CleanupTail()
15446 {       // remove all shelved variations
15447         int i;
15448         for(i=0; i<storedGames; i++) {
15449             if(savedDetails[i])
15450                 free(savedDetails[i]);
15451             savedDetails[i] = NULL;
15452         }
15453         for(i=framePtr; i<MAX_MOVES; i++) {
15454                 if(commentList[i]) free(commentList[i]);
15455                 commentList[i] = NULL;
15456         }
15457         framePtr = MAX_MOVES-1;
15458         storedGames = 0;
15459 }
15460
15461 void
15462 LoadVariation(int index, char *text)
15463 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15464         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15465         int level = 0, move;
15466
15467         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15468         // first find outermost bracketing variation
15469         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15470             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15471                 if(*p == '{') wait = '}'; else
15472                 if(*p == '[') wait = ']'; else
15473                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15474                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15475             }
15476             if(*p == wait) wait = NULLCHAR; // closing ]} found
15477             p++;
15478         }
15479         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15480         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15481         end[1] = NULLCHAR; // clip off comment beyond variation
15482         ToNrEvent(currentMove-1);
15483         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15484         // kludge: use ParsePV() to append variation to game
15485         move = currentMove;
15486         ParsePV(start, TRUE);
15487         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15488         ClearPremoveHighlights();
15489         CommentPopDown();
15490         ToNrEvent(currentMove+1);
15491 }
15492