updated timer
[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 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 #else /* not STDC_HEADERS */
81 # if HAVE_STRING_H
82 #  include <string.h>
83 # else /* not HAVE_STRING_H */
84 #  include <strings.h>
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
87
88 #if HAVE_SYS_FCNTL_H
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
91 # if HAVE_FCNTL_H
92 #  include <fcntl.h>
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
95
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
98 # include <time.h>
99 #else
100 # if HAVE_SYS_TIME_H
101 #  include <sys/time.h>
102 # else
103 #  include <time.h>
104 # endif
105 #endif
106
107 #if defined(_amigados) && !defined(__GNUC__)
108 struct timezone {
109     int tz_minuteswest;
110     int tz_dsttime;
111 };
112 extern int gettimeofday(struct timeval *, struct timezone *);
113 #endif
114
115 #if HAVE_UNISTD_H
116 # include <unistd.h>
117 #endif
118
119 #include "common.h"
120 #include "frontend.h"
121 #include "backend.h"
122 #include "parser.h"
123 #include "moves.h"
124 #if ZIPPY
125 # include "zippy.h"
126 #endif
127 #include "backendz.h"
128 #include "gettext.h"
129
130 #ifdef ENABLE_NLS
131 # define _(s) gettext (s)
132 # define N_(s) gettext_noop (s)
133 #else
134 # define _(s) (s)
135 # define N_(s) s
136 #endif
137
138
139 /* A point in time */
140 typedef struct {
141     long sec;  /* Assuming this is >= 32 bits */
142     int ms;    /* Assuming this is >= 16 bits */
143 } TimeMark;
144
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147                          char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149                       char *buf, int count, int error));
150 void SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
153                       int toX, int toY));
154 void InitPosition P((int redraw));
155 void HandleMachineMove P((char *message, ChessProgramState *cps));
156 int AutoPlayOneMove P((void));
157 int LoadGameOneMove P((ChessMove readAhead));
158 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
159 int LoadPositionFromFile P((char *filename, int n, char *title));
160 int SavePositionToFile P((char *filename));
161 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
162                   Board board, char *castle, char *ep));
163 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
164 void ShowMove P((int fromX, int fromY, int toX, int toY));
165 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
166                    /*char*/int promoChar));
167 void BackwardInner P((int target));
168 void ForwardInner P((int target));
169 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
170 void EditPositionDone P((void));
171 void PrintOpponents P((FILE *fp));
172 void PrintPosition P((FILE *fp, int move));
173 void StartChessProgram P((ChessProgramState *cps));
174 void SendToProgram P((char *message, ChessProgramState *cps));
175 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
176 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
177                            char *buf, int count, int error));
178 void SendTimeControl P((ChessProgramState *cps,
179                         int mps, long tc, int inc, int sd, int st));
180 char *TimeControlTagValue P((void));
181 void Attention P((ChessProgramState *cps));
182 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
183 void ResurrectChessProgram P((void));
184 void DisplayComment P((int moveNumber, char *text));
185 void DisplayMove P((int moveNumber));
186 void DisplayAnalysis P((void));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 extern char installDir[MSG_SIZ];
234
235 extern int tinyLayout, smallLayout;
236 ChessProgramStats programStats;
237 static int exiting = 0; /* [HGM] moved to top */
238 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
239 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
240 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
241 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
242 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
243 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
244 int opponentKibitzes;
245
246 /* States for ics_getting_history */
247 #define H_FALSE 0
248 #define H_REQUESTED 1
249 #define H_GOT_REQ_HEADER 2
250 #define H_GOT_UNREQ_HEADER 3
251 #define H_GETTING_MOVES 4
252 #define H_GOT_UNWANTED_HEADER 5
253
254 /* whosays values for GameEnds */
255 #define GE_ICS 0
256 #define GE_ENGINE 1
257 #define GE_PLAYER 2
258 #define GE_FILE 3
259 #define GE_XBOARD 4
260 #define GE_ENGINE1 5
261 #define GE_ENGINE2 6
262
263 /* Maximum number of games in a cmail message */
264 #define CMAIL_MAX_GAMES 20
265
266 /* Different types of move when calling RegisterMove */
267 #define CMAIL_MOVE   0
268 #define CMAIL_RESIGN 1
269 #define CMAIL_DRAW   2
270 #define CMAIL_ACCEPT 3
271
272 /* Different types of result to remember for each game */
273 #define CMAIL_NOT_RESULT 0
274 #define CMAIL_OLD_RESULT 1
275 #define CMAIL_NEW_RESULT 2
276
277 /* Telnet protocol constants */
278 #define TN_WILL 0373
279 #define TN_WONT 0374
280 #define TN_DO   0375
281 #define TN_DONT 0376
282 #define TN_IAC  0377
283 #define TN_ECHO 0001
284 #define TN_SGA  0003
285 #define TN_PORT 23
286
287 /* [AS] */
288 static char * safeStrCpy( char * dst, const char * src, size_t count )
289 {
290     assert( dst != NULL );
291     assert( src != NULL );
292     assert( count > 0 );
293
294     strncpy( dst, src, count );
295     dst[ count-1 ] = '\0';
296     return dst;
297 }
298
299 #if 0
300 //[HGM] for future use? Conditioned out for now to suppress warning.
301 static char * safeStrCat( char * dst, const char * src, size_t count )
302 {
303     size_t  dst_len;
304
305     assert( dst != NULL );
306     assert( src != NULL );
307     assert( count > 0 );
308
309     dst_len = strlen(dst);
310
311     assert( count > dst_len ); /* Buffer size must be greater than current length */
312
313     safeStrCpy( dst + dst_len, src, count - dst_len );
314
315     return dst;
316 }
317 #endif
318
319 /* Some compiler can't cast u64 to double
320  * This function do the job for us:
321
322  * We use the highest bit for cast, this only
323  * works if the highest bit is not
324  * in use (This should not happen)
325  *
326  * We used this for all compiler
327  */
328 double
329 u64ToDouble(u64 value)
330 {
331   double r;
332   u64 tmp = value & u64Const(0x7fffffffffffffff);
333   r = (double)(s64)tmp;
334   if (value & u64Const(0x8000000000000000))
335        r +=  9.2233720368547758080e18; /* 2^63 */
336  return r;
337 }
338
339 /* Fake up flags for now, as we aren't keeping track of castling
340    availability yet. [HGM] Change of logic: the flag now only
341    indicates the type of castlings allowed by the rule of the game.
342    The actual rights themselves are maintained in the array
343    castlingRights, as part of the game history, and are not probed
344    by this function.
345  */
346 int
347 PosFlags(index)
348 {
349   int flags = F_ALL_CASTLE_OK;
350   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
351   switch (gameInfo.variant) {
352   case VariantSuicide:
353     flags &= ~F_ALL_CASTLE_OK;
354   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
355     flags |= F_IGNORE_CHECK;
356   case VariantLosers:
357     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
358     break;
359   case VariantAtomic:
360     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
361     break;
362   case VariantKriegspiel:
363     flags |= F_KRIEGSPIEL_CAPTURE;
364     break;
365   case VariantCapaRandom:
366   case VariantFischeRandom:
367     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
368   case VariantNoCastle:
369   case VariantShatranj:
370   case VariantCourier:
371     flags &= ~F_ALL_CASTLE_OK;
372     break;
373   default:
374     break;
375   }
376   return flags;
377 }
378
379 FILE *gameFileFP, *debugFP;
380
381 /*
382     [AS] Note: sometimes, the sscanf() function is used to parse the input
383     into a fixed-size buffer. Because of this, we must be prepared to
384     receive strings as long as the size of the input buffer, which is currently
385     set to 4K for Windows and 8K for the rest.
386     So, we must either allocate sufficiently large buffers here, or
387     reduce the size of the input buffer in the input reading part.
388 */
389
390 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
391 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
392 char thinkOutput1[MSG_SIZ*10];
393
394 ChessProgramState first, second;
395
396 /* premove variables */
397 int premoveToX = 0;
398 int premoveToY = 0;
399 int premoveFromX = 0;
400 int premoveFromY = 0;
401 int premovePromoChar = 0;
402 int gotPremove = 0;
403 Boolean alarmSounded;
404 /* end premove variables */
405
406 char *ics_prefix = "$";
407 int ics_type = ICS_GENERIC;
408
409 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
410 int pauseExamForwardMostMove = 0;
411 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
412 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
413 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
414 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
415 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
416 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
417 int whiteFlag = FALSE, blackFlag = FALSE;
418 int userOfferedDraw = FALSE;
419 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
420 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
421 int cmailMoveType[CMAIL_MAX_GAMES];
422 long ics_clock_paused = 0;
423 ProcRef icsPR = NoProc, cmailPR = NoProc;
424 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
425 GameMode gameMode = BeginningOfGame;
426 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
427 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
428 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
429 int hiddenThinkOutputState = 0; /* [AS] */
430 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
431 int adjudicateLossPlies = 6;
432 char white_holding[64], black_holding[64];
433 TimeMark lastNodeCountTime;
434 long lastNodeCount=0;
435 int have_sent_ICS_logon = 0;
436 int movesPerSession;
437 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
438 long timeControl_2; /* [AS] Allow separate time controls */
439 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
440 long timeRemaining[2][MAX_MOVES];
441 int matchGame = 0;
442 TimeMark programStartTime;
443 char ics_handle[MSG_SIZ];
444 int have_set_title = 0;
445
446 /* animateTraining preserves the state of appData.animate
447  * when Training mode is activated. This allows the
448  * response to be animated when appData.animate == TRUE and
449  * appData.animateDragging == TRUE.
450  */
451 Boolean animateTraining;
452
453 GameInfo gameInfo;
454
455 AppData appData;
456
457 Board boards[MAX_MOVES];
458 /* [HGM] Following 7 needed for accurate legality tests: */
459 char  epStatus[MAX_MOVES];
460 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
461 char  castlingRank[BOARD_SIZE]; // and corresponding ranks
462 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
463 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
464 int   initialRulePlies, FENrulePlies;
465 char  FENepStatus;
466 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
467 int loadFlag = 0;
468 int shuffleOpenings;
469
470 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
472         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
473     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
474         BlackKing, BlackBishop, BlackKnight, BlackRook }
475 };
476
477 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
478     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
479         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
480     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
481         BlackKing, BlackKing, BlackKnight, BlackRook }
482 };
483
484 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
485     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
486         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
487     { BlackRook, BlackMan, BlackBishop, BlackQueen,
488         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
489 };
490
491 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
492     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
493         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
494     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
495         BlackKing, BlackBishop, BlackKnight, BlackRook }
496 };
497
498 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
499     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
500         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
502         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
503 };
504
505
506 #if (BOARD_SIZE>=10)
507 ChessSquare ShogiArray[2][BOARD_SIZE] = {
508     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
509         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
510     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
511         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
512 };
513
514 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
515     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
516         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
518         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
519 };
520
521 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
523         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
524     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
525         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
526 };
527
528 ChessSquare GreatArray[2][BOARD_SIZE] = {
529     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
530         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
531     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
532         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
533 };
534
535 ChessSquare JanusArray[2][BOARD_SIZE] = {
536     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
537         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
538     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
539         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
540 };
541
542 #ifdef GOTHIC
543 ChessSquare GothicArray[2][BOARD_SIZE] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
545         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
547         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
548 };
549 #else // !GOTHIC
550 #define GothicArray CapablancaArray
551 #endif // !GOTHIC
552
553 #ifdef FALCON
554 ChessSquare FalconArray[2][BOARD_SIZE] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
556         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
558         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !FALCON
561 #define FalconArray CapablancaArray
562 #endif // !FALCON
563
564 #else // !(BOARD_SIZE>=10)
565 #define XiangqiPosition FIDEArray
566 #define CapablancaArray FIDEArray
567 #define GothicArray FIDEArray
568 #define GreatArray FIDEArray
569 #endif // !(BOARD_SIZE>=10)
570
571 #if (BOARD_SIZE>=12)
572 ChessSquare CourierArray[2][BOARD_SIZE] = {
573     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
574         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
576         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
577 };
578 #else // !(BOARD_SIZE>=12)
579 #define CourierArray CapablancaArray
580 #endif // !(BOARD_SIZE>=12)
581
582
583 Board initialPosition;
584
585
586 /* Convert str to a rating. Checks for special cases of "----",
587
588    "++++", etc. Also strips ()'s */
589 int
590 string_to_rating(str)
591   char *str;
592 {
593   while(*str && !isdigit(*str)) ++str;
594   if (!*str)
595     return 0;   /* One of the special "no rating" cases */
596   else
597     return atoi(str);
598 }
599
600 void
601 ClearProgramStats()
602 {
603     /* Init programStats */
604     programStats.movelist[0] = 0;
605     programStats.depth = 0;
606     programStats.nr_moves = 0;
607     programStats.moves_left = 0;
608     programStats.nodes = 0;
609     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
610     programStats.score = 0;
611     programStats.got_only_move = 0;
612     programStats.got_fail = 0;
613     programStats.line_is_book = 0;
614 }
615
616 void
617 InitBackEnd1()
618 {
619     int matched, min, sec;
620
621     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
622
623     GetTimeMark(&programStartTime);
624     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
625
626     ClearProgramStats();
627     programStats.ok_to_send = 1;
628     programStats.seen_stat = 0;
629
630     /*
631      * Initialize game list
632      */
633     ListNew(&gameList);
634
635
636     /*
637      * Internet chess server status
638      */
639     if (appData.icsActive) {
640         appData.matchMode = FALSE;
641         appData.matchGames = 0;
642 #if ZIPPY
643         appData.noChessProgram = !appData.zippyPlay;
644 #else
645         appData.zippyPlay = FALSE;
646         appData.zippyTalk = FALSE;
647         appData.noChessProgram = TRUE;
648 #endif
649         if (*appData.icsHelper != NULLCHAR) {
650             appData.useTelnet = TRUE;
651             appData.telnetProgram = appData.icsHelper;
652         }
653     } else {
654         appData.zippyTalk = appData.zippyPlay = FALSE;
655     }
656
657     /* [AS] Initialize pv info list [HGM] and game state */
658     {
659         int i, j;
660
661         for( i=0; i<MAX_MOVES; i++ ) {
662             pvInfoList[i].depth = -1;
663             epStatus[i]=EP_NONE;
664             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
665         }
666     }
667
668     /*
669      * Parse timeControl resource
670      */
671     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
672                           appData.movesPerSession)) {
673         char buf[MSG_SIZ];
674         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
675         DisplayFatalError(buf, 0, 2);
676     }
677
678     /*
679      * Parse searchTime resource
680      */
681     if (*appData.searchTime != NULLCHAR) {
682         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
683         if (matched == 1) {
684             searchTime = min * 60;
685         } else if (matched == 2) {
686             searchTime = min * 60 + sec;
687         } else {
688             char buf[MSG_SIZ];
689             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
690             DisplayFatalError(buf, 0, 2);
691         }
692     }
693
694     /* [AS] Adjudication threshold */
695     adjudicateLossThreshold = appData.adjudicateLossThreshold;
696
697     first.which = "first";
698     second.which = "second";
699     first.maybeThinking = second.maybeThinking = FALSE;
700     first.pr = second.pr = NoProc;
701     first.isr = second.isr = NULL;
702     first.sendTime = second.sendTime = 2;
703     first.sendDrawOffers = 1;
704     if (appData.firstPlaysBlack) {
705         first.twoMachinesColor = "black\n";
706         second.twoMachinesColor = "white\n";
707     } else {
708         first.twoMachinesColor = "white\n";
709         second.twoMachinesColor = "black\n";
710     }
711     first.program = appData.firstChessProgram;
712     second.program = appData.secondChessProgram;
713     first.host = appData.firstHost;
714     second.host = appData.secondHost;
715     first.dir = appData.firstDirectory;
716     second.dir = appData.secondDirectory;
717     first.other = &second;
718     second.other = &first;
719     first.initString = appData.initString;
720     second.initString = appData.secondInitString;
721     first.computerString = appData.firstComputerString;
722     second.computerString = appData.secondComputerString;
723     first.useSigint = second.useSigint = TRUE;
724     first.useSigterm = second.useSigterm = TRUE;
725     first.reuse = appData.reuseFirst;
726     second.reuse = appData.reuseSecond;
727     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
728     second.nps = appData.secondNPS;
729     first.useSetboard = second.useSetboard = FALSE;
730     first.useSAN = second.useSAN = FALSE;
731     first.usePing = second.usePing = FALSE;
732     first.lastPing = second.lastPing = 0;
733     first.lastPong = second.lastPong = 0;
734     first.usePlayother = second.usePlayother = FALSE;
735     first.useColors = second.useColors = TRUE;
736     first.useUsermove = second.useUsermove = FALSE;
737     first.sendICS = second.sendICS = FALSE;
738     first.sendName = second.sendName = appData.icsActive;
739     first.sdKludge = second.sdKludge = FALSE;
740     first.stKludge = second.stKludge = FALSE;
741     TidyProgramName(first.program, first.host, first.tidy);
742     TidyProgramName(second.program, second.host, second.tidy);
743     first.matchWins = second.matchWins = 0;
744     strcpy(first.variants, appData.variant);
745     strcpy(second.variants, appData.variant);
746     first.analysisSupport = second.analysisSupport = 2; /* detect */
747     first.analyzing = second.analyzing = FALSE;
748     first.initDone = second.initDone = FALSE;
749
750     /* New features added by Tord: */
751     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
752     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
753     /* End of new features added by Tord. */
754     first.fenOverride  = appData.fenOverride1;
755     second.fenOverride = appData.fenOverride2;
756
757     /* [HGM] time odds: set factor for each machine */
758     first.timeOdds  = appData.firstTimeOdds;
759     second.timeOdds = appData.secondTimeOdds;
760     { int norm = 1;
761         if(appData.timeOddsMode) {
762             norm = first.timeOdds;
763             if(norm > second.timeOdds) norm = second.timeOdds;
764         }
765         first.timeOdds /= norm;
766         second.timeOdds /= norm;
767     }
768
769     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
770     first.accumulateTC = appData.firstAccumulateTC;
771     second.accumulateTC = appData.secondAccumulateTC;
772     first.maxNrOfSessions = second.maxNrOfSessions = 1;
773
774     /* [HGM] debug */
775     first.debug = second.debug = FALSE;
776     first.supportsNPS = second.supportsNPS = UNKNOWN;
777
778     /* [HGM] options */
779     first.optionSettings  = appData.firstOptions;
780     second.optionSettings = appData.secondOptions;
781
782     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
783     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
784     first.isUCI = appData.firstIsUCI; /* [AS] */
785     second.isUCI = appData.secondIsUCI; /* [AS] */
786     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
787     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
788
789     if (appData.firstProtocolVersion > PROTOVER ||
790         appData.firstProtocolVersion < 1) {
791       char buf[MSG_SIZ];
792       sprintf(buf, _("protocol version %d not supported"),
793               appData.firstProtocolVersion);
794       DisplayFatalError(buf, 0, 2);
795     } else {
796       first.protocolVersion = appData.firstProtocolVersion;
797     }
798
799     if (appData.secondProtocolVersion > PROTOVER ||
800         appData.secondProtocolVersion < 1) {
801       char buf[MSG_SIZ];
802       sprintf(buf, _("protocol version %d not supported"),
803               appData.secondProtocolVersion);
804       DisplayFatalError(buf, 0, 2);
805     } else {
806       second.protocolVersion = appData.secondProtocolVersion;
807     }
808
809     if (appData.icsActive) {
810         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
811     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
812         appData.clockMode = FALSE;
813         first.sendTime = second.sendTime = 0;
814     }
815
816 #if ZIPPY
817     /* Override some settings from environment variables, for backward
818        compatibility.  Unfortunately it's not feasible to have the env
819        vars just set defaults, at least in xboard.  Ugh.
820     */
821     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
822       ZippyInit();
823     }
824 #endif
825
826     if (appData.noChessProgram) {
827         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
828         sprintf(programVersion, "%s", PACKAGE_STRING);
829     } else {
830 #if 0
831         char *p, *q;
832         q = first.program;
833         while (*q != ' ' && *q != NULLCHAR) q++;
834         p = q;
835         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
836         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
837         sprintf(programVersion, "%s + ", PACKAGE_STRING);
838         strncat(programVersion, p, q - p);
839 #else
840         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
841         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
842         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
843 #endif
844     }
845
846     if (!appData.icsActive) {
847       char buf[MSG_SIZ];
848       /* Check for variants that are supported only in ICS mode,
849          or not at all.  Some that are accepted here nevertheless
850          have bugs; see comments below.
851       */
852       VariantClass variant = StringToVariant(appData.variant);
853       switch (variant) {
854       case VariantBughouse:     /* need four players and two boards */
855       case VariantKriegspiel:   /* need to hide pieces and move details */
856       /* case VariantFischeRandom: (Fabien: moved below) */
857         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
858         DisplayFatalError(buf, 0, 2);
859         return;
860
861       case VariantUnknown:
862       case VariantLoadable:
863       case Variant29:
864       case Variant30:
865       case Variant31:
866       case Variant32:
867       case Variant33:
868       case Variant34:
869       case Variant35:
870       case Variant36:
871       default:
872         sprintf(buf, _("Unknown variant name %s"), appData.variant);
873         DisplayFatalError(buf, 0, 2);
874         return;
875
876       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
877       case VariantFairy:      /* [HGM] TestLegality definitely off! */
878       case VariantGothic:     /* [HGM] should work */
879       case VariantCapablanca: /* [HGM] should work */
880       case VariantCourier:    /* [HGM] initial forced moves not implemented */
881       case VariantShogi:      /* [HGM] drops not tested for legality */
882       case VariantKnightmate: /* [HGM] should work */
883       case VariantCylinder:   /* [HGM] untested */
884       case VariantFalcon:     /* [HGM] untested */
885       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
886                                  offboard interposition not understood */
887       case VariantNormal:     /* definitely works! */
888       case VariantWildCastle: /* pieces not automatically shuffled */
889       case VariantNoCastle:   /* pieces not automatically shuffled */
890       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
891       case VariantLosers:     /* should work except for win condition,
892                                  and doesn't know captures are mandatory */
893       case VariantSuicide:    /* should work except for win condition,
894                                  and doesn't know captures are mandatory */
895       case VariantGiveaway:   /* should work except for win condition,
896                                  and doesn't know captures are mandatory */
897       case VariantTwoKings:   /* should work */
898       case VariantAtomic:     /* should work except for win condition */
899       case Variant3Check:     /* should work except for win condition */
900       case VariantShatranj:   /* should work except for all win conditions */
901       case VariantBerolina:   /* might work if TestLegality is off */
902       case VariantCapaRandom: /* should work */
903       case VariantJanus:      /* should work */
904       case VariantSuper:      /* experimental */
905       case VariantGreat:      /* experimental, requires legality testing to be off */
906         break;
907       }
908     }
909
910     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
911     InitEngineUCI( installDir, &second );
912 }
913
914 int NextIntegerFromString( char ** str, long * value )
915 {
916     int result = -1;
917     char * s = *str;
918
919     while( *s == ' ' || *s == '\t' ) {
920         s++;
921     }
922
923     *value = 0;
924
925     if( *s >= '0' && *s <= '9' ) {
926         while( *s >= '0' && *s <= '9' ) {
927             *value = *value * 10 + (*s - '0');
928             s++;
929         }
930
931         result = 0;
932     }
933
934     *str = s;
935
936     return result;
937 }
938
939 int NextTimeControlFromString( char ** str, long * value )
940 {
941     long temp;
942     int result = NextIntegerFromString( str, &temp );
943
944     if( result == 0 ) {
945         *value = temp * 60; /* Minutes */
946         if( **str == ':' ) {
947             (*str)++;
948             result = NextIntegerFromString( str, &temp );
949             *value += temp; /* Seconds */
950         }
951     }
952
953     return result;
954 }
955
956 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
957 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
958     int result = -1; long temp, temp2;
959
960     if(**str != '+') return -1; // old params remain in force!
961     (*str)++;
962     if( NextTimeControlFromString( str, &temp ) ) return -1;
963
964     if(**str != '/') {
965         /* time only: incremental or sudden-death time control */
966         if(**str == '+') { /* increment follows; read it */
967             (*str)++;
968             if(result = NextIntegerFromString( str, &temp2)) return -1;
969             *inc = temp2 * 1000;
970         } else *inc = 0;
971         *moves = 0; *tc = temp * 1000;
972         return 0;
973     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
974
975     (*str)++; /* classical time control */
976     result = NextTimeControlFromString( str, &temp2);
977     if(result == 0) {
978         *moves = temp/60;
979         *tc    = temp2 * 1000;
980         *inc   = 0;
981     }
982     return result;
983 }
984
985 int GetTimeQuota(int movenr)
986 {   /* [HGM] get time to add from the multi-session time-control string */
987     int moves=1; /* kludge to force reading of first session */
988     long time, increment;
989     char *s = fullTimeControlString;
990
991     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
992     do {
993         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
994         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
995         if(movenr == -1) return time;    /* last move before new session     */
996         if(!moves) return increment;     /* current session is incremental   */
997         if(movenr >= 0) movenr -= moves; /* we already finished this session */
998     } while(movenr >= -1);               /* try again for next session       */
999
1000     return 0; // no new time quota on this move
1001 }
1002
1003 int
1004 ParseTimeControl(tc, ti, mps)
1005      char *tc;
1006      int ti;
1007      int mps;
1008 {
1009 #if 0
1010     int matched, min, sec;
1011
1012     matched = sscanf(tc, "%d:%d", &min, &sec);
1013     if (matched == 1) {
1014         timeControl = min * 60 * 1000;
1015     } else if (matched == 2) {
1016         timeControl = (min * 60 + sec) * 1000;
1017     } else {
1018         return FALSE;
1019     }
1020 #else
1021     long tc1;
1022     long tc2;
1023     char buf[MSG_SIZ];
1024
1025     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1026     if(ti > 0) {
1027         if(mps)
1028              sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1029         else sprintf(buf, "+%s+%d", tc, ti);
1030     } else {
1031         if(mps)
1032              sprintf(buf, "+%d/%s", mps, tc);
1033         else sprintf(buf, "+%s", tc);
1034     }
1035     fullTimeControlString = StrSave(buf);
1036
1037     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1038         return FALSE;
1039     }
1040
1041     if( *tc == '/' ) {
1042         /* Parse second time control */
1043         tc++;
1044
1045         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1046             return FALSE;
1047         }
1048
1049         if( tc2 == 0 ) {
1050             return FALSE;
1051         }
1052
1053         timeControl_2 = tc2 * 1000;
1054     }
1055     else {
1056         timeControl_2 = 0;
1057     }
1058
1059     if( tc1 == 0 ) {
1060         return FALSE;
1061     }
1062
1063     timeControl = tc1 * 1000;
1064 #endif
1065
1066     if (ti >= 0) {
1067         timeIncrement = ti * 1000;  /* convert to ms */
1068         movesPerSession = 0;
1069     } else {
1070         timeIncrement = 0;
1071         movesPerSession = mps;
1072     }
1073     return TRUE;
1074 }
1075
1076 void
1077 InitBackEnd2()
1078 {
1079   if (appData.debugMode) {
1080     fprintf(debugFP, "%s\n", programVersion);
1081   }
1082
1083   if (appData.matchGames > 0) {
1084     appData.matchMode = TRUE;
1085   } else if (appData.matchMode) {
1086     appData.matchGames = 1;
1087   }
1088   if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1089     appData.matchGames = appData.sameColorGames;
1090   if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1091     if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1092     if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1093   }
1094   Reset(TRUE, FALSE);
1095   if (appData.noChessProgram || first.protocolVersion == 1) {
1096     InitBackEnd3();
1097   } else {
1098     /* kludge: allow timeout for initial "feature" commands */
1099     FreezeUI();
1100     DisplayMessage("", _("Starting chess program"));
1101     ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1102   }
1103 }
1104
1105 void
1106 InitBackEnd3 P((void))
1107 {
1108     GameMode initialMode;
1109     char buf[MSG_SIZ];
1110     int err;
1111
1112     InitChessProgram(&first, startedFromSetupPosition);
1113
1114
1115     if (appData.icsActive) {
1116 #ifdef WIN32
1117         /* [DM] Make a console window if needed [HGM] merged ifs */
1118         ConsoleCreate();
1119 #endif
1120         err = establish();
1121         if (err != 0) {
1122             if (*appData.icsCommPort != NULLCHAR) {
1123                 sprintf(buf, _("Could not open comm port %s"),
1124                         appData.icsCommPort);
1125             } else {
1126                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1127                         appData.icsHost, appData.icsPort);
1128             }
1129             DisplayFatalError(buf, err, 1);
1130             return;
1131         }
1132         SetICSMode();
1133         telnetISR =
1134           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1135         fromUserISR =
1136           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1137     } else if (appData.noChessProgram) {
1138         SetNCPMode();
1139     } else {
1140         SetGNUMode();
1141     }
1142
1143     if (*appData.cmailGameName != NULLCHAR) {
1144         SetCmailMode();
1145         OpenLoopback(&cmailPR);
1146         cmailISR =
1147           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1148     }
1149
1150     ThawUI();
1151     DisplayMessage("", "");
1152     if (StrCaseCmp(appData.initialMode, "") == 0) {
1153       initialMode = BeginningOfGame;
1154     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1155       initialMode = TwoMachinesPlay;
1156     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1157       initialMode = AnalyzeFile;
1158     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1159       initialMode = AnalyzeMode;
1160     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1161       initialMode = MachinePlaysWhite;
1162     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1163       initialMode = MachinePlaysBlack;
1164     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1165       initialMode = EditGame;
1166     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1167       initialMode = EditPosition;
1168     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1169       initialMode = Training;
1170     } else {
1171       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1172       DisplayFatalError(buf, 0, 2);
1173       return;
1174     }
1175
1176     if (appData.matchMode) {
1177         /* Set up machine vs. machine match */
1178         if (appData.noChessProgram) {
1179             DisplayFatalError(_("Can't have a match with no chess programs"),
1180                               0, 2);
1181             return;
1182         }
1183         matchMode = TRUE;
1184         matchGame = 1;
1185         if (*appData.loadGameFile != NULLCHAR) {
1186             int index = appData.loadGameIndex; // [HGM] autoinc
1187             if(index<0) lastIndex = index = 1;
1188             if (!LoadGameFromFile(appData.loadGameFile,
1189                                   index,
1190                                   appData.loadGameFile, FALSE)) {
1191                 DisplayFatalError(_("Bad game file"), 0, 1);
1192                 return;
1193             }
1194         } else if (*appData.loadPositionFile != NULLCHAR) {
1195             int index = appData.loadPositionIndex; // [HGM] autoinc
1196             if(index<0) lastIndex = index = 1;
1197             if (!LoadPositionFromFile(appData.loadPositionFile,
1198                                       index,
1199                                       appData.loadPositionFile)) {
1200                 DisplayFatalError(_("Bad position file"), 0, 1);
1201                 return;
1202             }
1203         }
1204         TwoMachinesEvent();
1205     } else if (*appData.cmailGameName != NULLCHAR) {
1206         /* Set up cmail mode */
1207         ReloadCmailMsgEvent(TRUE);
1208     } else {
1209         /* Set up other modes */
1210         if (initialMode == AnalyzeFile) {
1211           if (*appData.loadGameFile == NULLCHAR) {
1212             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1213             return;
1214           }
1215         }
1216         if (*appData.loadGameFile != NULLCHAR) {
1217             (void) LoadGameFromFile(appData.loadGameFile,
1218                                     appData.loadGameIndex,
1219                                     appData.loadGameFile, TRUE);
1220         } else if (*appData.loadPositionFile != NULLCHAR) {
1221             (void) LoadPositionFromFile(appData.loadPositionFile,
1222                                         appData.loadPositionIndex,
1223                                         appData.loadPositionFile);
1224             /* [HGM] try to make self-starting even after FEN load */
1225             /* to allow automatic setup of fairy variants with wtm */
1226             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1227                 gameMode = BeginningOfGame;
1228                 setboardSpoiledMachineBlack = 1;
1229             }
1230             /* [HGM] loadPos: make that every new game uses the setup */
1231             /* from file as long as we do not switch variant          */
1232             if(!blackPlaysFirst) { int i;
1233                 startedFromPositionFile = TRUE;
1234                 CopyBoard(filePosition, boards[0]);
1235                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1236             }
1237         }
1238         if (initialMode == AnalyzeMode) {
1239           if (appData.noChessProgram) {
1240             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1241             return;
1242           }
1243           if (appData.icsActive) {
1244             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1245             return;
1246           }
1247           AnalyzeModeEvent();
1248         } else if (initialMode == AnalyzeFile) {
1249           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1250           ShowThinkingEvent();
1251           AnalyzeFileEvent();
1252           AnalysisPeriodicEvent(1);
1253         } else if (initialMode == MachinePlaysWhite) {
1254           if (appData.noChessProgram) {
1255             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1256                               0, 2);
1257             return;
1258           }
1259           if (appData.icsActive) {
1260             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1261                               0, 2);
1262             return;
1263           }
1264           MachineWhiteEvent();
1265         } else if (initialMode == MachinePlaysBlack) {
1266           if (appData.noChessProgram) {
1267             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1268                               0, 2);
1269             return;
1270           }
1271           if (appData.icsActive) {
1272             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1273                               0, 2);
1274             return;
1275           }
1276           MachineBlackEvent();
1277         } else if (initialMode == TwoMachinesPlay) {
1278           if (appData.noChessProgram) {
1279             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1280                               0, 2);
1281             return;
1282           }
1283           if (appData.icsActive) {
1284             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1285                               0, 2);
1286             return;
1287           }
1288           TwoMachinesEvent();
1289         } else if (initialMode == EditGame) {
1290           EditGameEvent();
1291         } else if (initialMode == EditPosition) {
1292           EditPositionEvent();
1293         } else if (initialMode == Training) {
1294           if (*appData.loadGameFile == NULLCHAR) {
1295             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1296             return;
1297           }
1298           TrainingEvent();
1299         }
1300     }
1301 }
1302
1303 /*
1304  * Establish will establish a contact to a remote host.port.
1305  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1306  *  used to talk to the host.
1307  * Returns 0 if okay, error code if not.
1308  */
1309 int
1310 establish()
1311 {
1312     char buf[MSG_SIZ];
1313
1314     if (*appData.icsCommPort != NULLCHAR) {
1315         /* Talk to the host through a serial comm port */
1316         return OpenCommPort(appData.icsCommPort, &icsPR);
1317
1318     } else if (*appData.gateway != NULLCHAR) {
1319         if (*appData.remoteShell == NULLCHAR) {
1320             /* Use the rcmd protocol to run telnet program on a gateway host */
1321             snprintf(buf, sizeof(buf), "%s %s %s",
1322                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1323             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1324
1325         } else {
1326             /* Use the rsh program to run telnet program on a gateway host */
1327             if (*appData.remoteUser == NULLCHAR) {
1328                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1329                         appData.gateway, appData.telnetProgram,
1330                         appData.icsHost, appData.icsPort);
1331             } else {
1332                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1333                         appData.remoteShell, appData.gateway,
1334                         appData.remoteUser, appData.telnetProgram,
1335                         appData.icsHost, appData.icsPort);
1336             }
1337             return StartChildProcess(buf, "", &icsPR);
1338
1339         }
1340     } else if (appData.useTelnet) {
1341         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1342
1343     } else {
1344         /* TCP socket interface differs somewhat between
1345            Unix and NT; handle details in the front end.
1346            */
1347         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1348     }
1349 }
1350
1351 void
1352 show_bytes(fp, buf, count)
1353      FILE *fp;
1354      char *buf;
1355      int count;
1356 {
1357     while (count--) {
1358         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1359             fprintf(fp, "\\%03o", *buf & 0xff);
1360         } else {
1361             putc(*buf, fp);
1362         }
1363         buf++;
1364     }
1365     fflush(fp);
1366 }
1367
1368 /* Returns an errno value */
1369 int
1370 OutputMaybeTelnet(pr, message, count, outError)
1371      ProcRef pr;
1372      char *message;
1373      int count;
1374      int *outError;
1375 {
1376     char buf[8192], *p, *q, *buflim;
1377     int left, newcount, outcount;
1378
1379     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1380         *appData.gateway != NULLCHAR) {
1381         if (appData.debugMode) {
1382             fprintf(debugFP, ">ICS: ");
1383             show_bytes(debugFP, message, count);
1384             fprintf(debugFP, "\n");
1385         }
1386         return OutputToProcess(pr, message, count, outError);
1387     }
1388
1389     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1390     p = message;
1391     q = buf;
1392     left = count;
1393     newcount = 0;
1394     while (left) {
1395         if (q >= buflim) {
1396             if (appData.debugMode) {
1397                 fprintf(debugFP, ">ICS: ");
1398                 show_bytes(debugFP, buf, newcount);
1399                 fprintf(debugFP, "\n");
1400             }
1401             outcount = OutputToProcess(pr, buf, newcount, outError);
1402             if (outcount < newcount) return -1; /* to be sure */
1403             q = buf;
1404             newcount = 0;
1405         }
1406         if (*p == '\n') {
1407             *q++ = '\r';
1408             newcount++;
1409         } else if (((unsigned char) *p) == TN_IAC) {
1410             *q++ = (char) TN_IAC;
1411             newcount ++;
1412         }
1413         *q++ = *p++;
1414         newcount++;
1415         left--;
1416     }
1417     if (appData.debugMode) {
1418         fprintf(debugFP, ">ICS: ");
1419         show_bytes(debugFP, buf, newcount);
1420         fprintf(debugFP, "\n");
1421     }
1422     outcount = OutputToProcess(pr, buf, newcount, outError);
1423     if (outcount < newcount) return -1; /* to be sure */
1424     return count;
1425 }
1426
1427 void
1428 read_from_player(isr, closure, message, count, error)
1429      InputSourceRef isr;
1430      VOIDSTAR closure;
1431      char *message;
1432      int count;
1433      int error;
1434 {
1435     int outError, outCount;
1436     static int gotEof = 0;
1437
1438     /* Pass data read from player on to ICS */
1439     if (count > 0) {
1440         gotEof = 0;
1441         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1442         if (outCount < count) {
1443             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1444         }
1445     } else if (count < 0) {
1446         RemoveInputSource(isr);
1447         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1448     } else if (gotEof++ > 0) {
1449         RemoveInputSource(isr);
1450         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1451     }
1452 }
1453
1454 void
1455 SendToICS(s)
1456      char *s;
1457 {
1458     int count, outCount, outError;
1459
1460     if (icsPR == NULL) return;
1461
1462     count = strlen(s);
1463     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1464     if (outCount < count) {
1465         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1466     }
1467 }
1468
1469 /* This is used for sending logon scripts to the ICS. Sending
1470    without a delay causes problems when using timestamp on ICC
1471    (at least on my machine). */
1472 void
1473 SendToICSDelayed(s,msdelay)
1474      char *s;
1475      long msdelay;
1476 {
1477     int count, outCount, outError;
1478
1479     if (icsPR == NULL) return;
1480
1481     count = strlen(s);
1482     if (appData.debugMode) {
1483         fprintf(debugFP, ">ICS: ");
1484         show_bytes(debugFP, s, count);
1485         fprintf(debugFP, "\n");
1486     }
1487     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1488                                       msdelay);
1489     if (outCount < count) {
1490         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1491     }
1492 }
1493
1494
1495 /* Remove all highlighting escape sequences in s
1496    Also deletes any suffix starting with '('
1497    */
1498 char *
1499 StripHighlightAndTitle(s)
1500      char *s;
1501 {
1502     static char retbuf[MSG_SIZ];
1503     char *p = retbuf;
1504
1505     while (*s != NULLCHAR) {
1506         while (*s == '\033') {
1507             while (*s != NULLCHAR && !isalpha(*s)) s++;
1508             if (*s != NULLCHAR) s++;
1509         }
1510         while (*s != NULLCHAR && *s != '\033') {
1511             if (*s == '(' || *s == '[') {
1512                 *p = NULLCHAR;
1513                 return retbuf;
1514             }
1515             *p++ = *s++;
1516         }
1517     }
1518     *p = NULLCHAR;
1519     return retbuf;
1520 }
1521
1522 /* Remove all highlighting escape sequences in s */
1523 char *
1524 StripHighlight(s)
1525      char *s;
1526 {
1527     static char retbuf[MSG_SIZ];
1528     char *p = retbuf;
1529
1530     while (*s != NULLCHAR) {
1531         while (*s == '\033') {
1532             while (*s != NULLCHAR && !isalpha(*s)) s++;
1533             if (*s != NULLCHAR) s++;
1534         }
1535         while (*s != NULLCHAR && *s != '\033') {
1536             *p++ = *s++;
1537         }
1538     }
1539     *p = NULLCHAR;
1540     return retbuf;
1541 }
1542
1543 char *variantNames[] = VARIANT_NAMES;
1544 char *
1545 VariantName(v)
1546      VariantClass v;
1547 {
1548     return variantNames[v];
1549 }
1550
1551
1552 /* Identify a variant from the strings the chess servers use or the
1553    PGN Variant tag names we use. */
1554 VariantClass
1555 StringToVariant(e)
1556      char *e;
1557 {
1558     char *p;
1559     int wnum = -1;
1560     VariantClass v = VariantNormal;
1561     int i, found = FALSE;
1562     char buf[MSG_SIZ];
1563
1564     if (!e) return v;
1565
1566     /* [HGM] skip over optional board-size prefixes */
1567     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1568         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1569         while( *e++ != '_');
1570     }
1571
1572     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1573       if (StrCaseStr(e, variantNames[i])) {
1574         v = (VariantClass) i;
1575         found = TRUE;
1576         break;
1577       }
1578     }
1579
1580     if (!found) {
1581       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1582           || StrCaseStr(e, "wild/fr")
1583           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1584         v = VariantFischeRandom;
1585       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1586                  (i = 1, p = StrCaseStr(e, "w"))) {
1587         p += i;
1588         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1589         if (isdigit(*p)) {
1590           wnum = atoi(p);
1591         } else {
1592           wnum = -1;
1593         }
1594         switch (wnum) {
1595         case 0: /* FICS only, actually */
1596         case 1:
1597           /* Castling legal even if K starts on d-file */
1598           v = VariantWildCastle;
1599           break;
1600         case 2:
1601         case 3:
1602         case 4:
1603           /* Castling illegal even if K & R happen to start in
1604              normal positions. */
1605           v = VariantNoCastle;
1606           break;
1607         case 5:
1608         case 7:
1609         case 8:
1610         case 10:
1611         case 11:
1612         case 12:
1613         case 13:
1614         case 14:
1615         case 15:
1616         case 18:
1617         case 19:
1618           /* Castling legal iff K & R start in normal positions */
1619           v = VariantNormal;
1620           break;
1621         case 6:
1622         case 20:
1623         case 21:
1624           /* Special wilds for position setup; unclear what to do here */
1625           v = VariantLoadable;
1626           break;
1627         case 9:
1628           /* Bizarre ICC game */
1629           v = VariantTwoKings;
1630           break;
1631         case 16:
1632           v = VariantKriegspiel;
1633           break;
1634         case 17:
1635           v = VariantLosers;
1636           break;
1637         case 22:
1638           v = VariantFischeRandom;
1639           break;
1640         case 23:
1641           v = VariantCrazyhouse;
1642           break;
1643         case 24:
1644           v = VariantBughouse;
1645           break;
1646         case 25:
1647           v = Variant3Check;
1648           break;
1649         case 26:
1650           /* Not quite the same as FICS suicide! */
1651           v = VariantGiveaway;
1652           break;
1653         case 27:
1654           v = VariantAtomic;
1655           break;
1656         case 28:
1657           v = VariantShatranj;
1658           break;
1659
1660         /* Temporary names for future ICC types.  The name *will* change in
1661            the next xboard/WinBoard release after ICC defines it. */
1662         case 29:
1663           v = Variant29;
1664           break;
1665         case 30:
1666           v = Variant30;
1667           break;
1668         case 31:
1669           v = Variant31;
1670           break;
1671         case 32:
1672           v = Variant32;
1673           break;
1674         case 33:
1675           v = Variant33;
1676           break;
1677         case 34:
1678           v = Variant34;
1679           break;
1680         case 35:
1681           v = Variant35;
1682           break;
1683         case 36:
1684           v = Variant36;
1685           break;
1686         case 37:
1687           v = VariantShogi;
1688           break;
1689         case 38:
1690           v = VariantXiangqi;
1691           break;
1692         case 39:
1693           v = VariantCourier;
1694           break;
1695         case 40:
1696           v = VariantGothic;
1697           break;
1698         case 41:
1699           v = VariantCapablanca;
1700           break;
1701         case 42:
1702           v = VariantKnightmate;
1703           break;
1704         case 43:
1705           v = VariantFairy;
1706           break;
1707         case 44:
1708           v = VariantCylinder;
1709           break;
1710         case 45:
1711           v = VariantFalcon;
1712           break;
1713         case 46:
1714           v = VariantCapaRandom;
1715           break;
1716         case 47:
1717           v = VariantBerolina;
1718           break;
1719         case 48:
1720           v = VariantJanus;
1721           break;
1722         case 49:
1723           v = VariantSuper;
1724           break;
1725         case 50:
1726           v = VariantGreat;
1727           break;
1728         case -1:
1729           /* Found "wild" or "w" in the string but no number;
1730              must assume it's normal chess. */
1731           v = VariantNormal;
1732           break;
1733         default:
1734           sprintf(buf, _("Unknown wild type %d"), wnum);
1735           DisplayError(buf, 0);
1736           v = VariantUnknown;
1737           break;
1738         }
1739       }
1740     }
1741     if (appData.debugMode) {
1742       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1743               e, wnum, VariantName(v));
1744     }
1745     return v;
1746 }
1747
1748 static int leftover_start = 0, leftover_len = 0;
1749 char star_match[STAR_MATCH_N][MSG_SIZ];
1750
1751 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1752    advance *index beyond it, and set leftover_start to the new value of
1753    *index; else return FALSE.  If pattern contains the character '*', it
1754    matches any sequence of characters not containing '\r', '\n', or the
1755    character following the '*' (if any), and the matched sequence(s) are
1756    copied into star_match.
1757    */
1758 int
1759 looking_at(buf, index, pattern)
1760      char *buf;
1761      int *index;
1762      char *pattern;
1763 {
1764     char *bufp = &buf[*index], *patternp = pattern;
1765     int star_count = 0;
1766     char *matchp = star_match[0];
1767
1768     for (;;) {
1769         if (*patternp == NULLCHAR) {
1770             *index = leftover_start = bufp - buf;
1771             *matchp = NULLCHAR;
1772             return TRUE;
1773         }
1774         if (*bufp == NULLCHAR) return FALSE;
1775         if (*patternp == '*') {
1776             if (*bufp == *(patternp + 1)) {
1777                 *matchp = NULLCHAR;
1778                 matchp = star_match[++star_count];
1779                 patternp += 2;
1780                 bufp++;
1781                 continue;
1782             } else if (*bufp == '\n' || *bufp == '\r') {
1783                 patternp++;
1784                 if (*patternp == NULLCHAR)
1785                   continue;
1786                 else
1787                   return FALSE;
1788             } else {
1789                 *matchp++ = *bufp++;
1790                 continue;
1791             }
1792         }
1793         if (*patternp != *bufp) return FALSE;
1794         patternp++;
1795         bufp++;
1796     }
1797 }
1798
1799 void
1800 SendToPlayer(data, length)
1801      char *data;
1802      int length;
1803 {
1804     int error, outCount;
1805     outCount = OutputToProcess(NoProc, data, length, &error);
1806     if (outCount < length) {
1807         DisplayFatalError(_("Error writing to display"), error, 1);
1808     }
1809 }
1810
1811 void
1812 PackHolding(packed, holding)
1813      char packed[];
1814      char *holding;
1815 {
1816     char *p = holding;
1817     char *q = packed;
1818     int runlength = 0;
1819     int curr = 9999;
1820     do {
1821         if (*p == curr) {
1822             runlength++;
1823         } else {
1824             switch (runlength) {
1825               case 0:
1826                 break;
1827               case 1:
1828                 *q++ = curr;
1829                 break;
1830               case 2:
1831                 *q++ = curr;
1832                 *q++ = curr;
1833                 break;
1834               default:
1835                 sprintf(q, "%d", runlength);
1836                 while (*q) q++;
1837                 *q++ = curr;
1838                 break;
1839             }
1840             runlength = 1;
1841             curr = *p;
1842         }
1843     } while (*p++);
1844     *q = NULLCHAR;
1845 }
1846
1847 /* Telnet protocol requests from the front end */
1848 void
1849 TelnetRequest(ddww, option)
1850      unsigned char ddww, option;
1851 {
1852     unsigned char msg[3];
1853     int outCount, outError;
1854
1855     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1856
1857     if (appData.debugMode) {
1858         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1859         switch (ddww) {
1860           case TN_DO:
1861             ddwwStr = "DO";
1862             break;
1863           case TN_DONT:
1864             ddwwStr = "DONT";
1865             break;
1866           case TN_WILL:
1867             ddwwStr = "WILL";
1868             break;
1869           case TN_WONT:
1870             ddwwStr = "WONT";
1871             break;
1872           default:
1873             ddwwStr = buf1;
1874             sprintf(buf1, "%d", ddww);
1875             break;
1876         }
1877         switch (option) {
1878           case TN_ECHO:
1879             optionStr = "ECHO";
1880             break;
1881           default:
1882             optionStr = buf2;
1883             sprintf(buf2, "%d", option);
1884             break;
1885         }
1886         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1887     }
1888     msg[0] = TN_IAC;
1889     msg[1] = ddww;
1890     msg[2] = option;
1891     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1892     if (outCount < 3) {
1893         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1894     }
1895 }
1896
1897 void
1898 DoEcho()
1899 {
1900     if (!appData.icsActive) return;
1901     TelnetRequest(TN_DO, TN_ECHO);
1902 }
1903
1904 void
1905 DontEcho()
1906 {
1907     if (!appData.icsActive) return;
1908     TelnetRequest(TN_DONT, TN_ECHO);
1909 }
1910
1911 void
1912 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1913 {
1914     /* put the holdings sent to us by the server on the board holdings area */
1915     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1916     char p;
1917     ChessSquare piece;
1918
1919     if(gameInfo.holdingsWidth < 2)  return;
1920
1921     if( (int)lowestPiece >= BlackPawn ) {
1922         holdingsColumn = 0;
1923         countsColumn = 1;
1924         holdingsStartRow = BOARD_HEIGHT-1;
1925         direction = -1;
1926     } else {
1927         holdingsColumn = BOARD_WIDTH-1;
1928         countsColumn = BOARD_WIDTH-2;
1929         holdingsStartRow = 0;
1930         direction = 1;
1931     }
1932
1933     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934         board[i][holdingsColumn] = EmptySquare;
1935         board[i][countsColumn]   = (ChessSquare) 0;
1936     }
1937     while( (p=*holdings++) != NULLCHAR ) {
1938         piece = CharToPiece( ToUpper(p) );
1939         if(piece == EmptySquare) continue;
1940         /*j = (int) piece - (int) WhitePawn;*/
1941         j = PieceToNumber(piece);
1942         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943         if(j < 0) continue;               /* should not happen */
1944         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946         board[holdingsStartRow+j*direction][countsColumn]++;
1947     }
1948
1949 }
1950
1951
1952 void
1953 VariantSwitch(Board board, VariantClass newVariant)
1954 {
1955    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1956    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1957 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1958
1959    startedFromPositionFile = FALSE;
1960    if(gameInfo.variant == newVariant) return;
1961
1962    /* [HGM] This routine is called each time an assignment is made to
1963     * gameInfo.variant during a game, to make sure the board sizes
1964     * are set to match the new variant. If that means adding or deleting
1965     * holdings, we shift the playing board accordingly
1966     * This kludge is needed because in ICS observe mode, we get boards
1967     * of an ongoing game without knowing the variant, and learn about the
1968     * latter only later. This can be because of the move list we requested,
1969     * in which case the game history is refilled from the beginning anyway,
1970     * but also when receiving holdings of a crazyhouse game. In the latter
1971     * case we want to add those holdings to the already received position.
1972     */
1973
1974
1975   if (appData.debugMode) {
1976     fprintf(debugFP, "Switch board from %s to %s\n",
1977                VariantName(gameInfo.variant), VariantName(newVariant));
1978     setbuf(debugFP, NULL);
1979   }
1980     shuffleOpenings = 0;       /* [HGM] shuffle */
1981     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1982     switch(newVariant) {
1983             case VariantShogi:
1984               newWidth = 9;  newHeight = 9;
1985               gameInfo.holdingsSize = 7;
1986             case VariantBughouse:
1987             case VariantCrazyhouse:
1988               newHoldingsWidth = 2; break;
1989             default:
1990               newHoldingsWidth = gameInfo.holdingsSize = 0;
1991     }
1992
1993     if(newWidth  != gameInfo.boardWidth  ||
1994        newHeight != gameInfo.boardHeight ||
1995        newHoldingsWidth != gameInfo.holdingsWidth ) {
1996
1997         /* shift position to new playing area, if needed */
1998         if(newHoldingsWidth > gameInfo.holdingsWidth) {
1999            for(i=0; i<BOARD_HEIGHT; i++)
2000                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2001                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2002                                                      board[i][j];
2003            for(i=0; i<newHeight; i++) {
2004                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2005                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2006            }
2007         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2008            for(i=0; i<BOARD_HEIGHT; i++)
2009                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2010                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011                                                  board[i][j];
2012         }
2013
2014         gameInfo.boardWidth  = newWidth;
2015         gameInfo.boardHeight = newHeight;
2016         gameInfo.holdingsWidth = newHoldingsWidth;
2017         gameInfo.variant = newVariant;
2018         InitDrawingSizes(-2, 0);
2019
2020         /* [HGM] The following should definitely be solved in a better way */
2021 #if 0
2022         CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2023         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2024         saveEP = epStatus[0];
2025 #endif
2026         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2027 #if 0
2028         epStatus[0] = saveEP;
2029         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2030         CopyBoard(tempBoard, board); /* restore position received from ICS   */
2031 #endif
2032     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2033
2034     forwardMostMove = oldForwardMostMove;
2035     backwardMostMove = oldBackwardMostMove;
2036     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2037 }
2038
2039 static int loggedOn = FALSE;
2040
2041 /*-- Game start info cache: --*/
2042 int gs_gamenum;
2043 char gs_kind[MSG_SIZ];
2044 static char player1Name[128] = "";
2045 static char player2Name[128] = "";
2046 static int player1Rating = -1;
2047 static int player2Rating = -1;
2048 /*----------------------------*/
2049
2050 ColorClass curColor = ColorNormal;
2051 int suppressKibitz = 0;
2052
2053 void
2054 read_from_ics(isr, closure, data, count, error)
2055      InputSourceRef isr;
2056      VOIDSTAR closure;
2057      char *data;
2058      int count;
2059      int error;
2060 {
2061 #define BUF_SIZE 8192
2062 #define STARTED_NONE 0
2063 #define STARTED_MOVES 1
2064 #define STARTED_BOARD 2
2065 #define STARTED_OBSERVE 3
2066 #define STARTED_HOLDINGS 4
2067 #define STARTED_CHATTER 5
2068 #define STARTED_COMMENT 6
2069 #define STARTED_MOVES_NOHIDE 7
2070
2071     static int started = STARTED_NONE;
2072     static char parse[20000];
2073     static int parse_pos = 0;
2074     static char buf[BUF_SIZE + 1];
2075     static int firstTime = TRUE, intfSet = FALSE;
2076     static ColorClass prevColor = ColorNormal;
2077     static int savingComment = FALSE;
2078     char str[500];
2079     int i, oldi;
2080     int buf_len;
2081     int next_out;
2082     int tkind;
2083     int backup;    /* [DM] For zippy color lines */
2084     char *p;
2085
2086     if (appData.debugMode) {
2087       if (!error) {
2088         fprintf(debugFP, "<ICS: ");
2089         show_bytes(debugFP, data, count);
2090         fprintf(debugFP, "\n");
2091       }
2092     }
2093
2094     if (appData.debugMode) { int f = forwardMostMove;
2095         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2096                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2097     }
2098     if (count > 0) {
2099         /* If last read ended with a partial line that we couldn't parse,
2100            prepend it to the new read and try again. */
2101         if (leftover_len > 0) {
2102             for (i=0; i<leftover_len; i++)
2103               buf[i] = buf[leftover_start + i];
2104         }
2105
2106         /* Copy in new characters, removing nulls and \r's */
2107         buf_len = leftover_len;
2108         for (i = 0; i < count; i++) {
2109             if (data[i] != NULLCHAR && data[i] != '\r')
2110               buf[buf_len++] = data[i];
2111             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2112                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ')
2113                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2114         }
2115
2116         buf[buf_len] = NULLCHAR;
2117         next_out = leftover_len;
2118         leftover_start = 0;
2119
2120         i = 0;
2121         while (i < buf_len) {
2122             /* Deal with part of the TELNET option negotiation
2123                protocol.  We refuse to do anything beyond the
2124                defaults, except that we allow the WILL ECHO option,
2125                which ICS uses to turn off password echoing when we are
2126                directly connected to it.  We reject this option
2127                if localLineEditing mode is on (always on in xboard)
2128                and we are talking to port 23, which might be a real
2129                telnet server that will try to keep WILL ECHO on permanently.
2130              */
2131             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2132                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2133                 unsigned char option;
2134                 oldi = i;
2135                 switch ((unsigned char) buf[++i]) {
2136                   case TN_WILL:
2137                     if (appData.debugMode)
2138                       fprintf(debugFP, "\n<WILL ");
2139                     switch (option = (unsigned char) buf[++i]) {
2140                       case TN_ECHO:
2141                         if (appData.debugMode)
2142                           fprintf(debugFP, "ECHO ");
2143                         /* Reply only if this is a change, according
2144                            to the protocol rules. */
2145                         if (remoteEchoOption) break;
2146                         if (appData.localLineEditing &&
2147                             atoi(appData.icsPort) == TN_PORT) {
2148                             TelnetRequest(TN_DONT, TN_ECHO);
2149                         } else {
2150                             EchoOff();
2151                             TelnetRequest(TN_DO, TN_ECHO);
2152                             remoteEchoOption = TRUE;
2153                         }
2154                         break;
2155                       default:
2156                         if (appData.debugMode)
2157                           fprintf(debugFP, "%d ", option);
2158                         /* Whatever this is, we don't want it. */
2159                         TelnetRequest(TN_DONT, option);
2160                         break;
2161                     }
2162                     break;
2163                   case TN_WONT:
2164                     if (appData.debugMode)
2165                       fprintf(debugFP, "\n<WONT ");
2166                     switch (option = (unsigned char) buf[++i]) {
2167                       case TN_ECHO:
2168                         if (appData.debugMode)
2169                           fprintf(debugFP, "ECHO ");
2170                         /* Reply only if this is a change, according
2171                            to the protocol rules. */
2172                         if (!remoteEchoOption) break;
2173                         EchoOn();
2174                         TelnetRequest(TN_DONT, TN_ECHO);
2175                         remoteEchoOption = FALSE;
2176                         break;
2177                       default:
2178                         if (appData.debugMode)
2179                           fprintf(debugFP, "%d ", (unsigned char) option);
2180                         /* Whatever this is, it must already be turned
2181                            off, because we never agree to turn on
2182                            anything non-default, so according to the
2183                            protocol rules, we don't reply. */
2184                         break;
2185                     }
2186                     break;
2187                   case TN_DO:
2188                     if (appData.debugMode)
2189                       fprintf(debugFP, "\n<DO ");
2190                     switch (option = (unsigned char) buf[++i]) {
2191                       default:
2192                         /* Whatever this is, we refuse to do it. */
2193                         if (appData.debugMode)
2194                           fprintf(debugFP, "%d ", option);
2195                         TelnetRequest(TN_WONT, option);
2196                         break;
2197                     }
2198                     break;
2199                   case TN_DONT:
2200                     if (appData.debugMode)
2201                       fprintf(debugFP, "\n<DONT ");
2202                     switch (option = (unsigned char) buf[++i]) {
2203                       default:
2204                         if (appData.debugMode)
2205                           fprintf(debugFP, "%d ", option);
2206                         /* Whatever this is, we are already not doing
2207                            it, because we never agree to do anything
2208                            non-default, so according to the protocol
2209                            rules, we don't reply. */
2210                         break;
2211                     }
2212                     break;
2213                   case TN_IAC:
2214                     if (appData.debugMode)
2215                       fprintf(debugFP, "\n<IAC ");
2216                     /* Doubled IAC; pass it through */
2217                     i--;
2218                     break;
2219                   default:
2220                     if (appData.debugMode)
2221                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2222                     /* Drop all other telnet commands on the floor */
2223                     break;
2224                 }
2225                 if (oldi > next_out)
2226                   SendToPlayer(&buf[next_out], oldi - next_out);
2227                 if (++i > next_out)
2228                   next_out = i;
2229                 continue;
2230             }
2231
2232             /* OK, this at least will *usually* work */
2233             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2234                 loggedOn = TRUE;
2235             }
2236
2237             if (loggedOn && !intfSet) {
2238                 if (ics_type == ICS_ICC) {
2239                   sprintf(str,
2240                           "/set-quietly interface %s\n/set-quietly style 12\n",
2241                           programVersion);
2242
2243                 } else if (ics_type == ICS_CHESSNET) {
2244                   sprintf(str, "/style 12\n");
2245                 } else {
2246                   strcpy(str, "alias $ @\n$set interface ");
2247                   strcat(str, programVersion);
2248                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2249 #ifdef WIN32
2250                   strcat(str, "$iset nohighlight 1\n");
2251 #endif
2252                   strcat(str, "$iset lock 1\n$style 12\n");
2253                 }
2254                 SendToICS(str);
2255                 intfSet = TRUE;
2256             }
2257
2258             if (started == STARTED_COMMENT) {
2259                 /* Accumulate characters in comment */
2260                 parse[parse_pos++] = buf[i];
2261                 if (buf[i] == '\n') {
2262                     parse[parse_pos] = NULLCHAR;
2263                     if(!suppressKibitz) // [HGM] kibitz
2264                         AppendComment(forwardMostMove, StripHighlight(parse));
2265                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2266                         int nrDigit = 0, nrAlph = 0, i;
2267                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2268                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2269                         parse[parse_pos] = NULLCHAR;
2270                         // try to be smart: if it does not look like search info, it should go to
2271                         // ICS interaction window after all, not to engine-output window.
2272                         for(i=0; i<parse_pos; i++) { // count letters and digits
2273                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2274                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2275                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2276                         }
2277                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2278                             int depth=0; float score;
2279                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2280                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2281                                 pvInfoList[forwardMostMove-1].depth = depth;
2282                                 pvInfoList[forwardMostMove-1].score = 100*score;
2283                             }
2284                             OutputKibitz(suppressKibitz, parse);
2285                         } else {
2286                             char tmp[MSG_SIZ];
2287                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2288                             SendToPlayer(tmp, strlen(tmp));
2289                         }
2290                     }
2291                     started = STARTED_NONE;
2292                 } else {
2293                     /* Don't match patterns against characters in chatter */
2294                     i++;
2295                     continue;
2296                 }
2297             }
2298             if (started == STARTED_CHATTER) {
2299                 if (buf[i] != '\n') {
2300                     /* Don't match patterns against characters in chatter */
2301                     i++;
2302                     continue;
2303                 }
2304                 started = STARTED_NONE;
2305             }
2306
2307             /* Kludge to deal with rcmd protocol */
2308             if (firstTime && looking_at(buf, &i, "\001*")) {
2309                 DisplayFatalError(&buf[1], 0, 1);
2310                 continue;
2311             } else {
2312                 firstTime = FALSE;
2313             }
2314
2315             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2316                 ics_type = ICS_ICC;
2317                 ics_prefix = "/";
2318                 if (appData.debugMode)
2319                   fprintf(debugFP, "ics_type %d\n", ics_type);
2320                 continue;
2321             }
2322             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2323                 ics_type = ICS_FICS;
2324                 ics_prefix = "$";
2325                 if (appData.debugMode)
2326                   fprintf(debugFP, "ics_type %d\n", ics_type);
2327                 continue;
2328             }
2329             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2330                 ics_type = ICS_CHESSNET;
2331                 ics_prefix = "/";
2332                 if (appData.debugMode)
2333                   fprintf(debugFP, "ics_type %d\n", ics_type);
2334                 continue;
2335             }
2336
2337             if (!loggedOn &&
2338                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2339                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2340                  looking_at(buf, &i, "will be \"*\""))) {
2341               strcpy(ics_handle, star_match[0]);
2342               continue;
2343             }
2344
2345             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2346               char buf[MSG_SIZ];
2347               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2348               DisplayIcsInteractionTitle(buf);
2349               have_set_title = TRUE;
2350             }
2351
2352             /* skip finger notes */
2353             if (started == STARTED_NONE &&
2354                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2355                  (buf[i] == '1' && buf[i+1] == '0')) &&
2356                 buf[i+2] == ':' && buf[i+3] == ' ') {
2357               started = STARTED_CHATTER;
2358               i += 3;
2359               continue;
2360             }
2361
2362             /* skip formula vars */
2363             if (started == STARTED_NONE &&
2364                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2365               started = STARTED_CHATTER;
2366               i += 3;
2367               continue;
2368             }
2369
2370             oldi = i;
2371             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2372             if (appData.autoKibitz && started == STARTED_NONE &&
2373                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2374                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2375                 if(looking_at(buf, &i, "* kibitzes: ") &&
2376                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2377                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2378                         suppressKibitz = TRUE;
2379                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2380                                 && (gameMode == IcsPlayingWhite)) ||
2381                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2382                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2383                             started = STARTED_CHATTER; // own kibitz we simply discard
2384                         else {
2385                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2386                             parse_pos = 0; parse[0] = NULLCHAR;
2387                             savingComment = TRUE;
2388                             suppressKibitz = gameMode != IcsObserving ? 2 :
2389                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2390                         }
2391                         continue;
2392                 } else
2393                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2394                     started = STARTED_CHATTER;
2395                     suppressKibitz = TRUE;
2396                 }
2397             } // [HGM] kibitz: end of patch
2398
2399             if (appData.zippyTalk || appData.zippyPlay) {
2400                 /* [DM] Backup address for color zippy lines */
2401                 backup = i;
2402 #if ZIPPY
2403        #ifdef WIN32
2404                if (loggedOn == TRUE)
2405                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2406                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2407        #else
2408                 if (ZippyControl(buf, &i) ||
2409                     ZippyConverse(buf, &i) ||
2410                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2411                       loggedOn = TRUE;
2412                       if (!appData.colorize) continue;
2413                 }
2414        #endif
2415 #endif
2416             } // [DM] 'else { ' deleted
2417                 if (/* Don't color "message" or "messages" output */
2418                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2419                     looking_at(buf, &i, "*. * at *:*: ") ||
2420                     looking_at(buf, &i, "--* (*:*): ") ||
2421                     /* Regular tells and says */
2422                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2423                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2424                     looking_at(buf, &i, "* says: ") ||
2425                     /* Message notifications (same color as tells) */
2426                     looking_at(buf, &i, "* has left a message ") ||
2427                     looking_at(buf, &i, "* just sent you a message:\n") ||
2428                     /* Whispers and kibitzes */
2429                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2430                     looking_at(buf, &i, "* kibitzes: ") ||
2431                     /* Channel tells */
2432                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2433
2434                   if (tkind == 1 && strchr(star_match[0], ':')) {
2435                       /* Avoid "tells you:" spoofs in channels */
2436                      tkind = 3;
2437                   }
2438                   if (star_match[0][0] == NULLCHAR ||
2439                       strchr(star_match[0], ' ') ||
2440                       (tkind == 3 && strchr(star_match[1], ' '))) {
2441                     /* Reject bogus matches */
2442                     i = oldi;
2443                   } else {
2444                     if (appData.colorize) {
2445                       if (oldi > next_out) {
2446                         SendToPlayer(&buf[next_out], oldi - next_out);
2447                         next_out = oldi;
2448                       }
2449                       switch (tkind) {
2450                       case 1:
2451                         Colorize(ColorTell, FALSE);
2452                         curColor = ColorTell;
2453                         break;
2454                       case 2:
2455                         Colorize(ColorKibitz, FALSE);
2456                         curColor = ColorKibitz;
2457                         break;
2458                       case 3:
2459                         p = strrchr(star_match[1], '(');
2460                         if (p == NULL) {
2461                           p = star_match[1];
2462                         } else {
2463                           p++;
2464                         }
2465                         if (atoi(p) == 1) {
2466                           Colorize(ColorChannel1, FALSE);
2467                           curColor = ColorChannel1;
2468                         } else {
2469                           Colorize(ColorChannel, FALSE);
2470                           curColor = ColorChannel;
2471                         }
2472                         break;
2473                       case 5:
2474                         curColor = ColorNormal;
2475                         break;
2476                       }
2477                     }
2478                     if (started == STARTED_NONE && appData.autoComment &&
2479                         (gameMode == IcsObserving ||
2480                          gameMode == IcsPlayingWhite ||
2481                          gameMode == IcsPlayingBlack)) {
2482                       parse_pos = i - oldi;
2483                       memcpy(parse, &buf[oldi], parse_pos);
2484                       parse[parse_pos] = NULLCHAR;
2485                       started = STARTED_COMMENT;
2486                       savingComment = TRUE;
2487                     } else {
2488                       started = STARTED_CHATTER;
2489                       savingComment = FALSE;
2490                     }
2491                     loggedOn = TRUE;
2492                     continue;
2493                   }
2494                 }
2495
2496                 if (looking_at(buf, &i, "* s-shouts: ") ||
2497                     looking_at(buf, &i, "* c-shouts: ")) {
2498                     if (appData.colorize) {
2499                         if (oldi > next_out) {
2500                             SendToPlayer(&buf[next_out], oldi - next_out);
2501                             next_out = oldi;
2502                         }
2503                         Colorize(ColorSShout, FALSE);
2504                         curColor = ColorSShout;
2505                     }
2506                     loggedOn = TRUE;
2507                     started = STARTED_CHATTER;
2508                     continue;
2509                 }
2510
2511                 if (looking_at(buf, &i, "--->")) {
2512                     loggedOn = TRUE;
2513                     continue;
2514                 }
2515
2516                 if (looking_at(buf, &i, "* shouts: ") ||
2517                     looking_at(buf, &i, "--> ")) {
2518                     if (appData.colorize) {
2519                         if (oldi > next_out) {
2520                             SendToPlayer(&buf[next_out], oldi - next_out);
2521                             next_out = oldi;
2522                         }
2523                         Colorize(ColorShout, FALSE);
2524                         curColor = ColorShout;
2525                     }
2526                     loggedOn = TRUE;
2527                     started = STARTED_CHATTER;
2528                     continue;
2529                 }
2530
2531                 if (looking_at( buf, &i, "Challenge:")) {
2532                     if (appData.colorize) {
2533                         if (oldi > next_out) {
2534                             SendToPlayer(&buf[next_out], oldi - next_out);
2535                             next_out = oldi;
2536                         }
2537                         Colorize(ColorChallenge, FALSE);
2538                         curColor = ColorChallenge;
2539                     }
2540                     loggedOn = TRUE;
2541                     continue;
2542                 }
2543
2544                 if (looking_at(buf, &i, "* offers you") ||
2545                     looking_at(buf, &i, "* offers to be") ||
2546                     looking_at(buf, &i, "* would like to") ||
2547                     looking_at(buf, &i, "* requests to") ||
2548                     looking_at(buf, &i, "Your opponent offers") ||
2549                     looking_at(buf, &i, "Your opponent requests")) {
2550
2551                     if (appData.colorize) {
2552                         if (oldi > next_out) {
2553                             SendToPlayer(&buf[next_out], oldi - next_out);
2554                             next_out = oldi;
2555                         }
2556                         Colorize(ColorRequest, FALSE);
2557                         curColor = ColorRequest;
2558                     }
2559                     continue;
2560                 }
2561
2562                 if (looking_at(buf, &i, "* (*) seeking")) {
2563                     if (appData.colorize) {
2564                         if (oldi > next_out) {
2565                             SendToPlayer(&buf[next_out], oldi - next_out);
2566                             next_out = oldi;
2567                         }
2568                         Colorize(ColorSeek, FALSE);
2569                         curColor = ColorSeek;
2570                     }
2571                     continue;
2572             }
2573
2574             if (looking_at(buf, &i, "\\   ")) {
2575                 if (prevColor != ColorNormal) {
2576                     if (oldi > next_out) {
2577                         SendToPlayer(&buf[next_out], oldi - next_out);
2578                         next_out = oldi;
2579                     }
2580                     Colorize(prevColor, TRUE);
2581                     curColor = prevColor;
2582                 }
2583                 if (savingComment) {
2584                     parse_pos = i - oldi;
2585                     memcpy(parse, &buf[oldi], parse_pos);
2586                     parse[parse_pos] = NULLCHAR;
2587                     started = STARTED_COMMENT;
2588                 } else {
2589                     started = STARTED_CHATTER;
2590                 }
2591                 continue;
2592             }
2593
2594             if (looking_at(buf, &i, "Black Strength :") ||
2595                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2596                 looking_at(buf, &i, "<10>") ||
2597                 looking_at(buf, &i, "#@#")) {
2598                 /* Wrong board style */
2599                 loggedOn = TRUE;
2600                 SendToICS(ics_prefix);
2601                 SendToICS("set style 12\n");
2602                 SendToICS(ics_prefix);
2603                 SendToICS("refresh\n");
2604                 continue;
2605             }
2606
2607             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2608                 ICSInitScript();
2609                 have_sent_ICS_logon = 1;
2610                 continue;
2611             }
2612
2613             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2614                 (looking_at(buf, &i, "\n<12> ") ||
2615                  looking_at(buf, &i, "<12> "))) {
2616                 loggedOn = TRUE;
2617                 if (oldi > next_out) {
2618                     SendToPlayer(&buf[next_out], oldi - next_out);
2619                 }
2620                 next_out = i;
2621                 started = STARTED_BOARD;
2622                 parse_pos = 0;
2623                 continue;
2624             }
2625
2626             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2627                 looking_at(buf, &i, "<b1> ")) {
2628                 if (oldi > next_out) {
2629                     SendToPlayer(&buf[next_out], oldi - next_out);
2630                 }
2631                 next_out = i;
2632                 started = STARTED_HOLDINGS;
2633                 parse_pos = 0;
2634                 continue;
2635             }
2636
2637             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2638                 loggedOn = TRUE;
2639                 /* Header for a move list -- first line */
2640
2641                 switch (ics_getting_history) {
2642                   case H_FALSE:
2643                     switch (gameMode) {
2644                       case IcsIdle:
2645                       case BeginningOfGame:
2646                         /* User typed "moves" or "oldmoves" while we
2647                            were idle.  Pretend we asked for these
2648                            moves and soak them up so user can step
2649                            through them and/or save them.
2650                            */
2651                         Reset(FALSE, TRUE);
2652                         gameMode = IcsObserving;
2653                         ModeHighlight();
2654                         ics_gamenum = -1;
2655                         ics_getting_history = H_GOT_UNREQ_HEADER;
2656                         break;
2657                       case EditGame: /*?*/
2658                       case EditPosition: /*?*/
2659                         /* Should above feature work in these modes too? */
2660                         /* For now it doesn't */
2661                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2662                         break;
2663                       default:
2664                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2665                         break;
2666                     }
2667                     break;
2668                   case H_REQUESTED:
2669                     /* Is this the right one? */
2670                     if (gameInfo.white && gameInfo.black &&
2671                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2672                         strcmp(gameInfo.black, star_match[2]) == 0) {
2673                         /* All is well */
2674                         ics_getting_history = H_GOT_REQ_HEADER;
2675                     }
2676                     break;
2677                   case H_GOT_REQ_HEADER:
2678                   case H_GOT_UNREQ_HEADER:
2679                   case H_GOT_UNWANTED_HEADER:
2680                   case H_GETTING_MOVES:
2681                     /* Should not happen */
2682                     DisplayError(_("Error gathering move list: two headers"), 0);
2683                     ics_getting_history = H_FALSE;
2684                     break;
2685                 }
2686
2687                 /* Save player ratings into gameInfo if needed */
2688                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2689                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2690                     (gameInfo.whiteRating == -1 ||
2691                      gameInfo.blackRating == -1)) {
2692
2693                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2694                     gameInfo.blackRating = string_to_rating(star_match[3]);
2695                     if (appData.debugMode)
2696                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2697                               gameInfo.whiteRating, gameInfo.blackRating);
2698                 }
2699                 continue;
2700             }
2701
2702             if (looking_at(buf, &i,
2703               "* * match, initial time: * minute*, increment: * second")) {
2704                 /* Header for a move list -- second line */
2705                 /* Initial board will follow if this is a wild game */
2706                 if (gameInfo.event != NULL) free(gameInfo.event);
2707                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2708                 gameInfo.event = StrSave(str);
2709                 /* [HGM] we switched variant. Translate boards if needed. */
2710                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2711                 continue;
2712             }
2713
2714             if (looking_at(buf, &i, "Move  ")) {
2715                 /* Beginning of a move list */
2716                 switch (ics_getting_history) {
2717                   case H_FALSE:
2718                     /* Normally should not happen */
2719                     /* Maybe user hit reset while we were parsing */
2720                     break;
2721                   case H_REQUESTED:
2722                     /* Happens if we are ignoring a move list that is not
2723                      * the one we just requested.  Common if the user
2724                      * tries to observe two games without turning off
2725                      * getMoveList */
2726                     break;
2727                   case H_GETTING_MOVES:
2728                     /* Should not happen */
2729                     DisplayError(_("Error gathering move list: nested"), 0);
2730                     ics_getting_history = H_FALSE;
2731                     break;
2732                   case H_GOT_REQ_HEADER:
2733                     ics_getting_history = H_GETTING_MOVES;
2734                     started = STARTED_MOVES;
2735                     parse_pos = 0;
2736                     if (oldi > next_out) {
2737                         SendToPlayer(&buf[next_out], oldi - next_out);
2738                     }
2739                     break;
2740                   case H_GOT_UNREQ_HEADER:
2741                     ics_getting_history = H_GETTING_MOVES;
2742                     started = STARTED_MOVES_NOHIDE;
2743                     parse_pos = 0;
2744                     break;
2745                   case H_GOT_UNWANTED_HEADER:
2746                     ics_getting_history = H_FALSE;
2747                     break;
2748                 }
2749                 continue;
2750             }
2751
2752             if (looking_at(buf, &i, "% ") ||
2753                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2754                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2755                 savingComment = FALSE;
2756                 switch (started) {
2757                   case STARTED_MOVES:
2758                   case STARTED_MOVES_NOHIDE:
2759                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2760                     parse[parse_pos + i - oldi] = NULLCHAR;
2761                     ParseGameHistory(parse);
2762 #if ZIPPY
2763                     if (appData.zippyPlay && first.initDone) {
2764                         FeedMovesToProgram(&first, forwardMostMove);
2765                         if (gameMode == IcsPlayingWhite) {
2766                             if (WhiteOnMove(forwardMostMove)) {
2767                                 if (first.sendTime) {
2768                                   if (first.useColors) {
2769                                     SendToProgram("black\n", &first);
2770                                   }
2771                                   SendTimeRemaining(&first, TRUE);
2772                                 }
2773 #if 0
2774                                 if (first.useColors) {
2775                                   SendToProgram("white\ngo\n", &first);
2776                                 } else {
2777                                   SendToProgram("go\n", &first);
2778                                 }
2779 #else
2780                                 if (first.useColors) {
2781                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2782                                 }
2783                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2784 #endif
2785                                 first.maybeThinking = TRUE;
2786                             } else {
2787                                 if (first.usePlayother) {
2788                                   if (first.sendTime) {
2789                                     SendTimeRemaining(&first, TRUE);
2790                                   }
2791                                   SendToProgram("playother\n", &first);
2792                                   firstMove = FALSE;
2793                                 } else {
2794                                   firstMove = TRUE;
2795                                 }
2796                             }
2797                         } else if (gameMode == IcsPlayingBlack) {
2798                             if (!WhiteOnMove(forwardMostMove)) {
2799                                 if (first.sendTime) {
2800                                   if (first.useColors) {
2801                                     SendToProgram("white\n", &first);
2802                                   }
2803                                   SendTimeRemaining(&first, FALSE);
2804                                 }
2805 #if 0
2806                                 if (first.useColors) {
2807                                   SendToProgram("black\ngo\n", &first);
2808                                 } else {
2809                                   SendToProgram("go\n", &first);
2810                                 }
2811 #else
2812                                 if (first.useColors) {
2813                                   SendToProgram("black\n", &first);
2814                                 }
2815                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2816 #endif
2817                                 first.maybeThinking = TRUE;
2818                             } else {
2819                                 if (first.usePlayother) {
2820                                   if (first.sendTime) {
2821                                     SendTimeRemaining(&first, FALSE);
2822                                   }
2823                                   SendToProgram("playother\n", &first);
2824                                   firstMove = FALSE;
2825                                 } else {
2826                                   firstMove = TRUE;
2827                                 }
2828                             }
2829                         }
2830                     }
2831 #endif
2832                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2833                         /* Moves came from oldmoves or moves command
2834                            while we weren't doing anything else.
2835                            */
2836                         currentMove = forwardMostMove;
2837                         ClearHighlights();/*!!could figure this out*/
2838                         flipView = appData.flipView;
2839                         DrawPosition(FALSE, boards[currentMove]);
2840                         DisplayBothClocks();
2841                         sprintf(str, "%s vs. %s",
2842                                 gameInfo.white, gameInfo.black);
2843                         DisplayTitle(str);
2844                         gameMode = IcsIdle;
2845                     } else {
2846                         /* Moves were history of an active game */
2847                         if (gameInfo.resultDetails != NULL) {
2848                             free(gameInfo.resultDetails);
2849                             gameInfo.resultDetails = NULL;
2850                         }
2851                     }
2852                     HistorySet(parseList, backwardMostMove,
2853                                forwardMostMove, currentMove-1);
2854                     DisplayMove(currentMove - 1);
2855                     if (started == STARTED_MOVES) next_out = i;
2856                     started = STARTED_NONE;
2857                     ics_getting_history = H_FALSE;
2858                     break;
2859
2860                   case STARTED_OBSERVE:
2861                     started = STARTED_NONE;
2862                     SendToICS(ics_prefix);
2863                     SendToICS("refresh\n");
2864                     break;
2865
2866                   default:
2867                     break;
2868                 }
2869                 if(bookHit) { // [HGM] book: simulate book reply
2870                     static char bookMove[MSG_SIZ]; // a bit generous?
2871
2872                     programStats.nodes = programStats.depth = programStats.time =
2873                     programStats.score = programStats.got_only_move = 0;
2874                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2875
2876                     strcpy(bookMove, "move ");
2877                     strcat(bookMove, bookHit);
2878                     HandleMachineMove(bookMove, &first);
2879                 }
2880                 continue;
2881             }
2882
2883             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2884                  started == STARTED_HOLDINGS ||
2885                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2886                 /* Accumulate characters in move list or board */
2887                 parse[parse_pos++] = buf[i];
2888             }
2889
2890             /* Start of game messages.  Mostly we detect start of game
2891                when the first board image arrives.  On some versions
2892                of the ICS, though, we need to do a "refresh" after starting
2893                to observe in order to get the current board right away. */
2894             if (looking_at(buf, &i, "Adding game * to observation list")) {
2895                 started = STARTED_OBSERVE;
2896                 continue;
2897             }
2898
2899             /* Handle auto-observe */
2900             if (appData.autoObserve &&
2901                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2902                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2903                 char *player;
2904                 /* Choose the player that was highlighted, if any. */
2905                 if (star_match[0][0] == '\033' ||
2906                     star_match[1][0] != '\033') {
2907                     player = star_match[0];
2908                 } else {
2909                     player = star_match[2];
2910                 }
2911                 sprintf(str, "%sobserve %s\n",
2912                         ics_prefix, StripHighlightAndTitle(player));
2913                 SendToICS(str);
2914
2915                 /* Save ratings from notify string */
2916                 strcpy(player1Name, star_match[0]);
2917                 player1Rating = string_to_rating(star_match[1]);
2918                 strcpy(player2Name, star_match[2]);
2919                 player2Rating = string_to_rating(star_match[3]);
2920
2921                 if (appData.debugMode)
2922                   fprintf(debugFP,
2923                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2924                           player1Name, player1Rating,
2925                           player2Name, player2Rating);
2926
2927                 continue;
2928             }
2929
2930             /* Deal with automatic examine mode after a game,
2931                and with IcsObserving -> IcsExamining transition */
2932             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2933                 looking_at(buf, &i, "has made you an examiner of game *")) {
2934
2935                 int gamenum = atoi(star_match[0]);
2936                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2937                     gamenum == ics_gamenum) {
2938                     /* We were already playing or observing this game;
2939                        no need to refetch history */
2940                     gameMode = IcsExamining;
2941                     if (pausing) {
2942                         pauseExamForwardMostMove = forwardMostMove;
2943                     } else if (currentMove < forwardMostMove) {
2944                         ForwardInner(forwardMostMove);
2945                     }
2946                 } else {
2947                     /* I don't think this case really can happen */
2948                     SendToICS(ics_prefix);
2949                     SendToICS("refresh\n");
2950                 }
2951                 continue;
2952             }
2953
2954             /* Error messages */
2955 //          if (ics_user_moved) {
2956             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2957                 if (looking_at(buf, &i, "Illegal move") ||
2958                     looking_at(buf, &i, "Not a legal move") ||
2959                     looking_at(buf, &i, "Your king is in check") ||
2960                     looking_at(buf, &i, "It isn't your turn") ||
2961                     looking_at(buf, &i, "It is not your move")) {
2962                     /* Illegal move */
2963                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2964                         currentMove = --forwardMostMove;
2965                         DisplayMove(currentMove - 1); /* before DMError */
2966                         DrawPosition(FALSE, boards[currentMove]);
2967                         SwitchClocks();
2968                         DisplayBothClocks();
2969                     }
2970                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2971                     ics_user_moved = 0;
2972                     continue;
2973                 }
2974             }
2975
2976             if (looking_at(buf, &i, "still have time") ||
2977                 looking_at(buf, &i, "not out of time") ||
2978                 looking_at(buf, &i, "either player is out of time") ||
2979                 looking_at(buf, &i, "has timeseal; checking")) {
2980                 /* We must have called his flag a little too soon */
2981                 whiteFlag = blackFlag = FALSE;
2982                 continue;
2983             }
2984
2985             if (looking_at(buf, &i, "added * seconds to") ||
2986                 looking_at(buf, &i, "seconds were added to")) {
2987                 /* Update the clocks */
2988                 SendToICS(ics_prefix);
2989                 SendToICS("refresh\n");
2990                 continue;
2991             }
2992
2993             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2994                 ics_clock_paused = TRUE;
2995                 StopClocks();
2996                 continue;
2997             }
2998
2999             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3000                 ics_clock_paused = FALSE;
3001                 StartClocks();
3002                 continue;
3003             }
3004
3005             /* Grab player ratings from the Creating: message.
3006                Note we have to check for the special case when
3007                the ICS inserts things like [white] or [black]. */
3008             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3009                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3010                 /* star_matches:
3011                    0    player 1 name (not necessarily white)
3012                    1    player 1 rating
3013                    2    empty, white, or black (IGNORED)
3014                    3    player 2 name (not necessarily black)
3015                    4    player 2 rating
3016
3017                    The names/ratings are sorted out when the game
3018                    actually starts (below).
3019                 */
3020                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3021                 player1Rating = string_to_rating(star_match[1]);
3022                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3023                 player2Rating = string_to_rating(star_match[4]);
3024
3025                 if (appData.debugMode)
3026                   fprintf(debugFP,
3027                           "Ratings from 'Creating:' %s %d, %s %d\n",
3028                           player1Name, player1Rating,
3029                           player2Name, player2Rating);
3030
3031                 continue;
3032             }
3033
3034             /* Improved generic start/end-of-game messages */
3035             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3036                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3037                 /* If tkind == 0: */
3038                 /* star_match[0] is the game number */
3039                 /*           [1] is the white player's name */
3040                 /*           [2] is the black player's name */
3041                 /* For end-of-game: */
3042                 /*           [3] is the reason for the game end */
3043                 /*           [4] is a PGN end game-token, preceded by " " */
3044                 /* For start-of-game: */
3045                 /*           [3] begins with "Creating" or "Continuing" */
3046                 /*           [4] is " *" or empty (don't care). */
3047                 int gamenum = atoi(star_match[0]);
3048                 char *whitename, *blackname, *why, *endtoken;
3049                 ChessMove endtype = (ChessMove) 0;
3050
3051                 if (tkind == 0) {
3052                   whitename = star_match[1];
3053                   blackname = star_match[2];
3054                   why = star_match[3];
3055                   endtoken = star_match[4];
3056                 } else {
3057                   whitename = star_match[1];
3058                   blackname = star_match[3];
3059                   why = star_match[5];
3060                   endtoken = star_match[6];
3061                 }
3062
3063                 /* Game start messages */
3064                 if (strncmp(why, "Creating ", 9) == 0 ||
3065                     strncmp(why, "Continuing ", 11) == 0) {
3066                     gs_gamenum = gamenum;
3067                     strcpy(gs_kind, strchr(why, ' ') + 1);
3068 #if ZIPPY
3069                     if (appData.zippyPlay) {
3070                         ZippyGameStart(whitename, blackname);
3071                     }
3072 #endif /*ZIPPY*/
3073                     continue;
3074                 }
3075
3076                 /* Game end messages */
3077                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3078                     ics_gamenum != gamenum) {
3079                     continue;
3080                 }
3081                 while (endtoken[0] == ' ') endtoken++;
3082                 switch (endtoken[0]) {
3083                   case '*':
3084                   default:
3085                     endtype = GameUnfinished;
3086                     break;
3087                   case '0':
3088                     endtype = BlackWins;
3089                     break;
3090                   case '1':
3091                     if (endtoken[1] == '/')
3092                       endtype = GameIsDrawn;
3093                     else
3094                       endtype = WhiteWins;
3095                     break;
3096                 }
3097                 GameEnds(endtype, why, GE_ICS);
3098 #if ZIPPY
3099                 if (appData.zippyPlay && first.initDone) {
3100                     ZippyGameEnd(endtype, why);
3101                     if (first.pr == NULL) {
3102                       /* Start the next process early so that we'll
3103                          be ready for the next challenge */
3104                       StartChessProgram(&first);
3105                     }
3106                     /* Send "new" early, in case this command takes
3107                        a long time to finish, so that we'll be ready
3108                        for the next challenge. */
3109                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3110                     Reset(TRUE, TRUE);
3111                 }
3112 #endif /*ZIPPY*/
3113                 continue;
3114             }
3115
3116             if (looking_at(buf, &i, "Removing game * from observation") ||
3117                 looking_at(buf, &i, "no longer observing game *") ||
3118                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3119                 if (gameMode == IcsObserving &&
3120                     atoi(star_match[0]) == ics_gamenum)
3121                   {
3122                       /* icsEngineAnalyze */
3123                       if (appData.icsEngineAnalyze) {
3124                             ExitAnalyzeMode();
3125                             ModeHighlight();
3126                       }
3127                       StopClocks();
3128                       gameMode = IcsIdle;
3129                       ics_gamenum = -1;
3130                       ics_user_moved = FALSE;
3131                   }
3132                 continue;
3133             }
3134
3135             if (looking_at(buf, &i, "no longer examining game *")) {
3136                 if (gameMode == IcsExamining &&
3137                     atoi(star_match[0]) == ics_gamenum)
3138                   {
3139                       gameMode = IcsIdle;
3140                       ics_gamenum = -1;
3141                       ics_user_moved = FALSE;
3142                   }
3143                 continue;
3144             }
3145
3146             /* Advance leftover_start past any newlines we find,
3147                so only partial lines can get reparsed */
3148             if (looking_at(buf, &i, "\n")) {
3149                 prevColor = curColor;
3150                 if (curColor != ColorNormal) {
3151                     if (oldi > next_out) {
3152                         SendToPlayer(&buf[next_out], oldi - next_out);
3153                         next_out = oldi;
3154                     }
3155                     Colorize(ColorNormal, FALSE);
3156                     curColor = ColorNormal;
3157                 }
3158                 if (started == STARTED_BOARD) {
3159                     started = STARTED_NONE;
3160                     parse[parse_pos] = NULLCHAR;
3161                     ParseBoard12(parse);
3162                     ics_user_moved = 0;
3163
3164                     /* Send premove here */
3165                     if (appData.premove) {
3166                       char str[MSG_SIZ];
3167                       if (currentMove == 0 &&
3168                           gameMode == IcsPlayingWhite &&
3169                           appData.premoveWhite) {
3170                         sprintf(str, "%s%s\n", ics_prefix,
3171                                 appData.premoveWhiteText);
3172                         if (appData.debugMode)
3173                           fprintf(debugFP, "Sending premove:\n");
3174                         SendToICS(str);
3175                       } else if (currentMove == 1 &&
3176                                  gameMode == IcsPlayingBlack &&
3177                                  appData.premoveBlack) {
3178                         sprintf(str, "%s%s\n", ics_prefix,
3179                                 appData.premoveBlackText);
3180                         if (appData.debugMode)
3181                           fprintf(debugFP, "Sending premove:\n");
3182                         SendToICS(str);
3183                       } else if (gotPremove) {
3184                         gotPremove = 0;
3185                         ClearPremoveHighlights();
3186                         if (appData.debugMode)
3187                           fprintf(debugFP, "Sending premove:\n");
3188                           UserMoveEvent(premoveFromX, premoveFromY,
3189                                         premoveToX, premoveToY,
3190                                         premovePromoChar);
3191                       }
3192                     }
3193
3194                     /* Usually suppress following prompt */
3195                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3196                         if (looking_at(buf, &i, "*% ")) {
3197                             savingComment = FALSE;
3198                         }
3199                     }
3200                     next_out = i;
3201                 } else if (started == STARTED_HOLDINGS) {
3202                     int gamenum;
3203                     char new_piece[MSG_SIZ];
3204                     started = STARTED_NONE;
3205                     parse[parse_pos] = NULLCHAR;
3206                     if (appData.debugMode)
3207                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3208                                                         parse, currentMove);
3209                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3210                         gamenum == ics_gamenum) {
3211                         if (gameInfo.variant == VariantNormal) {
3212                           /* [HGM] We seem to switch variant during a game!
3213                            * Presumably no holdings were displayed, so we have
3214                            * to move the position two files to the right to
3215                            * create room for them!
3216                            */
3217                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3218                           /* Get a move list just to see the header, which
3219                              will tell us whether this is really bug or zh */
3220                           if (ics_getting_history == H_FALSE) {
3221                             ics_getting_history = H_REQUESTED;
3222                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3223                             SendToICS(str);
3224                           }
3225                         }
3226                         new_piece[0] = NULLCHAR;
3227                         sscanf(parse, "game %d white [%s black [%s <- %s",
3228                                &gamenum, white_holding, black_holding,
3229                                new_piece);
3230                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3231                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3232                         /* [HGM] copy holdings to board holdings area */
3233                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3234                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3235 #if ZIPPY
3236                         if (appData.zippyPlay && first.initDone) {
3237                             ZippyHoldings(white_holding, black_holding,
3238                                           new_piece);
3239                         }
3240 #endif /*ZIPPY*/
3241                         if (tinyLayout || smallLayout) {
3242                             char wh[16], bh[16];
3243                             PackHolding(wh, white_holding);
3244                             PackHolding(bh, black_holding);
3245                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3246                                     gameInfo.white, gameInfo.black);
3247                         } else {
3248                             sprintf(str, "%s [%s] vs. %s [%s]",
3249                                     gameInfo.white, white_holding,
3250                                     gameInfo.black, black_holding);
3251                         }
3252
3253                         DrawPosition(FALSE, boards[currentMove]);
3254                         DisplayTitle(str);
3255                     }
3256                     /* Suppress following prompt */
3257                     if (looking_at(buf, &i, "*% ")) {
3258                         savingComment = FALSE;
3259                     }
3260                     next_out = i;
3261                 }
3262                 continue;
3263             }
3264
3265             i++;                /* skip unparsed character and loop back */
3266         }
3267
3268         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3269             started != STARTED_HOLDINGS && i > next_out) {
3270             SendToPlayer(&buf[next_out], i - next_out);
3271             next_out = i;
3272         }
3273         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3274
3275         leftover_len = buf_len - leftover_start;
3276         /* if buffer ends with something we couldn't parse,
3277            reparse it after appending the next read */
3278
3279     } else if (count == 0) {
3280         RemoveInputSource(isr);
3281         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3282     } else {
3283         DisplayFatalError(_("Error reading from ICS"), error, 1);
3284     }
3285 }
3286
3287
3288 /* Board style 12 looks like this:
3289
3290    <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
3291
3292  * The "<12> " is stripped before it gets to this routine.  The two
3293  * trailing 0's (flip state and clock ticking) are later addition, and
3294  * some chess servers may not have them, or may have only the first.
3295  * Additional trailing fields may be added in the future.
3296  */
3297
3298 #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"
3299
3300 #define RELATION_OBSERVING_PLAYED    0
3301 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3302 #define RELATION_PLAYING_MYMOVE      1
3303 #define RELATION_PLAYING_NOTMYMOVE  -1
3304 #define RELATION_EXAMINING           2
3305 #define RELATION_ISOLATED_BOARD     -3
3306 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3307
3308 void
3309 ParseBoard12(string)
3310      char *string;
3311 {
3312     GameMode newGameMode;
3313     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3314     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3315     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3316     char to_play, board_chars[200];
3317     char move_str[500], str[500], elapsed_time[500];
3318     char black[32], white[32];
3319     Board board;
3320     int prevMove = currentMove;
3321     int ticking = 2;
3322     ChessMove moveType;
3323     int fromX, fromY, toX, toY;
3324     char promoChar;
3325     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3326     char *bookHit = NULL; // [HGM] book
3327
3328     fromX = fromY = toX = toY = -1;
3329
3330     newGame = FALSE;
3331
3332     if (appData.debugMode)
3333       fprintf(debugFP, _("Parsing board: %s\n"), string);
3334
3335     move_str[0] = NULLCHAR;
3336     elapsed_time[0] = NULLCHAR;
3337     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3338         int  i = 0, j;
3339         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3340             if(string[i] == ' ') { ranks++; files = 0; }
3341             else files++;
3342             i++;
3343         }
3344         for(j = 0; j <i; j++) board_chars[j] = string[j];
3345         board_chars[i] = '\0';
3346         string += i + 1;
3347     }
3348     n = sscanf(string, PATTERN, &to_play, &double_push,
3349                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3350                &gamenum, white, black, &relation, &basetime, &increment,
3351                &white_stren, &black_stren, &white_time, &black_time,
3352                &moveNum, str, elapsed_time, move_str, &ics_flip,
3353                &ticking);
3354
3355     if (n < 21) {
3356         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3357         DisplayError(str, 0);
3358         return;
3359     }
3360
3361     /* Convert the move number to internal form */
3362     moveNum = (moveNum - 1) * 2;
3363     if (to_play == 'B') moveNum++;
3364     if (moveNum >= MAX_MOVES) {
3365       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3366                         0, 1);
3367       return;
3368     }
3369
3370     switch (relation) {
3371       case RELATION_OBSERVING_PLAYED:
3372       case RELATION_OBSERVING_STATIC:
3373         if (gamenum == -1) {
3374             /* Old ICC buglet */
3375             relation = RELATION_OBSERVING_STATIC;
3376         }
3377         newGameMode = IcsObserving;
3378         break;
3379       case RELATION_PLAYING_MYMOVE:
3380       case RELATION_PLAYING_NOTMYMOVE:
3381         newGameMode =
3382           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3383             IcsPlayingWhite : IcsPlayingBlack;
3384         break;
3385       case RELATION_EXAMINING:
3386         newGameMode = IcsExamining;
3387         break;
3388       case RELATION_ISOLATED_BOARD:
3389       default:
3390         /* Just display this board.  If user was doing something else,
3391            we will forget about it until the next board comes. */
3392         newGameMode = IcsIdle;
3393         break;
3394       case RELATION_STARTING_POSITION:
3395         newGameMode = gameMode;
3396         break;
3397     }
3398
3399     /* Modify behavior for initial board display on move listing
3400        of wild games.
3401        */
3402     switch (ics_getting_history) {
3403       case H_FALSE:
3404       case H_REQUESTED:
3405         break;
3406       case H_GOT_REQ_HEADER:
3407       case H_GOT_UNREQ_HEADER:
3408         /* This is the initial position of the current game */
3409         gamenum = ics_gamenum;
3410         moveNum = 0;            /* old ICS bug workaround */
3411         if (to_play == 'B') {
3412           startedFromSetupPosition = TRUE;
3413           blackPlaysFirst = TRUE;
3414           moveNum = 1;
3415           if (forwardMostMove == 0) forwardMostMove = 1;
3416           if (backwardMostMove == 0) backwardMostMove = 1;
3417           if (currentMove == 0) currentMove = 1;
3418         }
3419         newGameMode = gameMode;
3420         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3421         break;
3422       case H_GOT_UNWANTED_HEADER:
3423         /* This is an initial board that we don't want */
3424         return;
3425       case H_GETTING_MOVES:
3426         /* Should not happen */
3427         DisplayError(_("Error gathering move list: extra board"), 0);
3428         ics_getting_history = H_FALSE;
3429         return;
3430     }
3431
3432     /* Take action if this is the first board of a new game, or of a
3433        different game than is currently being displayed.  */
3434     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3435         relation == RELATION_ISOLATED_BOARD) {
3436
3437         /* Forget the old game and get the history (if any) of the new one */
3438         if (gameMode != BeginningOfGame) {
3439           Reset(FALSE, TRUE);
3440         }
3441         newGame = TRUE;
3442         if (appData.autoRaiseBoard) BoardToTop();
3443         prevMove = -3;
3444         if (gamenum == -1) {
3445             newGameMode = IcsIdle;
3446         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3447                    appData.getMoveList) {
3448             /* Need to get game history */
3449             ics_getting_history = H_REQUESTED;
3450             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3451             SendToICS(str);
3452         }
3453
3454         /* Initially flip the board to have black on the bottom if playing
3455            black or if the ICS flip flag is set, but let the user change
3456            it with the Flip View button. */
3457         flipView = appData.autoFlipView ?
3458           (newGameMode == IcsPlayingBlack) || ics_flip :
3459           appData.flipView;
3460
3461         /* Done with values from previous mode; copy in new ones */
3462         gameMode = newGameMode;
3463         ModeHighlight();
3464         ics_gamenum = gamenum;
3465         if (gamenum == gs_gamenum) {
3466             int klen = strlen(gs_kind);
3467             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3468             sprintf(str, "ICS %s", gs_kind);
3469             gameInfo.event = StrSave(str);
3470         } else {
3471             gameInfo.event = StrSave("ICS game");
3472         }
3473         gameInfo.site = StrSave(appData.icsHost);
3474         gameInfo.date = PGNDate();
3475         gameInfo.round = StrSave("-");
3476         gameInfo.white = StrSave(white);
3477         gameInfo.black = StrSave(black);
3478         timeControl = basetime * 60 * 1000;
3479         timeControl_2 = 0;
3480         timeIncrement = increment * 1000;
3481         movesPerSession = 0;
3482         gameInfo.timeControl = TimeControlTagValue();
3483         VariantSwitch(board, StringToVariant(gameInfo.event) );
3484   if (appData.debugMode) {
3485     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3486     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3487     setbuf(debugFP, NULL);
3488   }
3489
3490         gameInfo.outOfBook = NULL;
3491
3492         /* Do we have the ratings? */
3493         if (strcmp(player1Name, white) == 0 &&
3494             strcmp(player2Name, black) == 0) {
3495             if (appData.debugMode)
3496               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3497                       player1Rating, player2Rating);
3498             gameInfo.whiteRating = player1Rating;
3499             gameInfo.blackRating = player2Rating;
3500         } else if (strcmp(player2Name, white) == 0 &&
3501                    strcmp(player1Name, black) == 0) {
3502             if (appData.debugMode)
3503               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3504                       player2Rating, player1Rating);
3505             gameInfo.whiteRating = player2Rating;
3506             gameInfo.blackRating = player1Rating;
3507         }
3508         player1Name[0] = player2Name[0] = NULLCHAR;
3509
3510         /* Silence shouts if requested */
3511         if (appData.quietPlay &&
3512             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3513             SendToICS(ics_prefix);
3514             SendToICS("set shout 0\n");
3515         }
3516     }
3517
3518     /* Deal with midgame name changes */
3519     if (!newGame) {
3520         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3521             if (gameInfo.white) free(gameInfo.white);
3522             gameInfo.white = StrSave(white);
3523         }
3524         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3525             if (gameInfo.black) free(gameInfo.black);
3526             gameInfo.black = StrSave(black);
3527         }
3528     }
3529
3530     /* Throw away game result if anything actually changes in examine mode */
3531     if (gameMode == IcsExamining && !newGame) {
3532         gameInfo.result = GameUnfinished;
3533         if (gameInfo.resultDetails != NULL) {
3534             free(gameInfo.resultDetails);
3535             gameInfo.resultDetails = NULL;
3536         }
3537     }
3538
3539     /* In pausing && IcsExamining mode, we ignore boards coming
3540        in if they are in a different variation than we are. */
3541     if (pauseExamInvalid) return;
3542     if (pausing && gameMode == IcsExamining) {
3543         if (moveNum <= pauseExamForwardMostMove) {
3544             pauseExamInvalid = TRUE;
3545             forwardMostMove = pauseExamForwardMostMove;
3546             return;
3547         }
3548     }
3549
3550   if (appData.debugMode) {
3551     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3552   }
3553     /* Parse the board */
3554     for (k = 0; k < ranks; k++) {
3555       for (j = 0; j < files; j++)
3556         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3557       if(gameInfo.holdingsWidth > 1) {
3558            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3559            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3560       }
3561     }
3562     CopyBoard(boards[moveNum], board);
3563     if (moveNum == 0) {
3564         startedFromSetupPosition =
3565           !CompareBoards(board, initialPosition);
3566         if(startedFromSetupPosition)
3567             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3568     }
3569
3570     /* [HGM] Set castling rights. Take the outermost Rooks,
3571        to make it also work for FRC opening positions. Note that board12
3572        is really defective for later FRC positions, as it has no way to
3573        indicate which Rook can castle if they are on the same side of King.
3574        For the initial position we grant rights to the outermost Rooks,
3575        and remember thos rights, and we then copy them on positions
3576        later in an FRC game. This means WB might not recognize castlings with
3577        Rooks that have moved back to their original position as illegal,
3578        but in ICS mode that is not its job anyway.
3579     */
3580     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3581     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3582
3583         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3584             if(board[0][i] == WhiteRook) j = i;
3585         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3586         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3587             if(board[0][i] == WhiteRook) j = i;
3588         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3589         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3590             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3591         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3592         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3593             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3594         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3595
3596         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3597         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3598             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3599         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3600             if(board[BOARD_HEIGHT-1][k] == bKing)
3601                 initialRights[5] = castlingRights[moveNum][5] = k;
3602     } else { int r;
3603         r = castlingRights[moveNum][0] = initialRights[0];
3604         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3605         r = castlingRights[moveNum][1] = initialRights[1];
3606         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3607         r = castlingRights[moveNum][3] = initialRights[3];
3608         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3609         r = castlingRights[moveNum][4] = initialRights[4];
3610         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3611         /* wildcastle kludge: always assume King has rights */
3612         r = castlingRights[moveNum][2] = initialRights[2];
3613         r = castlingRights[moveNum][5] = initialRights[5];
3614     }
3615     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3616     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3617
3618
3619     if (ics_getting_history == H_GOT_REQ_HEADER ||
3620         ics_getting_history == H_GOT_UNREQ_HEADER) {
3621         /* This was an initial position from a move list, not
3622            the current position */
3623         return;
3624     }
3625
3626     /* Update currentMove and known move number limits */
3627     newMove = newGame || moveNum > forwardMostMove;
3628
3629     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3630     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3631         takeback = forwardMostMove - moveNum;
3632         for (i = 0; i < takeback; i++) {
3633              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3634              SendToProgram("undo\n", &first);
3635         }
3636     }
3637
3638     if (newGame) {
3639         forwardMostMove = backwardMostMove = currentMove = moveNum;
3640         if (gameMode == IcsExamining && moveNum == 0) {
3641           /* Workaround for ICS limitation: we are not told the wild
3642              type when starting to examine a game.  But if we ask for
3643              the move list, the move list header will tell us */
3644             ics_getting_history = H_REQUESTED;
3645             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3646             SendToICS(str);
3647         }
3648     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3649                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3650         forwardMostMove = moveNum;
3651         if (!pausing || currentMove > forwardMostMove)
3652           currentMove = forwardMostMove;
3653     } else {
3654         /* New part of history that is not contiguous with old part */
3655         if (pausing && gameMode == IcsExamining) {
3656             pauseExamInvalid = TRUE;
3657             forwardMostMove = pauseExamForwardMostMove;
3658             return;
3659         }
3660         forwardMostMove = backwardMostMove = currentMove = moveNum;
3661         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3662             ics_getting_history = H_REQUESTED;
3663             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3664             SendToICS(str);
3665         }
3666     }
3667
3668     /* Update the clocks */
3669     if (strchr(elapsed_time, '.')) {
3670       /* Time is in ms */
3671       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3672       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3673     } else {
3674       /* Time is in seconds */
3675       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3676       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3677     }
3678
3679
3680 #if ZIPPY
3681     if (appData.zippyPlay && newGame &&
3682         gameMode != IcsObserving && gameMode != IcsIdle &&
3683         gameMode != IcsExamining)
3684       ZippyFirstBoard(moveNum, basetime, increment);
3685 #endif
3686
3687     /* Put the move on the move list, first converting
3688        to canonical algebraic form. */
3689     if (moveNum > 0) {
3690   if (appData.debugMode) {
3691     if (appData.debugMode) { int f = forwardMostMove;
3692         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3693                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3694     }
3695     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3696     fprintf(debugFP, "moveNum = %d\n", moveNum);
3697     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3698     setbuf(debugFP, NULL);
3699   }
3700         if (moveNum <= backwardMostMove) {
3701             /* We don't know what the board looked like before
3702                this move.  Punt. */
3703             strcpy(parseList[moveNum - 1], move_str);
3704             strcat(parseList[moveNum - 1], " ");
3705             strcat(parseList[moveNum - 1], elapsed_time);
3706             moveList[moveNum - 1][0] = NULLCHAR;
3707         } else if (strcmp(move_str, "none") == 0) {
3708             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3709             /* Again, we don't know what the board looked like;
3710                this is really the start of the game. */
3711             parseList[moveNum - 1][0] = NULLCHAR;
3712             moveList[moveNum - 1][0] = NULLCHAR;
3713             backwardMostMove = moveNum;
3714             startedFromSetupPosition = TRUE;
3715             fromX = fromY = toX = toY = -1;
3716         } else {
3717           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3718           //                 So we parse the long-algebraic move string in stead of the SAN move
3719           int valid; char buf[MSG_SIZ], *prom;
3720
3721           // str looks something like "Q/a1-a2"; kill the slash
3722           if(str[1] == '/')
3723                 sprintf(buf, "%c%s", str[0], str+2);
3724           else  strcpy(buf, str); // might be castling
3725           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3726                 strcat(buf, prom); // long move lacks promo specification!
3727           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3728                 if(appData.debugMode)
3729                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3730                 strcpy(move_str, buf);
3731           }
3732           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3733                                 &fromX, &fromY, &toX, &toY, &promoChar)
3734                || ParseOneMove(buf, moveNum - 1, &moveType,
3735                                 &fromX, &fromY, &toX, &toY, &promoChar);
3736           // end of long SAN patch
3737           if (valid) {
3738             (void) CoordsToAlgebraic(boards[moveNum - 1],
3739                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3740                                      fromY, fromX, toY, toX, promoChar,
3741                                      parseList[moveNum-1]);
3742             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3743                              castlingRights[moveNum]) ) {
3744               case MT_NONE:
3745               case MT_STALEMATE:
3746               default:
3747                 break;
3748               case MT_CHECK:
3749                 if(gameInfo.variant != VariantShogi)
3750                     strcat(parseList[moveNum - 1], "+");
3751                 break;
3752               case MT_CHECKMATE:
3753               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3754                 strcat(parseList[moveNum - 1], "#");
3755                 break;
3756             }
3757             strcat(parseList[moveNum - 1], " ");
3758             strcat(parseList[moveNum - 1], elapsed_time);
3759             /* currentMoveString is set as a side-effect of ParseOneMove */
3760             strcpy(moveList[moveNum - 1], currentMoveString);
3761             strcat(moveList[moveNum - 1], "\n");
3762           } else {
3763             /* Move from ICS was illegal!?  Punt. */
3764   if (appData.debugMode) {
3765     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3766     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3767   }
3768 #if 0
3769             if (appData.testLegality && appData.debugMode) {
3770                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3771                 DisplayError(str, 0);
3772             }
3773 #endif
3774             strcpy(parseList[moveNum - 1], move_str);
3775             strcat(parseList[moveNum - 1], " ");
3776             strcat(parseList[moveNum - 1], elapsed_time);
3777             moveList[moveNum - 1][0] = NULLCHAR;
3778             fromX = fromY = toX = toY = -1;
3779           }
3780         }
3781   if (appData.debugMode) {
3782     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3783     setbuf(debugFP, NULL);
3784   }
3785
3786 #if ZIPPY
3787         /* Send move to chess program (BEFORE animating it). */
3788         if (appData.zippyPlay && !newGame && newMove &&
3789            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3790
3791             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3792                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3793                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3794                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3795                             move_str);
3796                     DisplayError(str, 0);
3797                 } else {
3798                     if (first.sendTime) {
3799                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3800                     }
3801                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3802                     if (firstMove && !bookHit) {
3803                         firstMove = FALSE;
3804                         if (first.useColors) {
3805                           SendToProgram(gameMode == IcsPlayingWhite ?
3806                                         "white\ngo\n" :
3807                                         "black\ngo\n", &first);
3808                         } else {
3809                           SendToProgram("go\n", &first);
3810                         }
3811                         first.maybeThinking = TRUE;
3812                     }
3813                 }
3814             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3815               if (moveList[moveNum - 1][0] == NULLCHAR) {
3816                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3817                 DisplayError(str, 0);
3818               } else {
3819                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3820                 SendMoveToProgram(moveNum - 1, &first);
3821               }
3822             }
3823         }
3824 #endif
3825     }
3826
3827     if (moveNum > 0 && !gotPremove) {
3828         /* If move comes from a remote source, animate it.  If it
3829            isn't remote, it will have already been animated. */
3830         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3831             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3832         }
3833         if (!pausing && appData.highlightLastMove) {
3834             SetHighlights(fromX, fromY, toX, toY);
3835         }
3836     }
3837
3838     /* Start the clocks */
3839     whiteFlag = blackFlag = FALSE;
3840     appData.clockMode = !(basetime == 0 && increment == 0);
3841     if (ticking == 0) {
3842       ics_clock_paused = TRUE;
3843       StopClocks();
3844     } else if (ticking == 1) {
3845       ics_clock_paused = FALSE;
3846     }
3847     if (gameMode == IcsIdle ||
3848         relation == RELATION_OBSERVING_STATIC ||
3849         relation == RELATION_EXAMINING ||
3850         ics_clock_paused)
3851       DisplayBothClocks();
3852     else
3853       StartClocks();
3854
3855     /* Display opponents and material strengths */
3856     if (gameInfo.variant != VariantBughouse &&
3857         gameInfo.variant != VariantCrazyhouse) {
3858         if (tinyLayout || smallLayout) {
3859             if(gameInfo.variant == VariantNormal)
3860                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3861                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3862                     basetime, increment);
3863             else
3864                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3865                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3866                     basetime, increment, (int) gameInfo.variant);
3867         } else {
3868             if(gameInfo.variant == VariantNormal)
3869                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3870                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3871                     basetime, increment);
3872             else
3873                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3874                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3875                     basetime, increment, VariantName(gameInfo.variant));
3876         }
3877         DisplayTitle(str);
3878   if (appData.debugMode) {
3879     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3880   }
3881     }
3882
3883
3884     /* Display the board */
3885     if (!pausing) {
3886
3887       if (appData.premove)
3888           if (!gotPremove ||
3889              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3890              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3891               ClearPremoveHighlights();
3892
3893       DrawPosition(FALSE, boards[currentMove]);
3894       DisplayMove(moveNum - 1);
3895       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3896             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3897               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3898         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3899       }
3900     }
3901
3902     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3903 #if ZIPPY
3904     if(bookHit) { // [HGM] book: simulate book reply
3905         static char bookMove[MSG_SIZ]; // a bit generous?
3906
3907         programStats.nodes = programStats.depth = programStats.time =
3908         programStats.score = programStats.got_only_move = 0;
3909         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3910
3911         strcpy(bookMove, "move ");
3912         strcat(bookMove, bookHit);
3913         HandleMachineMove(bookMove, &first);
3914     }
3915 #endif
3916 }
3917
3918 void
3919 GetMoveListEvent()
3920 {
3921     char buf[MSG_SIZ];
3922     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3923         ics_getting_history = H_REQUESTED;
3924         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3925         SendToICS(buf);
3926     }
3927 }
3928
3929 void
3930 AnalysisPeriodicEvent(force)
3931      int force;
3932 {
3933     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3934          && !force) || !appData.periodicUpdates)
3935       return;
3936
3937     /* Send . command to Crafty to collect stats */
3938     SendToProgram(".\n", &first);
3939
3940     /* Don't send another until we get a response (this makes
3941        us stop sending to old Crafty's which don't understand
3942        the "." command (sending illegal cmds resets node count & time,
3943        which looks bad)) */
3944     programStats.ok_to_send = 0;
3945 }
3946
3947 void
3948 SendMoveToProgram(moveNum, cps)
3949      int moveNum;
3950      ChessProgramState *cps;
3951 {
3952     char buf[MSG_SIZ];
3953
3954     if (cps->useUsermove) {
3955       SendToProgram("usermove ", cps);
3956     }
3957     if (cps->useSAN) {
3958       char *space;
3959       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3960         int len = space - parseList[moveNum];
3961         memcpy(buf, parseList[moveNum], len);
3962         buf[len++] = '\n';
3963         buf[len] = NULLCHAR;
3964       } else {
3965         sprintf(buf, "%s\n", parseList[moveNum]);
3966       }
3967       SendToProgram(buf, cps);
3968     } else {
3969       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3970         AlphaRank(moveList[moveNum], 4);
3971         SendToProgram(moveList[moveNum], cps);
3972         AlphaRank(moveList[moveNum], 4); // and back
3973       } else
3974       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3975        * the engine. It would be nice to have a better way to identify castle
3976        * moves here. */
3977       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3978                                                                          && cps->useOOCastle) {
3979         int fromX = moveList[moveNum][0] - AAA;
3980         int fromY = moveList[moveNum][1] - ONE;
3981         int toX = moveList[moveNum][2] - AAA;
3982         int toY = moveList[moveNum][3] - ONE;
3983         if((boards[moveNum][fromY][fromX] == WhiteKing
3984             && boards[moveNum][toY][toX] == WhiteRook)
3985            || (boards[moveNum][fromY][fromX] == BlackKing
3986                && boards[moveNum][toY][toX] == BlackRook)) {
3987           if(toX > fromX) SendToProgram("O-O\n", cps);
3988           else SendToProgram("O-O-O\n", cps);
3989         }
3990         else SendToProgram(moveList[moveNum], cps);
3991       }
3992       else SendToProgram(moveList[moveNum], cps);
3993       /* End of additions by Tord */
3994     }
3995
3996     /* [HGM] setting up the opening has brought engine in force mode! */
3997     /*       Send 'go' if we are in a mode where machine should play. */
3998     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
3999         (gameMode == TwoMachinesPlay   ||
4000 #ifdef ZIPPY
4001          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4002 #endif
4003          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4004         SendToProgram("go\n", cps);
4005   if (appData.debugMode) {
4006     fprintf(debugFP, "(extra)\n");
4007   }
4008     }
4009     setboardSpoiledMachineBlack = 0;
4010 }
4011
4012 void
4013 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4014      ChessMove moveType;
4015      int fromX, fromY, toX, toY;
4016 {
4017     char user_move[MSG_SIZ];
4018
4019     switch (moveType) {
4020       default:
4021         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4022                 (int)moveType, fromX, fromY, toX, toY);
4023         DisplayError(user_move + strlen("say "), 0);
4024         break;
4025       case WhiteKingSideCastle:
4026       case BlackKingSideCastle:
4027       case WhiteQueenSideCastleWild:
4028       case BlackQueenSideCastleWild:
4029       /* PUSH Fabien */
4030       case WhiteHSideCastleFR:
4031       case BlackHSideCastleFR:
4032       /* POP Fabien */
4033         sprintf(user_move, "o-o\n");
4034         break;
4035       case WhiteQueenSideCastle:
4036       case BlackQueenSideCastle:
4037       case WhiteKingSideCastleWild:
4038       case BlackKingSideCastleWild:
4039       /* PUSH Fabien */
4040       case WhiteASideCastleFR:
4041       case BlackASideCastleFR:
4042       /* POP Fabien */
4043         sprintf(user_move, "o-o-o\n");
4044         break;
4045       case WhitePromotionQueen:
4046       case BlackPromotionQueen:
4047       case WhitePromotionRook:
4048       case BlackPromotionRook:
4049       case WhitePromotionBishop:
4050       case BlackPromotionBishop:
4051       case WhitePromotionKnight:
4052       case BlackPromotionKnight:
4053       case WhitePromotionKing:
4054       case BlackPromotionKing:
4055       case WhitePromotionChancellor:
4056       case BlackPromotionChancellor:
4057       case WhitePromotionArchbishop:
4058       case BlackPromotionArchbishop:
4059         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4060             sprintf(user_move, "%c%c%c%c=%c\n",
4061                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4062                 PieceToChar(WhiteFerz));
4063         else if(gameInfo.variant == VariantGreat)
4064             sprintf(user_move, "%c%c%c%c=%c\n",
4065                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4066                 PieceToChar(WhiteMan));
4067         else
4068             sprintf(user_move, "%c%c%c%c=%c\n",
4069                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4070                 PieceToChar(PromoPiece(moveType)));
4071         break;
4072       case WhiteDrop:
4073       case BlackDrop:
4074         sprintf(user_move, "%c@%c%c\n",
4075                 ToUpper(PieceToChar((ChessSquare) fromX)),
4076                 AAA + toX, ONE + toY);
4077         break;
4078       case NormalMove:
4079       case WhiteCapturesEnPassant:
4080       case BlackCapturesEnPassant:
4081       case IllegalMove:  /* could be a variant we don't quite understand */
4082         sprintf(user_move, "%c%c%c%c\n",
4083                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4084         break;
4085     }
4086     SendToICS(user_move);
4087 }
4088
4089 void
4090 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4091      int rf, ff, rt, ft;
4092      char promoChar;
4093      char move[7];
4094 {
4095     if (rf == DROP_RANK) {
4096         sprintf(move, "%c@%c%c\n",
4097                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4098     } else {
4099         if (promoChar == 'x' || promoChar == NULLCHAR) {
4100             sprintf(move, "%c%c%c%c\n",
4101                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4102         } else {
4103             sprintf(move, "%c%c%c%c%c\n",
4104                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4105         }
4106     }
4107 }
4108
4109 void
4110 ProcessICSInitScript(f)
4111      FILE *f;
4112 {
4113     char buf[MSG_SIZ];
4114
4115     while (fgets(buf, MSG_SIZ, f)) {
4116         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4117     }
4118
4119     fclose(f);
4120 }
4121
4122
4123 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4124 void
4125 AlphaRank(char *move, int n)
4126 {
4127 //    char *p = move, c; int x, y;
4128
4129     if (appData.debugMode) {
4130         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4131     }
4132
4133     if(move[1]=='*' &&
4134        move[2]>='0' && move[2]<='9' &&
4135        move[3]>='a' && move[3]<='x'    ) {
4136         move[1] = '@';
4137         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4138         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4139     } else
4140     if(move[0]>='0' && move[0]<='9' &&
4141        move[1]>='a' && move[1]<='x' &&
4142        move[2]>='0' && move[2]<='9' &&
4143        move[3]>='a' && move[3]<='x'    ) {
4144         /* input move, Shogi -> normal */
4145         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4146         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4147         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4148         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4149     } else
4150     if(move[1]=='@' &&
4151        move[3]>='0' && move[3]<='9' &&
4152        move[2]>='a' && move[2]<='x'    ) {
4153         move[1] = '*';
4154         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4155         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4156     } else
4157     if(
4158        move[0]>='a' && move[0]<='x' &&
4159        move[3]>='0' && move[3]<='9' &&
4160        move[2]>='a' && move[2]<='x'    ) {
4161          /* output move, normal -> Shogi */
4162         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4163         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4164         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4165         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4166         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4167     }
4168     if (appData.debugMode) {
4169         fprintf(debugFP, "   out = '%s'\n", move);
4170     }
4171 }
4172
4173 /* Parser for moves from gnuchess, ICS, or user typein box */
4174 Boolean
4175 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4176      char *move;
4177      int moveNum;
4178      ChessMove *moveType;
4179      int *fromX, *fromY, *toX, *toY;
4180      char *promoChar;
4181 {
4182     if (appData.debugMode) {
4183         fprintf(debugFP, "move to parse: %s\n", move);
4184     }
4185     *moveType = yylexstr(moveNum, move);
4186
4187     switch (*moveType) {
4188       case WhitePromotionChancellor:
4189       case BlackPromotionChancellor:
4190       case WhitePromotionArchbishop:
4191       case BlackPromotionArchbishop:
4192       case WhitePromotionQueen:
4193       case BlackPromotionQueen:
4194       case WhitePromotionRook:
4195       case BlackPromotionRook:
4196       case WhitePromotionBishop:
4197       case BlackPromotionBishop:
4198       case WhitePromotionKnight:
4199       case BlackPromotionKnight:
4200       case WhitePromotionKing:
4201       case BlackPromotionKing:
4202       case NormalMove:
4203       case WhiteCapturesEnPassant:
4204       case BlackCapturesEnPassant:
4205       case WhiteKingSideCastle:
4206       case WhiteQueenSideCastle:
4207       case BlackKingSideCastle:
4208       case BlackQueenSideCastle:
4209       case WhiteKingSideCastleWild:
4210       case WhiteQueenSideCastleWild:
4211       case BlackKingSideCastleWild:
4212       case BlackQueenSideCastleWild:
4213       /* Code added by Tord: */
4214       case WhiteHSideCastleFR:
4215       case WhiteASideCastleFR:
4216       case BlackHSideCastleFR:
4217       case BlackASideCastleFR:
4218       /* End of code added by Tord */
4219       case IllegalMove:         /* bug or odd chess variant */
4220         *fromX = currentMoveString[0] - AAA;
4221         *fromY = currentMoveString[1] - ONE;
4222         *toX = currentMoveString[2] - AAA;
4223         *toY = currentMoveString[3] - ONE;
4224         *promoChar = currentMoveString[4];
4225         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4226             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4227     if (appData.debugMode) {
4228         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4229     }
4230             *fromX = *fromY = *toX = *toY = 0;
4231             return FALSE;
4232         }
4233         if (appData.testLegality) {
4234           return (*moveType != IllegalMove);
4235         } else {
4236           return !(fromX == fromY && toX == toY);
4237         }
4238
4239       case WhiteDrop:
4240       case BlackDrop:
4241         *fromX = *moveType == WhiteDrop ?
4242           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4243           (int) CharToPiece(ToLower(currentMoveString[0]));
4244         *fromY = DROP_RANK;
4245         *toX = currentMoveString[2] - AAA;
4246         *toY = currentMoveString[3] - ONE;
4247         *promoChar = NULLCHAR;
4248         return TRUE;
4249
4250       case AmbiguousMove:
4251       case ImpossibleMove:
4252       case (ChessMove) 0:       /* end of file */
4253       case ElapsedTime:
4254       case Comment:
4255       case PGNTag:
4256       case NAG:
4257       case WhiteWins:
4258       case BlackWins:
4259       case GameIsDrawn:
4260       default:
4261     if (appData.debugMode) {
4262         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4263     }
4264         /* bug? */
4265         *fromX = *fromY = *toX = *toY = 0;
4266         *promoChar = NULLCHAR;
4267         return FALSE;
4268     }
4269 }
4270
4271 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4272 // All positions will have equal probability, but the current method will not provide a unique
4273 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4274 #define DARK 1
4275 #define LITE 2
4276 #define ANY 3
4277
4278 int squaresLeft[4];
4279 int piecesLeft[(int)BlackPawn];
4280 int seed, nrOfShuffles;
4281
4282 void GetPositionNumber()
4283 {       // sets global variable seed
4284         int i;
4285
4286         seed = appData.defaultFrcPosition;
4287         if(seed < 0) { // randomize based on time for negative FRC position numbers
4288                 for(i=0; i<50; i++) seed += random();
4289                 seed = random() ^ random() >> 8 ^ random() << 8;
4290                 if(seed<0) seed = -seed;
4291         }
4292 }
4293
4294 int put(Board board, int pieceType, int rank, int n, int shade)
4295 // put the piece on the (n-1)-th empty squares of the given shade
4296 {
4297         int i;
4298
4299         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4300                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4301                         board[rank][i] = (ChessSquare) pieceType;
4302                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4303                         squaresLeft[ANY]--;
4304                         piecesLeft[pieceType]--;
4305                         return i;
4306                 }
4307         }
4308         return -1;
4309 }
4310
4311
4312 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4313 // calculate where the next piece goes, (any empty square), and put it there
4314 {
4315         int i;
4316
4317         i = seed % squaresLeft[shade];
4318         nrOfShuffles *= squaresLeft[shade];
4319         seed /= squaresLeft[shade];
4320         put(board, pieceType, rank, i, shade);
4321 }
4322
4323 void AddTwoPieces(Board board, int pieceType, int rank)
4324 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4325 {
4326         int i, n=squaresLeft[ANY], j=n-1, k;
4327
4328         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4329         i = seed % k;  // pick one
4330         nrOfShuffles *= k;
4331         seed /= k;
4332         while(i >= j) i -= j--;
4333         j = n - 1 - j; i += j;
4334         put(board, pieceType, rank, j, ANY);
4335         put(board, pieceType, rank, i, ANY);
4336 }
4337
4338 void SetUpShuffle(Board board, int number)
4339 {
4340         int i, p, first=1;
4341
4342         GetPositionNumber(); nrOfShuffles = 1;
4343
4344         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4345         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4346         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4347
4348         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4349
4350         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4351             p = (int) board[0][i];
4352             if(p < (int) BlackPawn) piecesLeft[p] ++;
4353             board[0][i] = EmptySquare;
4354         }
4355
4356         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4357             // shuffles restricted to allow normal castling put KRR first
4358             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4359                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4360             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4361                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4362             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4363                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4364             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4365                 put(board, WhiteRook, 0, 0, ANY);
4366             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4367         }
4368
4369         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4370             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4371             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4372                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4373                 while(piecesLeft[p] >= 2) {
4374                     AddOnePiece(board, p, 0, LITE);
4375                     AddOnePiece(board, p, 0, DARK);
4376                 }
4377                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4378             }
4379
4380         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4381             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4382             // but we leave King and Rooks for last, to possibly obey FRC restriction
4383             if(p == (int)WhiteRook) continue;
4384             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4385             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4386         }
4387
4388         // now everything is placed, except perhaps King (Unicorn) and Rooks
4389
4390         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4391             // Last King gets castling rights
4392             while(piecesLeft[(int)WhiteUnicorn]) {
4393                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4394                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4395             }
4396
4397             while(piecesLeft[(int)WhiteKing]) {
4398                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4399                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4400             }
4401
4402
4403         } else {
4404             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4405             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4406         }
4407
4408         // Only Rooks can be left; simply place them all
4409         while(piecesLeft[(int)WhiteRook]) {
4410                 i = put(board, WhiteRook, 0, 0, ANY);
4411                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4412                         if(first) {
4413                                 first=0;
4414                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4415                         }
4416                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4417                 }
4418         }
4419         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4420             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4421         }
4422
4423         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4424 }
4425
4426 int SetCharTable( char *table, const char * map )
4427 /* [HGM] moved here from winboard.c because of its general usefulness */
4428 /*       Basically a safe strcpy that uses the last character as King */
4429 {
4430     int result = FALSE; int NrPieces;
4431
4432     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4433                     && NrPieces >= 12 && !(NrPieces&1)) {
4434         int i; /* [HGM] Accept even length from 12 to 34 */
4435
4436         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4437         for( i=0; i<NrPieces/2-1; i++ ) {
4438             table[i] = map[i];
4439             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4440         }
4441         table[(int) WhiteKing]  = map[NrPieces/2-1];
4442         table[(int) BlackKing]  = map[NrPieces-1];
4443
4444         result = TRUE;
4445     }
4446
4447     return result;
4448 }
4449
4450 void Prelude(Board board)
4451 {       // [HGM] superchess: random selection of exo-pieces
4452         int i, j, k; ChessSquare p;
4453         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4454
4455         GetPositionNumber(); // use FRC position number
4456
4457         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4458             SetCharTable(pieceToChar, appData.pieceToCharTable);
4459             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4460                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4461         }
4462
4463         j = seed%4;                 seed /= 4;
4464         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4465         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4466         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4467         j = seed%3 + (seed%3 >= j); seed /= 3;
4468         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4469         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4470         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4471         j = seed%3;                 seed /= 3;
4472         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4473         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4474         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4475         j = seed%2 + (seed%2 >= j); seed /= 2;
4476         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4477         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4478         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4479         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4480         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4481         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4482         put(board, exoPieces[0],    0, 0, ANY);
4483         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4484 }
4485
4486 void
4487 InitPosition(redraw)
4488      int redraw;
4489 {
4490     ChessSquare (* pieces)[BOARD_SIZE];
4491     int i, j, pawnRow, overrule,
4492     oldx = gameInfo.boardWidth,
4493     oldy = gameInfo.boardHeight,
4494     oldh = gameInfo.holdingsWidth,
4495     oldv = gameInfo.variant;
4496
4497     currentMove = forwardMostMove = backwardMostMove = 0;
4498     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4499
4500     /* [AS] Initialize pv info list [HGM] and game status */
4501     {
4502         for( i=0; i<MAX_MOVES; i++ ) {
4503             pvInfoList[i].depth = 0;
4504             epStatus[i]=EP_NONE;
4505             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4506         }
4507
4508         initialRulePlies = 0; /* 50-move counter start */
4509
4510         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4511         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4512     }
4513
4514
4515     /* [HGM] logic here is completely changed. In stead of full positions */
4516     /* the initialized data only consist of the two backranks. The switch */
4517     /* selects which one we will use, which is than copied to the Board   */
4518     /* initialPosition, which for the rest is initialized by Pawns and    */
4519     /* empty squares. This initial position is then copied to boards[0],  */
4520     /* possibly after shuffling, so that it remains available.            */
4521
4522     gameInfo.holdingsWidth = 0; /* default board sizes */
4523     gameInfo.boardWidth    = 8;
4524     gameInfo.boardHeight   = 8;
4525     gameInfo.holdingsSize  = 0;
4526     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4527     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4528     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4529
4530     switch (gameInfo.variant) {
4531     case VariantFischeRandom:
4532       shuffleOpenings = TRUE;
4533     default:
4534       pieces = FIDEArray;
4535       break;
4536     case VariantShatranj:
4537       pieces = ShatranjArray;
4538       nrCastlingRights = 0;
4539       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4540       break;
4541     case VariantTwoKings:
4542       pieces = twoKingsArray;
4543       break;
4544     case VariantCapaRandom:
4545       shuffleOpenings = TRUE;
4546     case VariantCapablanca:
4547       pieces = CapablancaArray;
4548       gameInfo.boardWidth = 10;
4549       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4550       break;
4551     case VariantGothic:
4552       pieces = GothicArray;
4553       gameInfo.boardWidth = 10;
4554       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4555       break;
4556     case VariantJanus:
4557       pieces = JanusArray;
4558       gameInfo.boardWidth = 10;
4559       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4560       nrCastlingRights = 6;
4561         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4562         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4563         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4564         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4565         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4566         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4567       break;
4568     case VariantFalcon:
4569       pieces = FalconArray;
4570       gameInfo.boardWidth = 10;
4571       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4572       break;
4573     case VariantXiangqi:
4574       pieces = XiangqiArray;
4575       gameInfo.boardWidth  = 9;
4576       gameInfo.boardHeight = 10;
4577       nrCastlingRights = 0;
4578       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4579       break;
4580     case VariantShogi:
4581       pieces = ShogiArray;
4582       gameInfo.boardWidth  = 9;
4583       gameInfo.boardHeight = 9;
4584       gameInfo.holdingsSize = 7;
4585       nrCastlingRights = 0;
4586       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4587       break;
4588     case VariantCourier:
4589       pieces = CourierArray;
4590       gameInfo.boardWidth  = 12;
4591       nrCastlingRights = 0;
4592       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4593       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4594       break;
4595     case VariantKnightmate:
4596       pieces = KnightmateArray;
4597       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4598       break;
4599     case VariantFairy:
4600       pieces = fairyArray;
4601       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4602       break;
4603     case VariantGreat:
4604       pieces = GreatArray;
4605       gameInfo.boardWidth = 10;
4606       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4607       gameInfo.holdingsSize = 8;
4608       break;
4609     case VariantSuper:
4610       pieces = FIDEArray;
4611       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4612       gameInfo.holdingsSize = 8;
4613       startedFromSetupPosition = TRUE;
4614       break;
4615     case VariantCrazyhouse:
4616     case VariantBughouse:
4617       pieces = FIDEArray;
4618       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4619       gameInfo.holdingsSize = 5;
4620       break;
4621     case VariantWildCastle:
4622       pieces = FIDEArray;
4623       /* !!?shuffle with kings guaranteed to be on d or e file */
4624       shuffleOpenings = 1;
4625       break;
4626     case VariantNoCastle:
4627       pieces = FIDEArray;
4628       nrCastlingRights = 0;
4629       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4630       /* !!?unconstrained back-rank shuffle */
4631       shuffleOpenings = 1;
4632       break;
4633     }
4634
4635     overrule = 0;
4636     if(appData.NrFiles >= 0) {
4637         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4638         gameInfo.boardWidth = appData.NrFiles;
4639     }
4640     if(appData.NrRanks >= 0) {
4641         gameInfo.boardHeight = appData.NrRanks;
4642     }
4643     if(appData.holdingsSize >= 0) {
4644         i = appData.holdingsSize;
4645         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4646         gameInfo.holdingsSize = i;
4647     }
4648     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4649     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4650         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4651
4652     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4653     if(pawnRow < 1) pawnRow = 1;
4654
4655     /* User pieceToChar list overrules defaults */
4656     if(appData.pieceToCharTable != NULL)
4657         SetCharTable(pieceToChar, appData.pieceToCharTable);
4658
4659     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4660
4661         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4662             s = (ChessSquare) 0; /* account holding counts in guard band */
4663         for( i=0; i<BOARD_HEIGHT; i++ )
4664             initialPosition[i][j] = s;
4665
4666         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4667         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4668         initialPosition[pawnRow][j] = WhitePawn;
4669         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4670         if(gameInfo.variant == VariantXiangqi) {
4671             if(j&1) {
4672                 initialPosition[pawnRow][j] =
4673                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4674                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4675                    initialPosition[2][j] = WhiteCannon;
4676                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4677                 }
4678             }
4679         }
4680         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4681     }
4682     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4683
4684             j=BOARD_LEFT+1;
4685             initialPosition[1][j] = WhiteBishop;
4686             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4687             j=BOARD_RGHT-2;
4688             initialPosition[1][j] = WhiteRook;
4689             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4690     }
4691
4692     if( nrCastlingRights == -1) {
4693         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4694         /*       This sets default castling rights from none to normal corners   */
4695         /* Variants with other castling rights must set them themselves above    */
4696         nrCastlingRights = 6;
4697
4698         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4699         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4700         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4701         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4702         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4703         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4704      }
4705
4706      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4707      if(gameInfo.variant == VariantGreat) { // promotion commoners
4708         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4709         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4710         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4711         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4712      }
4713 #if 0
4714     if(gameInfo.variant == VariantFischeRandom) {
4715       if( appData.defaultFrcPosition < 0 ) {
4716         ShuffleFRC( initialPosition );
4717       }
4718       else {
4719         SetupFRC( initialPosition, appData.defaultFrcPosition );
4720       }
4721       startedFromSetupPosition = TRUE;
4722     } else
4723 #else
4724   if (appData.debugMode) {
4725     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4726   }
4727     if(shuffleOpenings) {
4728         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4729         startedFromSetupPosition = TRUE;
4730     }
4731 #endif
4732     if(startedFromPositionFile) {
4733       /* [HGM] loadPos: use PositionFile for every new game */
4734       CopyBoard(initialPosition, filePosition);
4735       for(i=0; i<nrCastlingRights; i++)
4736           castlingRights[0][i] = initialRights[i] = fileRights[i];
4737       startedFromSetupPosition = TRUE;
4738     }
4739
4740     CopyBoard(boards[0], initialPosition);
4741     if(oldx != gameInfo.boardWidth ||
4742        oldy != gameInfo.boardHeight ||
4743        oldh != gameInfo.holdingsWidth
4744 #ifdef GOTHIC
4745        || oldv == VariantGothic ||        // For licensing popups
4746        gameInfo.variant == VariantGothic
4747 #endif
4748 #ifdef FALCON
4749        || oldv == VariantFalcon ||
4750        gameInfo.variant == VariantFalcon
4751 #endif
4752                                          )
4753       {
4754             InitDrawingSizes(-2 ,0);
4755       }
4756
4757     if (redraw)
4758       DrawPosition(TRUE, boards[currentMove]);
4759
4760 }
4761
4762 void
4763 SendBoard(cps, moveNum)
4764      ChessProgramState *cps;
4765      int moveNum;
4766 {
4767     char message[MSG_SIZ];
4768
4769     if (cps->useSetboard) {
4770       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4771       sprintf(message, "setboard %s\n", fen);
4772       SendToProgram(message, cps);
4773       free(fen);
4774
4775     } else {
4776       ChessSquare *bp;
4777       int i, j;
4778       /* Kludge to set black to move, avoiding the troublesome and now
4779        * deprecated "black" command.
4780        */
4781       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4782
4783       SendToProgram("edit\n", cps);
4784       SendToProgram("#\n", cps);
4785       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4786         bp = &boards[moveNum][i][BOARD_LEFT];
4787         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4788           if ((int) *bp < (int) BlackPawn) {
4789             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4790                     AAA + j, ONE + i);
4791             if(message[0] == '+' || message[0] == '~') {
4792                 sprintf(message, "%c%c%c+\n",
4793                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4794                         AAA + j, ONE + i);
4795             }
4796             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4797                 message[1] = BOARD_RGHT   - 1 - j + '1';
4798                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4799             }
4800             SendToProgram(message, cps);
4801           }
4802         }
4803       }
4804
4805       SendToProgram("c\n", cps);
4806       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4807         bp = &boards[moveNum][i][BOARD_LEFT];
4808         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4809           if (((int) *bp != (int) EmptySquare)
4810               && ((int) *bp >= (int) BlackPawn)) {
4811             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4812                     AAA + j, ONE + i);
4813             if(message[0] == '+' || message[0] == '~') {
4814                 sprintf(message, "%c%c%c+\n",
4815                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4816                         AAA + j, ONE + i);
4817             }
4818             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4819                 message[1] = BOARD_RGHT   - 1 - j + '1';
4820                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4821             }
4822             SendToProgram(message, cps);
4823           }
4824         }
4825       }
4826
4827       SendToProgram(".\n", cps);
4828     }
4829     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4830 }
4831
4832 int
4833 IsPromotion(fromX, fromY, toX, toY)
4834      int fromX, fromY, toX, toY;
4835 {
4836     /* [HGM] add Shogi promotions */
4837     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4838     ChessSquare piece;
4839
4840     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4841       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4842    /* [HGM] Note to self: line above also weeds out drops */
4843     piece = boards[currentMove][fromY][fromX];
4844     if(gameInfo.variant == VariantShogi) {
4845         promotionZoneSize = 3;
4846         highestPromotingPiece = (int)WhiteKing;
4847         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4848            and if in normal chess we then allow promotion to King, why not
4849            allow promotion of other piece in Shogi?                         */
4850     }
4851     if((int)piece >= BlackPawn) {
4852         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4853              return FALSE;
4854         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4855     } else {
4856         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4857            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4858     }
4859     return ( (int)piece <= highestPromotingPiece );
4860 }
4861
4862 int
4863 InPalace(row, column)
4864      int row, column;
4865 {   /* [HGM] for Xiangqi */
4866     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4867          column < (BOARD_WIDTH + 4)/2 &&
4868          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4869     return FALSE;
4870 }
4871
4872 int
4873 PieceForSquare (x, y)
4874      int x;
4875      int y;
4876 {
4877   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4878      return -1;
4879   else
4880      return boards[currentMove][y][x];
4881 }
4882
4883 int
4884 OKToStartUserMove(x, y)
4885      int x, y;
4886 {
4887     ChessSquare from_piece;
4888     int white_piece;
4889
4890     if (matchMode) return FALSE;
4891     if (gameMode == EditPosition) return TRUE;
4892
4893     if (x >= 0 && y >= 0)
4894       from_piece = boards[currentMove][y][x];
4895     else
4896       from_piece = EmptySquare;
4897
4898     if (from_piece == EmptySquare) return FALSE;
4899
4900     white_piece = (int)from_piece >= (int)WhitePawn &&
4901       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4902
4903     switch (gameMode) {
4904       case PlayFromGameFile:
4905       case AnalyzeFile:
4906       case TwoMachinesPlay:
4907       case EndOfGame:
4908         return FALSE;
4909
4910       case IcsObserving:
4911       case IcsIdle:
4912         return FALSE;
4913
4914       case MachinePlaysWhite:
4915       case IcsPlayingBlack:
4916         if (appData.zippyPlay) return FALSE;
4917         if (white_piece) {
4918             DisplayMoveError(_("You are playing Black"));
4919             return FALSE;
4920         }
4921         break;
4922
4923       case MachinePlaysBlack:
4924       case IcsPlayingWhite:
4925         if (appData.zippyPlay) return FALSE;
4926         if (!white_piece) {
4927             DisplayMoveError(_("You are playing White"));
4928             return FALSE;
4929         }
4930         break;
4931
4932       case EditGame:
4933         if (!white_piece && WhiteOnMove(currentMove)) {
4934             DisplayMoveError(_("It is White's turn"));
4935             return FALSE;
4936         }
4937         if (white_piece && !WhiteOnMove(currentMove)) {
4938             DisplayMoveError(_("It is Black's turn"));
4939             return FALSE;
4940         }
4941         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4942             /* Editing correspondence game history */
4943             /* Could disallow this or prompt for confirmation */
4944             cmailOldMove = -1;
4945         }
4946         if (currentMove < forwardMostMove) {
4947             /* Discarding moves */
4948             /* Could prompt for confirmation here,
4949                but I don't think that's such a good idea */
4950             forwardMostMove = currentMove;
4951         }
4952         break;
4953
4954       case BeginningOfGame:
4955         if (appData.icsActive) return FALSE;
4956         if (!appData.noChessProgram) {
4957             if (!white_piece) {
4958                 DisplayMoveError(_("You are playing White"));
4959                 return FALSE;
4960             }
4961         }
4962         break;
4963
4964       case Training:
4965         if (!white_piece && WhiteOnMove(currentMove)) {
4966             DisplayMoveError(_("It is White's turn"));
4967             return FALSE;
4968         }
4969         if (white_piece && !WhiteOnMove(currentMove)) {
4970             DisplayMoveError(_("It is Black's turn"));
4971             return FALSE;
4972         }
4973         break;
4974
4975       default:
4976       case IcsExamining:
4977         break;
4978     }
4979     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4980         && gameMode != AnalyzeFile && gameMode != Training) {
4981         DisplayMoveError(_("Displayed position is not current"));
4982         return FALSE;
4983     }
4984     return TRUE;
4985 }
4986
4987 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4988 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4989 int lastLoadGameUseList = FALSE;
4990 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4991 ChessMove lastLoadGameStart = (ChessMove) 0;
4992
4993
4994 ChessMove
4995 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4996      int fromX, fromY, toX, toY;
4997      int promoChar;
4998 {
4999     ChessMove moveType;
5000     ChessSquare pdown, pup;
5001
5002     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5003     if ((fromX == toX) && (fromY == toY)) {
5004         return ImpossibleMove;
5005     }
5006
5007     /* [HGM] suppress all moves into holdings area and guard band */
5008     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5009             return ImpossibleMove;
5010
5011     /* [HGM] <sameColor> moved to here from winboard.c */
5012     /* note: this code seems to exist for filtering out some obviously illegal premoves */
5013     pdown = boards[currentMove][fromY][fromX];
5014     pup = boards[currentMove][toY][toX];
5015     if (    gameMode != EditPosition &&
5016             (WhitePawn <= pdown && pdown < BlackPawn &&
5017              WhitePawn <= pup && pup < BlackPawn  ||
5018              BlackPawn <= pdown && pdown < EmptySquare &&
5019              BlackPawn <= pup && pup < EmptySquare
5020             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5021                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5022                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  )
5023         )           )
5024          return ImpossibleMove;
5025
5026     /* Check if the user is playing in turn.  This is complicated because we
5027        let the user "pick up" a piece before it is his turn.  So the piece he
5028        tried to pick up may have been captured by the time he puts it down!
5029        Therefore we use the color the user is supposed to be playing in this
5030        test, not the color of the piece that is currently on the starting
5031        square---except in EditGame mode, where the user is playing both
5032        sides; fortunately there the capture race can't happen.  (It can
5033        now happen in IcsExamining mode, but that's just too bad.  The user
5034        will get a somewhat confusing message in that case.)
5035        */
5036
5037     switch (gameMode) {
5038       case PlayFromGameFile:
5039       case AnalyzeFile:
5040       case TwoMachinesPlay:
5041       case EndOfGame:
5042       case IcsObserving:
5043       case IcsIdle:
5044         /* We switched into a game mode where moves are not accepted,
5045            perhaps while the mouse button was down. */
5046         return ImpossibleMove;
5047
5048       case MachinePlaysWhite:
5049         /* User is moving for Black */
5050         if (WhiteOnMove(currentMove)) {
5051             DisplayMoveError(_("It is White's turn"));
5052             return ImpossibleMove;
5053         }
5054         break;
5055
5056       case MachinePlaysBlack:
5057         /* User is moving for White */
5058         if (!WhiteOnMove(currentMove)) {
5059             DisplayMoveError(_("It is Black's turn"));
5060             return ImpossibleMove;
5061         }
5062         break;
5063
5064       case EditGame:
5065       case IcsExamining:
5066       case BeginningOfGame:
5067       case AnalyzeMode:
5068       case Training:
5069         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5070             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5071             /* User is moving for Black */
5072             if (WhiteOnMove(currentMove)) {
5073                 DisplayMoveError(_("It is White's turn"));
5074                 return ImpossibleMove;
5075             }
5076         } else {
5077             /* User is moving for White */
5078             if (!WhiteOnMove(currentMove)) {
5079                 DisplayMoveError(_("It is Black's turn"));
5080                 return ImpossibleMove;
5081             }
5082         }
5083         break;
5084
5085       case IcsPlayingBlack:
5086         /* User is moving for Black */
5087         if (WhiteOnMove(currentMove)) {
5088             if (!appData.premove) {
5089                 DisplayMoveError(_("It is White's turn"));
5090             } else if (toX >= 0 && toY >= 0) {
5091                 premoveToX = toX;
5092                 premoveToY = toY;
5093                 premoveFromX = fromX;
5094                 premoveFromY = fromY;
5095                 premovePromoChar = promoChar;
5096                 gotPremove = 1;
5097                 if (appData.debugMode)
5098                     fprintf(debugFP, "Got premove: fromX %d,"
5099                             "fromY %d, toX %d, toY %d\n",
5100                             fromX, fromY, toX, toY);
5101             }
5102             return ImpossibleMove;
5103         }
5104         break;
5105
5106       case IcsPlayingWhite:
5107         /* User is moving for White */
5108         if (!WhiteOnMove(currentMove)) {
5109             if (!appData.premove) {
5110                 DisplayMoveError(_("It is Black's turn"));
5111             } else if (toX >= 0 && toY >= 0) {
5112                 premoveToX = toX;
5113                 premoveToY = toY;
5114                 premoveFromX = fromX;
5115                 premoveFromY = fromY;
5116                 premovePromoChar = promoChar;
5117                 gotPremove = 1;
5118                 if (appData.debugMode)
5119                     fprintf(debugFP, "Got premove: fromX %d,"
5120                             "fromY %d, toX %d, toY %d\n",
5121                             fromX, fromY, toX, toY);
5122             }
5123             return ImpossibleMove;
5124         }
5125         break;
5126
5127       default:
5128         break;
5129
5130       case EditPosition:
5131         /* EditPosition, empty square, or different color piece;
5132            click-click move is possible */
5133         if (toX == -2 || toY == -2) {
5134             boards[0][fromY][fromX] = EmptySquare;
5135             return AmbiguousMove;
5136         } else if (toX >= 0 && toY >= 0) {
5137             boards[0][toY][toX] = boards[0][fromY][fromX];
5138             boards[0][fromY][fromX] = EmptySquare;
5139             return AmbiguousMove;
5140         }
5141         return ImpossibleMove;
5142     }
5143
5144     /* [HGM] If move started in holdings, it means a drop */
5145     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5146          if( pup != EmptySquare ) return ImpossibleMove;
5147          if(appData.testLegality) {
5148              /* it would be more logical if LegalityTest() also figured out
5149               * which drops are legal. For now we forbid pawns on back rank.
5150               * Shogi is on its own here...
5151               */
5152              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5153                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5154                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5155          }
5156          return WhiteDrop; /* Not needed to specify white or black yet */
5157     }
5158
5159     userOfferedDraw = FALSE;
5160
5161     /* [HGM] always test for legality, to get promotion info */
5162     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5163                           epStatus[currentMove], castlingRights[currentMove],
5164                                          fromY, fromX, toY, toX, promoChar);
5165
5166     /* [HGM] but possibly ignore an IllegalMove result */
5167     if (appData.testLegality) {
5168         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5169             DisplayMoveError(_("Illegal move"));
5170             return ImpossibleMove;
5171         }
5172     }
5173     if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5174     return moveType;
5175     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5176        function is made into one that returns an OK move type if FinishMove
5177        should be called. This to give the calling driver routine the
5178        opportunity to finish the userMove input with a promotion popup,
5179        without bothering the user with this for invalid or illegal moves */
5180
5181 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5182 }
5183
5184 /* Common tail of UserMoveEvent and DropMenuEvent */
5185 int
5186 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5187      ChessMove moveType;
5188      int fromX, fromY, toX, toY;
5189      /*char*/int promoChar;
5190 {
5191   char *bookHit = 0;
5192
5193   if(appData.debugMode)
5194     fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5195
5196   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5197     {
5198       // [HGM] superchess: suppress promotions to non-available piece
5199       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5200       if(WhiteOnMove(currentMove))
5201         {
5202           if(!boards[currentMove][k][BOARD_WIDTH-2])
5203             return 0;
5204         }
5205       else
5206         {
5207           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5208             return 0;
5209         }
5210     }
5211
5212   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5213      move type in caller when we know the move is a legal promotion */
5214   if(moveType == NormalMove && promoChar)
5215     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5216
5217   if(appData.debugMode)
5218     fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5219
5220   /* [HGM] convert drag-and-drop piece drops to standard form */
5221   if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1)
5222     {
5223       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5224       fromX = boards[currentMove][fromY][fromX];
5225       fromY = DROP_RANK;
5226     }
5227
5228   /* [HGM] <popupFix> The following if has been moved here from
5229      UserMoveEvent(). Because it seemed to belon here (why not allow
5230      piece drops in training games?), and because it can only be
5231      performed after it is known to what we promote. */
5232   if (gameMode == Training)
5233     {
5234       /* compare the move played on the board to the next move in the
5235        * game. If they match, display the move and the opponent's response.
5236        * If they don't match, display an error message.
5237        */
5238       int saveAnimate;
5239       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5240       CopyBoard(testBoard, boards[currentMove]);
5241       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5242
5243       if (CompareBoards(testBoard, boards[currentMove+1]))
5244         {
5245           ForwardInner(currentMove+1);
5246
5247           /* Autoplay the opponent's response.
5248            * if appData.animate was TRUE when Training mode was entered,
5249            * the response will be animated.
5250            */
5251           saveAnimate = appData.animate;
5252           appData.animate = animateTraining;
5253           ForwardInner(currentMove+1);
5254           appData.animate = saveAnimate;
5255
5256           /* check for the end of the game */
5257           if (currentMove >= forwardMostMove)
5258             {
5259               gameMode = PlayFromGameFile;
5260               ModeHighlight();
5261               SetTrainingModeOff();
5262               DisplayInformation(_("End of game"));
5263             }
5264         }
5265       else
5266         {
5267           DisplayError(_("Incorrect move"), 0);
5268         }
5269       return 1;
5270     }
5271
5272   /* Ok, now we know that the move is good, so we can kill
5273      the previous line in Analysis Mode */
5274   if (gameMode == AnalyzeMode && currentMove < forwardMostMove)
5275     {
5276       forwardMostMove = currentMove;
5277     }
5278
5279   /* If we need the chess program but it's dead, restart it */
5280   ResurrectChessProgram();
5281
5282   /* A user move restarts a paused game*/
5283   if (pausing)
5284     PauseEvent();
5285
5286   thinkOutput[0] = NULLCHAR;
5287
5288   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5289
5290   if (gameMode == BeginningOfGame)
5291     {
5292       if (appData.noChessProgram)
5293         {
5294           gameMode = EditGame;
5295           SetGameInfo();
5296         }
5297       else
5298         {
5299           char buf[MSG_SIZ];
5300           gameMode = MachinePlaysBlack;
5301           StartClocks();
5302           SetGameInfo();
5303           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5304           DisplayTitle(buf);
5305           if (first.sendName)
5306             {
5307               sprintf(buf, "name %s\n", gameInfo.white);
5308               SendToProgram(buf, &first);
5309             }
5310           StartClocks();
5311         }
5312       ModeHighlight();
5313     }
5314   if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5315
5316   /* Relay move to ICS or chess engine */
5317   if (appData.icsActive)
5318     {
5319       if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5320           gameMode == IcsExamining)
5321         {
5322           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5323           ics_user_moved = 1;
5324         }
5325     }
5326   else
5327     {
5328       if (first.sendTime && (gameMode == BeginningOfGame ||
5329                              gameMode == MachinePlaysWhite ||
5330                              gameMode == MachinePlaysBlack))
5331         {
5332           SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5333         }
5334       if (gameMode != EditGame && gameMode != PlayFromGameFile)
5335         {
5336           // [HGM] book: if program might be playing, let it use book
5337           bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5338           first.maybeThinking = TRUE;
5339         }
5340       else
5341         SendMoveToProgram(forwardMostMove-1, &first);
5342       if (currentMove == cmailOldMove + 1)
5343         {
5344           cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5345         }
5346     }
5347
5348   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5349
5350   switch (gameMode)
5351     {
5352     case EditGame:
5353       switch (MateTest(boards[currentMove], PosFlags(currentMove),
5354                        EP_UNKNOWN, castlingRights[currentMove]) )
5355         {
5356         case MT_NONE:
5357         case MT_CHECK:
5358           break;
5359         case MT_CHECKMATE:
5360         case MT_STAINMATE:
5361           if (WhiteOnMove(currentMove))
5362             {
5363               GameEnds(BlackWins, "Black mates", GE_PLAYER);
5364             }
5365           else
5366             {
5367               GameEnds(WhiteWins, "White mates", GE_PLAYER);
5368             }
5369           break;
5370         case MT_STALEMATE:
5371           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5372           break;
5373     }
5374       break;
5375
5376     case MachinePlaysBlack:
5377     case MachinePlaysWhite:
5378       /* disable certain menu options while machine is thinking */
5379       SetMachineThinkingEnables();
5380       break;
5381
5382     default:
5383       break;
5384     }
5385
5386   if(bookHit)
5387     { // [HGM] book: simulate book reply
5388       static char bookMove[MSG_SIZ]; // a bit generous?
5389
5390       programStats.nodes = programStats.depth = programStats.time =
5391         programStats.score = programStats.got_only_move = 0;
5392       sprintf(programStats.movelist, "%s (xbook)", bookHit);
5393
5394       strcpy(bookMove, "move ");
5395       strcat(bookMove, bookHit);
5396       HandleMachineMove(bookMove, &first);
5397     }
5398
5399   return 1;
5400 }
5401
5402 void
5403 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5404      int fromX, fromY, toX, toY;
5405      int promoChar;
5406 {
5407     /* [HGM] This routine was added to allow calling of its two logical
5408        parts from other modules in the old way. Before, UserMoveEvent()
5409        automatically called FinishMove() if the move was OK, and returned
5410        otherwise. I separated the two, in order to make it possible to
5411        slip a promotion popup in between. But that it always needs two
5412        calls, to the first part, (now called UserMoveTest() ), and to
5413        FinishMove if the first part succeeded. Calls that do not need
5414        to do anything in between, can call this routine the old way.
5415     */
5416   ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5417   if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5418   if(moveType != ImpossibleMove)
5419     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5420 }
5421
5422 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5423 {
5424 //    char * hint = lastHint;
5425     FrontEndProgramStats stats;
5426
5427     stats.which = cps == &first ? 0 : 1;
5428     stats.depth = cpstats->depth;
5429     stats.nodes = cpstats->nodes;
5430     stats.score = cpstats->score;
5431     stats.time = cpstats->time;
5432     stats.pv = cpstats->movelist;
5433     stats.hint = lastHint;
5434     stats.an_move_index = 0;
5435     stats.an_move_count = 0;
5436
5437     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5438         stats.hint = cpstats->move_name;
5439         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5440         stats.an_move_count = cpstats->nr_moves;
5441     }
5442
5443     SetProgramStats( &stats );
5444 }
5445
5446 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5447 {   // [HGM] book: this routine intercepts moves to simulate book replies
5448     char *bookHit = NULL;
5449
5450     //first determine if the incoming move brings opponent into his book
5451     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5452         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5453     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5454     if(bookHit != NULL && !cps->bookSuspend) {
5455         // make sure opponent is not going to reply after receiving move to book position
5456         SendToProgram("force\n", cps);
5457         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5458     }
5459     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5460     // now arrange restart after book miss
5461     if(bookHit) {
5462         // after a book hit we never send 'go', and the code after the call to this routine
5463         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5464         char buf[MSG_SIZ];
5465         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5466         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5467         SendToProgram(buf, cps);
5468         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5469     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5470         SendToProgram("go\n", cps);
5471         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5472     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5473         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5474             SendToProgram("go\n", cps);
5475         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5476     }
5477     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5478 }
5479
5480 char *savedMessage;
5481 ChessProgramState *savedState;
5482 void DeferredBookMove(void)
5483 {
5484         if(savedState->lastPing != savedState->lastPong)
5485                     ScheduleDelayedEvent(DeferredBookMove, 10);
5486         else
5487         HandleMachineMove(savedMessage, savedState);
5488 }
5489
5490 void
5491 HandleMachineMove(message, cps)
5492      char *message;
5493      ChessProgramState *cps;
5494 {
5495     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5496     char realname[MSG_SIZ];
5497     int fromX, fromY, toX, toY;
5498     ChessMove moveType;
5499     char promoChar;
5500     char *p;
5501     int machineWhite;
5502     char *bookHit;
5503
5504 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5505     /*
5506      * Kludge to ignore BEL characters
5507      */
5508     while (*message == '\007') message++;
5509
5510     /*
5511      * [HGM] engine debug message: ignore lines starting with '#' character
5512      */
5513     if(cps->debug && *message == '#') return;
5514
5515     /*
5516      * Look for book output
5517      */
5518     if (cps == &first && bookRequested) {
5519         if (message[0] == '\t' || message[0] == ' ') {
5520             /* Part of the book output is here; append it */
5521             strcat(bookOutput, message);
5522             strcat(bookOutput, "  \n");
5523             return;
5524         } else if (bookOutput[0] != NULLCHAR) {
5525             /* All of book output has arrived; display it */
5526             char *p = bookOutput;
5527             while (*p != NULLCHAR) {
5528                 if (*p == '\t') *p = ' ';
5529                 p++;
5530             }
5531             DisplayInformation(bookOutput);
5532             bookRequested = FALSE;
5533             /* Fall through to parse the current output */
5534         }
5535     }
5536
5537     /*
5538      * Look for machine move.
5539      */
5540     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5541         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5542     {
5543         /* This method is only useful on engines that support ping */
5544         if (cps->lastPing != cps->lastPong) {
5545           if (gameMode == BeginningOfGame) {
5546             /* Extra move from before last new; ignore */
5547             if (appData.debugMode) {
5548                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5549             }
5550           } else {
5551             if (appData.debugMode) {
5552                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5553                         cps->which, gameMode);
5554             }
5555
5556             SendToProgram("undo\n", cps);
5557           }
5558           return;
5559         }
5560
5561         switch (gameMode) {
5562           case BeginningOfGame:
5563             /* Extra move from before last reset; ignore */
5564             if (appData.debugMode) {
5565                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5566             }
5567             return;
5568
5569           case EndOfGame:
5570           case IcsIdle:
5571           default:
5572             /* Extra move after we tried to stop.  The mode test is
5573                not a reliable way of detecting this problem, but it's
5574                the best we can do on engines that don't support ping.
5575             */
5576             if (appData.debugMode) {
5577                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5578                         cps->which, gameMode);
5579             }
5580             SendToProgram("undo\n", cps);
5581             return;
5582
5583           case MachinePlaysWhite:
5584           case IcsPlayingWhite:
5585             machineWhite = TRUE;
5586             break;
5587
5588           case MachinePlaysBlack:
5589           case IcsPlayingBlack:
5590             machineWhite = FALSE;
5591             break;
5592
5593           case TwoMachinesPlay:
5594             machineWhite = (cps->twoMachinesColor[0] == 'w');
5595             break;
5596         }
5597         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5598             if (appData.debugMode) {
5599                 fprintf(debugFP,
5600                         "Ignoring move out of turn by %s, gameMode %d"
5601                         ", forwardMost %d\n",
5602                         cps->which, gameMode, forwardMostMove);
5603             }
5604             return;
5605         }
5606
5607     if (appData.debugMode) { int f = forwardMostMove;
5608         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5609                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5610     }
5611         if(cps->alphaRank) AlphaRank(machineMove, 4);
5612         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5613                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5614             /* Machine move could not be parsed; ignore it. */
5615             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5616                     machineMove, cps->which);
5617             DisplayError(buf1, 0);
5618             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5619                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5620             if (gameMode == TwoMachinesPlay) {
5621               GameEnds(machineWhite ? BlackWins : WhiteWins,
5622                        buf1, GE_XBOARD);
5623             }
5624             return;
5625         }
5626
5627         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5628         /* So we have to redo legality test with true e.p. status here,  */
5629         /* to make sure an illegal e.p. capture does not slip through,   */
5630         /* to cause a forfeit on a justified illegal-move complaint      */
5631         /* of the opponent.                                              */
5632         if( gameMode==TwoMachinesPlay && appData.testLegality
5633             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5634                                                               ) {
5635            ChessMove moveType;
5636            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5637                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5638                              fromY, fromX, toY, toX, promoChar);
5639             if (appData.debugMode) {
5640                 int i;
5641                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5642                     castlingRights[forwardMostMove][i], castlingRank[i]);
5643                 fprintf(debugFP, "castling rights\n");
5644             }
5645             if(moveType == IllegalMove) {
5646                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5647                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5648                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5649                            buf1, GE_XBOARD);
5650                 return;
5651            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5652            /* [HGM] Kludge to handle engines that send FRC-style castling
5653               when they shouldn't (like TSCP-Gothic) */
5654            switch(moveType) {
5655              case WhiteASideCastleFR:
5656              case BlackASideCastleFR:
5657                toX+=2;
5658                currentMoveString[2]++;
5659                break;
5660              case WhiteHSideCastleFR:
5661              case BlackHSideCastleFR:
5662                toX--;
5663                currentMoveString[2]--;
5664                break;
5665              default: ; // nothing to do, but suppresses warning of pedantic compilers
5666            }
5667         }
5668         hintRequested = FALSE;
5669         lastHint[0] = NULLCHAR;
5670         bookRequested = FALSE;
5671         /* Program may be pondering now */
5672         cps->maybeThinking = TRUE;
5673         if (cps->sendTime == 2) cps->sendTime = 1;
5674         if (cps->offeredDraw) cps->offeredDraw--;
5675
5676 #if ZIPPY
5677         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5678             first.initDone) {
5679           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5680           ics_user_moved = 1;
5681           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5682                 char buf[3*MSG_SIZ];
5683
5684                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",
5685                         programStats.score / 100.,
5686                         programStats.depth,
5687                         programStats.time / 100.,
5688                         u64ToDouble(programStats.nodes),
5689                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
5690                         programStats.movelist);
5691                 SendToICS(buf);
5692           }
5693         }
5694 #endif
5695         /* currentMoveString is set as a side-effect of ParseOneMove */
5696         strcpy(machineMove, currentMoveString);
5697         strcat(machineMove, "\n");
5698         strcpy(moveList[forwardMostMove], machineMove);
5699
5700         /* [AS] Save move info and clear stats for next move */
5701         pvInfoList[ forwardMostMove ].score = programStats.score;
5702         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5703         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5704         ClearProgramStats();
5705         thinkOutput[0] = NULLCHAR;
5706         hiddenThinkOutputState = 0;
5707
5708         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5709
5710         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5711         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5712             int count = 0;
5713
5714             while( count < adjudicateLossPlies ) {
5715                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5716
5717                 if( count & 1 ) {
5718                     score = -score; /* Flip score for winning side */
5719                 }
5720
5721                 if( score > adjudicateLossThreshold ) {
5722                     break;
5723                 }
5724
5725                 count++;
5726             }
5727
5728             if( count >= adjudicateLossPlies ) {
5729                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5730
5731                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5732                     "Xboard adjudication",
5733                     GE_XBOARD );
5734
5735                 return;
5736             }
5737         }
5738
5739         if( gameMode == TwoMachinesPlay ) {
5740           // [HGM] some adjudications useful with buggy engines
5741             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5742           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5743
5744
5745             if( appData.testLegality )
5746             {   /* [HGM] Some more adjudications for obstinate engines */
5747                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5748                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5749                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5750                 static int moveCount = 6;
5751                 ChessMove result;
5752                 char *reason = NULL;
5753
5754                 /* Count what is on board. */
5755                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5756                 {   ChessSquare p = boards[forwardMostMove][i][j];
5757                     int m=i;
5758
5759                     switch((int) p)
5760                     {   /* count B,N,R and other of each side */
5761                         case WhiteKing:
5762                         case BlackKing:
5763                              NrK++; break; // [HGM] atomic: count Kings
5764                         case WhiteKnight:
5765                              NrWN++; break;
5766                         case WhiteBishop:
5767                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5768                              bishopsColor |= 1 << ((i^j)&1);
5769                              NrWB++; break;
5770                         case BlackKnight:
5771                              NrBN++; break;
5772                         case BlackBishop:
5773                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5774                              bishopsColor |= 1 << ((i^j)&1);
5775                              NrBB++; break;
5776                         case WhiteRook:
5777                              NrWR++; break;
5778                         case BlackRook:
5779                              NrBR++; break;
5780                         case WhiteQueen:
5781                              NrWQ++; break;
5782                         case BlackQueen:
5783                              NrBQ++; break;
5784                         case EmptySquare:
5785                              break;
5786                         case BlackPawn:
5787                              m = 7-i;
5788                         case WhitePawn:
5789                              PawnAdvance += m; NrPawns++;
5790                     }
5791                     NrPieces += (p != EmptySquare);
5792                     NrW += ((int)p < (int)BlackPawn);
5793                     if(gameInfo.variant == VariantXiangqi &&
5794                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5795                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5796                         NrW -= ((int)p < (int)BlackPawn);
5797                     }
5798                 }
5799
5800                 /* Some material-based adjudications that have to be made before stalemate test */
5801                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5802                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5803                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5804                      if(appData.checkMates) {
5805                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5806                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5807                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5808                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5809                          return;
5810                      }
5811                 }
5812
5813                 /* Bare King in Shatranj (loses) or Losers (wins) */
5814                 if( NrW == 1 || NrPieces - NrW == 1) {
5815                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5816                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5817                      if(appData.checkMates) {
5818                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5819                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5820                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5821                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5822                          return;
5823                      }
5824                   } else
5825                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5826                   {    /* bare King */
5827                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5828                         if(appData.checkMates) {
5829                             /* but only adjudicate if adjudication enabled */
5830                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5831                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5832                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5833                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5834                             return;
5835                         }
5836                   }
5837                 } else bare = 1;
5838
5839
5840             // don't wait for engine to announce game end if we can judge ourselves
5841             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5842                                        castlingRights[forwardMostMove]) ) {
5843               case MT_CHECK:
5844                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5845                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5846                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5847                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5848                             checkCnt++;
5849                         if(checkCnt >= 2) {
5850                             reason = "Xboard adjudication: 3rd check";
5851                             epStatus[forwardMostMove] = EP_CHECKMATE;
5852                             break;
5853                         }
5854                     }
5855                 }
5856               case MT_NONE:
5857               default:
5858                 break;
5859               case MT_STALEMATE:
5860               case MT_STAINMATE:
5861                 reason = "Xboard adjudication: Stalemate";
5862                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5863                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5864                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5865                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5866                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5867                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5868                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5869                                                                         EP_CHECKMATE : EP_WINS);
5870                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5871                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5872                 }
5873                 break;
5874               case MT_CHECKMATE:
5875                 reason = "Xboard adjudication: Checkmate";
5876                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5877                 break;
5878             }
5879
5880                 switch(i = epStatus[forwardMostMove]) {
5881                     case EP_STALEMATE:
5882                         result = GameIsDrawn; break;
5883                     case EP_CHECKMATE:
5884                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5885                     case EP_WINS:
5886                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5887                     default:
5888                         result = (ChessMove) 0;
5889                 }
5890                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5891                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5892                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5893                     GameEnds( result, reason, GE_XBOARD );
5894                     return;
5895                 }
5896
5897                 /* Next absolutely insufficient mating material. */
5898                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5899                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5900                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5901                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5902                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5903
5904                      /* always flag draws, for judging claims */
5905                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5906
5907                      if(appData.materialDraws) {
5908                          /* but only adjudicate them if adjudication enabled */
5909                          SendToProgram("force\n", cps->other); // suppress reply
5910                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5911                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5912                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5913                          return;
5914                      }
5915                 }
5916
5917                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5918                 if(NrPieces == 4 &&
5919                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5920                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5921                    || NrWN==2 || NrBN==2     /* KNNK */
5922                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5923                   ) ) {
5924                      if(--moveCount < 0 && appData.trivialDraws)
5925                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5926                           SendToProgram("force\n", cps->other); // suppress reply
5927                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5928                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5929                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5930                           return;
5931                      }
5932                 } else moveCount = 6;
5933             }
5934           }
5935 #if 1
5936     if (appData.debugMode) { int i;
5937       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5938               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5939               appData.drawRepeats);
5940       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5941            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5942
5943     }
5944 #endif
5945                 /* Check for rep-draws */
5946                 count = 0;
5947                 for(k = forwardMostMove-2;
5948                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5949                         epStatus[k] < EP_UNKNOWN &&
5950                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5951                     k-=2)
5952                 {   int rights=0;
5953 #if 0
5954     if (appData.debugMode) {
5955       fprintf(debugFP, " loop\n");
5956     }
5957 #endif
5958                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5959 #if 0
5960     if (appData.debugMode) {
5961       fprintf(debugFP, "match\n");
5962     }
5963 #endif
5964                         /* compare castling rights */
5965                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5966                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5967                                 rights++; /* King lost rights, while rook still had them */
5968                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5969                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5970                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5971                                    rights++; /* but at least one rook lost them */
5972                         }
5973                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5974                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5975                                 rights++;
5976                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5977                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5978                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5979                                    rights++;
5980                         }
5981 #if 0
5982     if (appData.debugMode) {
5983       for(i=0; i<nrCastlingRights; i++)
5984       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5985     }
5986
5987     if (appData.debugMode) {
5988       fprintf(debugFP, " %d %d\n", rights, k);
5989     }
5990 #endif
5991                         if( rights == 0 && ++count > appData.drawRepeats-2
5992                             && appData.drawRepeats > 1) {
5993                              /* adjudicate after user-specified nr of repeats */
5994                              SendToProgram("force\n", cps->other); // suppress reply
5995                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5996                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5997                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5998                                 // [HGM] xiangqi: check for forbidden perpetuals
5999                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6000                                 for(m=forwardMostMove; m>k; m-=2) {
6001                                     if(MateTest(boards[m], PosFlags(m),
6002                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6003                                         ourPerpetual = 0; // the current mover did not always check
6004                                     if(MateTest(boards[m-1], PosFlags(m-1),
6005                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6006                                         hisPerpetual = 0; // the opponent did not always check
6007                                 }
6008                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6009                                                                         ourPerpetual, hisPerpetual);
6010                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6011                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6012                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6013                                     return;
6014                                 }
6015                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6016                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6017                                 // Now check for perpetual chases
6018                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6019                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6020                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6021                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6022                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6023                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6024                                         return;
6025                                     }
6026                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6027                                         break; // Abort repetition-checking loop.
6028                                 }
6029                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6030                              }
6031                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6032                              return;
6033                         }
6034                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6035                              epStatus[forwardMostMove] = EP_REP_DRAW;
6036                     }
6037                 }
6038
6039                 /* Now we test for 50-move draws. Determine ply count */
6040                 count = forwardMostMove;
6041                 /* look for last irreversble move */
6042                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6043                     count--;
6044                 /* if we hit starting position, add initial plies */
6045                 if( count == backwardMostMove )
6046                     count -= initialRulePlies;
6047                 count = forwardMostMove - count;
6048                 if( count >= 100)
6049                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6050                          /* this is used to judge if draw claims are legal */
6051                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6052                          SendToProgram("force\n", cps->other); // suppress reply
6053                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6054                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6055                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6056                          return;
6057                 }
6058
6059                 /* if draw offer is pending, treat it as a draw claim
6060                  * when draw condition present, to allow engines a way to
6061                  * claim draws before making their move to avoid a race
6062                  * condition occurring after their move
6063                  */
6064                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6065                          char *p = NULL;
6066                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6067                              p = "Draw claim: 50-move rule";
6068                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6069                              p = "Draw claim: 3-fold repetition";
6070                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6071                              p = "Draw claim: insufficient mating material";
6072                          if( p != NULL ) {
6073                              SendToProgram("force\n", cps->other); // suppress reply
6074                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6075                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6076                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6077                              return;
6078                          }
6079                 }
6080
6081
6082                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6083                     SendToProgram("force\n", cps->other); // suppress reply
6084                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6085                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6086
6087                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6088
6089                     return;
6090                 }
6091         }
6092
6093         bookHit = NULL;
6094         if (gameMode == TwoMachinesPlay) {
6095             /* [HGM] relaying draw offers moved to after reception of move */
6096             /* and interpreting offer as claim if it brings draw condition */
6097             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6098                 SendToProgram("draw\n", cps->other);
6099             }
6100             if (cps->other->sendTime) {
6101                 SendTimeRemaining(cps->other,
6102                                   cps->other->twoMachinesColor[0] == 'w');
6103             }
6104             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6105             if (firstMove && !bookHit) {
6106                 firstMove = FALSE;
6107                 if (cps->other->useColors) {
6108                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6109                 }
6110                 SendToProgram("go\n", cps->other);
6111             }
6112             cps->other->maybeThinking = TRUE;
6113         }
6114
6115         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6116
6117         if (!pausing && appData.ringBellAfterMoves) {
6118             RingBell();
6119         }
6120
6121         /*
6122          * Reenable menu items that were disabled while
6123          * machine was thinking
6124          */
6125         if (gameMode != TwoMachinesPlay)
6126             SetUserThinkingEnables();
6127
6128         // [HGM] book: after book hit opponent has received move and is now in force mode
6129         // force the book reply into it, and then fake that it outputted this move by jumping
6130         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6131         if(bookHit) {
6132                 static char bookMove[MSG_SIZ]; // a bit generous?
6133
6134                 strcpy(bookMove, "move ");
6135                 strcat(bookMove, bookHit);
6136                 message = bookMove;
6137                 cps = cps->other;
6138                 programStats.nodes = programStats.depth = programStats.time =
6139                 programStats.score = programStats.got_only_move = 0;
6140                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6141
6142                 if(cps->lastPing != cps->lastPong) {
6143                     savedMessage = message; // args for deferred call
6144                     savedState = cps;
6145                     ScheduleDelayedEvent(DeferredBookMove, 10);
6146                     return;
6147                 }
6148                 goto FakeBookMove;
6149         }
6150
6151         return;
6152     }
6153
6154     /* Set special modes for chess engines.  Later something general
6155      *  could be added here; for now there is just one kludge feature,
6156      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6157      *  when "xboard" is given as an interactive command.
6158      */
6159     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6160         cps->useSigint = FALSE;
6161         cps->useSigterm = FALSE;
6162     }
6163
6164     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6165      * want this, I was asked to put it in, and obliged.
6166      */
6167     if (!strncmp(message, "setboard ", 9)) {
6168         Board initial_position; int i;
6169
6170         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6171
6172         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6173             DisplayError(_("Bad FEN received from engine"), 0);
6174             return ;
6175         } else {
6176            Reset(FALSE, FALSE);
6177            CopyBoard(boards[0], initial_position);
6178            initialRulePlies = FENrulePlies;
6179            epStatus[0] = FENepStatus;
6180            for( i=0; i<nrCastlingRights; i++ )
6181                 castlingRights[0][i] = FENcastlingRights[i];
6182            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6183            else gameMode = MachinePlaysBlack;
6184            DrawPosition(FALSE, boards[currentMove]);
6185         }
6186         return;
6187     }
6188
6189     /*
6190      * Look for communication commands
6191      */
6192     if (!strncmp(message, "telluser ", 9)) {
6193         DisplayNote(message + 9);
6194         return;
6195     }
6196     if (!strncmp(message, "tellusererror ", 14)) {
6197         DisplayError(message + 14, 0);
6198         return;
6199     }
6200     if (!strncmp(message, "tellopponent ", 13)) {
6201       if (appData.icsActive) {
6202         if (loggedOn) {
6203           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6204           SendToICS(buf1);
6205         }
6206       } else {
6207         DisplayNote(message + 13);
6208       }
6209       return;
6210     }
6211     if (!strncmp(message, "tellothers ", 11)) {
6212       if (appData.icsActive) {
6213         if (loggedOn) {
6214           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6215           SendToICS(buf1);
6216         }
6217       }
6218       return;
6219     }
6220     if (!strncmp(message, "tellall ", 8)) {
6221       if (appData.icsActive) {
6222         if (loggedOn) {
6223           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6224           SendToICS(buf1);
6225         }
6226       } else {
6227         DisplayNote(message + 8);
6228       }
6229       return;
6230     }
6231     if (strncmp(message, "warning", 7) == 0) {
6232         /* Undocumented feature, use tellusererror in new code */
6233         DisplayError(message, 0);
6234         return;
6235     }
6236     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6237         strcpy(realname, cps->tidy);
6238         strcat(realname, " query");
6239         AskQuestion(realname, buf2, buf1, cps->pr);
6240         return;
6241     }
6242     /* Commands from the engine directly to ICS.  We don't allow these to be
6243      *  sent until we are logged on. Crafty kibitzes have been known to
6244      *  interfere with the login process.
6245      */
6246     if (loggedOn) {
6247         if (!strncmp(message, "tellics ", 8)) {
6248             SendToICS(message + 8);
6249             SendToICS("\n");
6250             return;
6251         }
6252         if (!strncmp(message, "tellicsnoalias ", 15)) {
6253             SendToICS(ics_prefix);
6254             SendToICS(message + 15);
6255             SendToICS("\n");
6256             return;
6257         }
6258         /* The following are for backward compatibility only */
6259         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6260             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6261             SendToICS(ics_prefix);
6262             SendToICS(message);
6263             SendToICS("\n");
6264             return;
6265         }
6266     }
6267     if (strncmp(message, "feature ", 8) == 0) {
6268       ParseFeatures(message+8, cps);
6269     }
6270     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6271         return;
6272     }
6273     /*
6274      * If the move is illegal, cancel it and redraw the board.
6275      * Also deal with other error cases.  Matching is rather loose
6276      * here to accommodate engines written before the spec.
6277      */
6278     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6279         strncmp(message, "Error", 5) == 0) {
6280         if (StrStr(message, "name") ||
6281             StrStr(message, "rating") || StrStr(message, "?") ||
6282             StrStr(message, "result") || StrStr(message, "board") ||
6283             StrStr(message, "bk") || StrStr(message, "computer") ||
6284             StrStr(message, "variant") || StrStr(message, "hint") ||
6285             StrStr(message, "random") || StrStr(message, "depth") ||
6286             StrStr(message, "accepted")) {
6287             return;
6288         }
6289         if (StrStr(message, "protover")) {
6290           /* Program is responding to input, so it's apparently done
6291              initializing, and this error message indicates it is
6292              protocol version 1.  So we don't need to wait any longer
6293              for it to initialize and send feature commands. */
6294           FeatureDone(cps, 1);
6295           cps->protocolVersion = 1;
6296           return;
6297         }
6298         cps->maybeThinking = FALSE;
6299
6300         if (StrStr(message, "draw")) {
6301             /* Program doesn't have "draw" command */
6302             cps->sendDrawOffers = 0;
6303             return;
6304         }
6305         if (cps->sendTime != 1 &&
6306             (StrStr(message, "time") || StrStr(message, "otim"))) {
6307           /* Program apparently doesn't have "time" or "otim" command */
6308           cps->sendTime = 0;
6309           return;
6310         }
6311         if (StrStr(message, "analyze")) {
6312             cps->analysisSupport = FALSE;
6313             cps->analyzing = FALSE;
6314             Reset(FALSE, TRUE);
6315             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6316             DisplayError(buf2, 0);
6317             return;
6318         }
6319         if (StrStr(message, "(no matching move)st")) {
6320           /* Special kludge for GNU Chess 4 only */
6321           cps->stKludge = TRUE;
6322           SendTimeControl(cps, movesPerSession, timeControl,
6323                           timeIncrement, appData.searchDepth,
6324                           searchTime);
6325           return;
6326         }
6327         if (StrStr(message, "(no matching move)sd")) {
6328           /* Special kludge for GNU Chess 4 only */
6329           cps->sdKludge = TRUE;
6330           SendTimeControl(cps, movesPerSession, timeControl,
6331                           timeIncrement, appData.searchDepth,
6332                           searchTime);
6333           return;
6334         }
6335         if (!StrStr(message, "llegal")) {
6336             return;
6337         }
6338         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6339             gameMode == IcsIdle) return;
6340         if (forwardMostMove <= backwardMostMove) return;
6341 #if 0
6342         /* Following removed: it caused a bug where a real illegal move
6343            message in analyze mored would be ignored. */
6344         if (cps == &first && programStats.ok_to_send == 0) {
6345             /* Bogus message from Crafty responding to "."  This filtering
6346                can miss some of the bad messages, but fortunately the bug
6347                is fixed in current Crafty versions, so it doesn't matter. */
6348             return;
6349         }
6350 #endif
6351         if (pausing) PauseEvent();
6352         if (gameMode == PlayFromGameFile) {
6353             /* Stop reading this game file */
6354             gameMode = EditGame;
6355             ModeHighlight();
6356         }
6357         currentMove = --forwardMostMove;
6358         DisplayMove(currentMove-1); /* before DisplayMoveError */
6359         SwitchClocks();
6360         DisplayBothClocks();
6361         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6362                 parseList[currentMove], cps->which);
6363         DisplayMoveError(buf1);
6364         DrawPosition(FALSE, boards[currentMove]);
6365
6366         /* [HGM] illegal-move claim should forfeit game when Xboard */
6367         /* only passes fully legal moves                            */
6368         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6369             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6370                                 "False illegal-move claim", GE_XBOARD );
6371         }
6372         return;
6373     }
6374     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6375         /* Program has a broken "time" command that
6376            outputs a string not ending in newline.
6377            Don't use it. */
6378         cps->sendTime = 0;
6379     }
6380
6381     /*
6382      * If chess program startup fails, exit with an error message.
6383      * Attempts to recover here are futile.
6384      */
6385     if ((StrStr(message, "unknown host") != NULL)
6386         || (StrStr(message, "No remote directory") != NULL)
6387         || (StrStr(message, "not found") != NULL)
6388         || (StrStr(message, "No such file") != NULL)
6389         || (StrStr(message, "can't alloc") != NULL)
6390         || (StrStr(message, "Permission denied") != NULL)) {
6391
6392         cps->maybeThinking = FALSE;
6393         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6394                 cps->which, cps->program, cps->host, message);
6395         RemoveInputSource(cps->isr);
6396         DisplayFatalError(buf1, 0, 1);
6397         return;
6398     }
6399
6400     /*
6401      * Look for hint output
6402      */
6403     if (sscanf(message, "Hint: %s", buf1) == 1) {
6404         if (cps == &first && hintRequested) {
6405             hintRequested = FALSE;
6406             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6407                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6408                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6409                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6410                                     fromY, fromX, toY, toX, promoChar, buf1);
6411                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6412                 DisplayInformation(buf2);
6413             } else {
6414                 /* Hint move could not be parsed!? */
6415               snprintf(buf2, sizeof(buf2),
6416                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6417                         buf1, cps->which);
6418                 DisplayError(buf2, 0);
6419             }
6420         } else {
6421             strcpy(lastHint, buf1);
6422         }
6423         return;
6424     }
6425
6426     /*
6427      * Ignore other messages if game is not in progress
6428      */
6429     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6430         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6431
6432     /*
6433      * look for win, lose, draw, or draw offer
6434      */
6435     if (strncmp(message, "1-0", 3) == 0) {
6436         char *p, *q, *r = "";
6437         p = strchr(message, '{');
6438         if (p) {
6439             q = strchr(p, '}');
6440             if (q) {
6441                 *q = NULLCHAR;
6442                 r = p + 1;
6443             }
6444         }
6445         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6446         return;
6447     } else if (strncmp(message, "0-1", 3) == 0) {
6448         char *p, *q, *r = "";
6449         p = strchr(message, '{');
6450         if (p) {
6451             q = strchr(p, '}');
6452             if (q) {
6453                 *q = NULLCHAR;
6454                 r = p + 1;
6455             }
6456         }
6457         /* Kludge for Arasan 4.1 bug */
6458         if (strcmp(r, "Black resigns") == 0) {
6459             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6460             return;
6461         }
6462         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6463         return;
6464     } else if (strncmp(message, "1/2", 3) == 0) {
6465         char *p, *q, *r = "";
6466         p = strchr(message, '{');
6467         if (p) {
6468             q = strchr(p, '}');
6469             if (q) {
6470                 *q = NULLCHAR;
6471                 r = p + 1;
6472             }
6473         }
6474
6475         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6476         return;
6477
6478     } else if (strncmp(message, "White resign", 12) == 0) {
6479         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6480         return;
6481     } else if (strncmp(message, "Black resign", 12) == 0) {
6482         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6483         return;
6484     } else if (strncmp(message, "White matches", 13) == 0 ||
6485                strncmp(message, "Black matches", 13) == 0   ) {
6486         /* [HGM] ignore GNUShogi noises */
6487         return;
6488     } else if (strncmp(message, "White", 5) == 0 &&
6489                message[5] != '(' &&
6490                StrStr(message, "Black") == NULL) {
6491         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6492         return;
6493     } else if (strncmp(message, "Black", 5) == 0 &&
6494                message[5] != '(') {
6495         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6496         return;
6497     } else if (strcmp(message, "resign") == 0 ||
6498                strcmp(message, "computer resigns") == 0) {
6499         switch (gameMode) {
6500           case MachinePlaysBlack:
6501           case IcsPlayingBlack:
6502             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6503             break;
6504           case MachinePlaysWhite:
6505           case IcsPlayingWhite:
6506             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6507             break;
6508           case TwoMachinesPlay:
6509             if (cps->twoMachinesColor[0] == 'w')
6510               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6511             else
6512               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6513             break;
6514           default:
6515             /* can't happen */
6516             break;
6517         }
6518         return;
6519     } else if (strncmp(message, "opponent mates", 14) == 0) {
6520         switch (gameMode) {
6521           case MachinePlaysBlack:
6522           case IcsPlayingBlack:
6523             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6524             break;
6525           case MachinePlaysWhite:
6526           case IcsPlayingWhite:
6527             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6528             break;
6529           case TwoMachinesPlay:
6530             if (cps->twoMachinesColor[0] == 'w')
6531               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6532             else
6533               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6534             break;
6535           default:
6536             /* can't happen */
6537             break;
6538         }
6539         return;
6540     } else if (strncmp(message, "computer mates", 14) == 0) {
6541         switch (gameMode) {
6542           case MachinePlaysBlack:
6543           case IcsPlayingBlack:
6544             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6545             break;
6546           case MachinePlaysWhite:
6547           case IcsPlayingWhite:
6548             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6549             break;
6550           case TwoMachinesPlay:
6551             if (cps->twoMachinesColor[0] == 'w')
6552               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6553             else
6554               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6555             break;
6556           default:
6557             /* can't happen */
6558             break;
6559         }
6560         return;
6561     } else if (strncmp(message, "checkmate", 9) == 0) {
6562         if (WhiteOnMove(forwardMostMove)) {
6563             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6564         } else {
6565             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6566         }
6567         return;
6568     } else if (strstr(message, "Draw") != NULL ||
6569                strstr(message, "game is a draw") != NULL) {
6570         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6571         return;
6572     } else if (strstr(message, "offer") != NULL &&
6573                strstr(message, "draw") != NULL) {
6574 #if ZIPPY
6575         if (appData.zippyPlay && first.initDone) {
6576             /* Relay offer to ICS */
6577             SendToICS(ics_prefix);
6578             SendToICS("draw\n");
6579         }
6580 #endif
6581         cps->offeredDraw = 2; /* valid until this engine moves twice */
6582         if (gameMode == TwoMachinesPlay) {
6583             if (cps->other->offeredDraw) {
6584                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6585             /* [HGM] in two-machine mode we delay relaying draw offer      */
6586             /* until after we also have move, to see if it is really claim */
6587             }
6588 #if 0
6589               else {
6590                 if (cps->other->sendDrawOffers) {
6591                     SendToProgram("draw\n", cps->other);
6592                 }
6593             }
6594 #endif
6595         } else if (gameMode == MachinePlaysWhite ||
6596                    gameMode == MachinePlaysBlack) {
6597           if (userOfferedDraw) {
6598             DisplayInformation(_("Machine accepts your draw offer"));
6599             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6600           } else {
6601             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6602           }
6603         }
6604     }
6605
6606
6607     /*
6608      * Look for thinking output
6609      */
6610     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6611           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6612                                 ) {
6613         int plylev, mvleft, mvtot, curscore, time;
6614         char mvname[MOVE_LEN];
6615         u64 nodes; // [DM]
6616         char plyext;
6617         int ignore = FALSE;
6618         int prefixHint = FALSE;
6619         mvname[0] = NULLCHAR;
6620
6621         switch (gameMode) {
6622           case MachinePlaysBlack:
6623           case IcsPlayingBlack:
6624             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6625             break;
6626           case MachinePlaysWhite:
6627           case IcsPlayingWhite:
6628             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6629             break;
6630           case AnalyzeMode:
6631           case AnalyzeFile:
6632             break;
6633           case IcsObserving: /* [DM] icsEngineAnalyze */
6634             if (!appData.icsEngineAnalyze) ignore = TRUE;
6635             break;
6636           case TwoMachinesPlay:
6637             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6638                 ignore = TRUE;
6639             }
6640             break;
6641           default:
6642             ignore = TRUE;
6643             break;
6644         }
6645
6646         if (!ignore) {
6647             buf1[0] = NULLCHAR;
6648             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6649                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6650
6651                 if (plyext != ' ' && plyext != '\t') {
6652                     time *= 100;
6653                 }
6654
6655                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6656                 if( cps->scoreIsAbsolute &&
6657                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6658                 {
6659                     curscore = -curscore;
6660                 }
6661
6662
6663                 programStats.depth = plylev;
6664                 programStats.nodes = nodes;
6665                 programStats.time = time;
6666                 programStats.score = curscore;
6667                 programStats.got_only_move = 0;
6668
6669                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6670                         int ticklen;
6671
6672                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6673                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6674                         if(WhiteOnMove(forwardMostMove))
6675                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6676                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6677                 }
6678
6679                 /* Buffer overflow protection */
6680                 if (buf1[0] != NULLCHAR) {
6681                     if (strlen(buf1) >= sizeof(programStats.movelist)
6682                         && appData.debugMode) {
6683                         fprintf(debugFP,
6684                                 "PV is too long; using the first %d bytes.\n",
6685                                 sizeof(programStats.movelist) - 1);
6686                     }
6687
6688                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6689                 } else {
6690                     sprintf(programStats.movelist, " no PV\n");
6691                 }
6692
6693                 if (programStats.seen_stat) {
6694                     programStats.ok_to_send = 1;
6695                 }
6696
6697                 if (strchr(programStats.movelist, '(') != NULL) {
6698                     programStats.line_is_book = 1;
6699                     programStats.nr_moves = 0;
6700                     programStats.moves_left = 0;
6701                 } else {
6702                     programStats.line_is_book = 0;
6703                 }
6704
6705                 SendProgramStatsToFrontend( cps, &programStats );
6706
6707                 /*
6708                     [AS] Protect the thinkOutput buffer from overflow... this
6709                     is only useful if buf1 hasn't overflowed first!
6710                 */
6711                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6712                         plylev,
6713                         (gameMode == TwoMachinesPlay ?
6714                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6715                         ((double) curscore) / 100.0,
6716                         prefixHint ? lastHint : "",
6717                         prefixHint ? " " : "" );
6718
6719                 if( buf1[0] != NULLCHAR ) {
6720                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6721
6722                     if( strlen(buf1) > max_len ) {
6723                         if( appData.debugMode) {
6724                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6725                         }
6726                         buf1[max_len+1] = '\0';
6727                     }
6728
6729                     strcat( thinkOutput, buf1 );
6730                 }
6731
6732                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6733                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6734                     DisplayMove(currentMove - 1);
6735                     DisplayAnalysis();
6736                 }
6737                 return;
6738
6739             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6740                 /* crafty (9.25+) says "(only move) <move>"
6741                  * if there is only 1 legal move
6742                  */
6743                 sscanf(p, "(only move) %s", buf1);
6744                 sprintf(thinkOutput, "%s (only move)", buf1);
6745                 sprintf(programStats.movelist, "%s (only move)", buf1);
6746                 programStats.depth = 1;
6747                 programStats.nr_moves = 1;
6748                 programStats.moves_left = 1;
6749                 programStats.nodes = 1;
6750                 programStats.time = 1;
6751                 programStats.got_only_move = 1;
6752
6753                 /* Not really, but we also use this member to
6754                    mean "line isn't going to change" (Crafty
6755                    isn't searching, so stats won't change) */
6756                 programStats.line_is_book = 1;
6757
6758                 SendProgramStatsToFrontend( cps, &programStats );
6759
6760                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6761                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6762                     DisplayMove(currentMove - 1);
6763                     DisplayAnalysis();
6764                 }
6765                 return;
6766             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6767                               &time, &nodes, &plylev, &mvleft,
6768                               &mvtot, mvname) >= 5) {
6769                 /* The stat01: line is from Crafty (9.29+) in response
6770                    to the "." command */
6771                 programStats.seen_stat = 1;
6772                 cps->maybeThinking = TRUE;
6773
6774                 if (programStats.got_only_move || !appData.periodicUpdates)
6775                   return;
6776
6777                 programStats.depth = plylev;
6778                 programStats.time = time;
6779                 programStats.nodes = nodes;
6780                 programStats.moves_left = mvleft;
6781                 programStats.nr_moves = mvtot;
6782                 strcpy(programStats.move_name, mvname);
6783                 programStats.ok_to_send = 1;
6784                 programStats.movelist[0] = '\0';
6785
6786                 SendProgramStatsToFrontend( cps, &programStats );
6787
6788                 DisplayAnalysis();
6789                 return;
6790
6791             } else if (strncmp(message,"++",2) == 0) {
6792                 /* Crafty 9.29+ outputs this */
6793                 programStats.got_fail = 2;
6794                 return;
6795
6796             } else if (strncmp(message,"--",2) == 0) {
6797                 /* Crafty 9.29+ outputs this */
6798                 programStats.got_fail = 1;
6799                 return;
6800
6801             } else if (thinkOutput[0] != NULLCHAR &&
6802                        strncmp(message, "    ", 4) == 0) {
6803                 unsigned message_len;
6804
6805                 p = message;
6806                 while (*p && *p == ' ') p++;
6807
6808                 message_len = strlen( p );
6809
6810                 /* [AS] Avoid buffer overflow */
6811                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6812                     strcat(thinkOutput, " ");
6813                     strcat(thinkOutput, p);
6814                 }
6815
6816                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6817                     strcat(programStats.movelist, " ");
6818                     strcat(programStats.movelist, p);
6819                 }
6820
6821                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6822                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6823                     DisplayMove(currentMove - 1);
6824                     DisplayAnalysis();
6825                 }
6826                 return;
6827             }
6828         }
6829         else {
6830             buf1[0] = NULLCHAR;
6831
6832             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6833                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6834             {
6835                 ChessProgramStats cpstats;
6836
6837                 if (plyext != ' ' && plyext != '\t') {
6838                     time *= 100;
6839                 }
6840
6841                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6842                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6843                     curscore = -curscore;
6844                 }
6845
6846                 cpstats.depth = plylev;
6847                 cpstats.nodes = nodes;
6848                 cpstats.time = time;
6849                 cpstats.score = curscore;
6850                 cpstats.got_only_move = 0;
6851                 cpstats.movelist[0] = '\0';
6852
6853                 if (buf1[0] != NULLCHAR) {
6854                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6855                 }
6856
6857                 cpstats.ok_to_send = 0;
6858                 cpstats.line_is_book = 0;
6859                 cpstats.nr_moves = 0;
6860                 cpstats.moves_left = 0;
6861
6862                 SendProgramStatsToFrontend( cps, &cpstats );
6863             }
6864         }
6865     }
6866 }
6867
6868
6869 /* Parse a game score from the character string "game", and
6870    record it as the history of the current game.  The game
6871    score is NOT assumed to start from the standard position.
6872    The display is not updated in any way.
6873    */
6874 void
6875 ParseGameHistory(game)
6876      char *game;
6877 {
6878     ChessMove moveType;
6879     int fromX, fromY, toX, toY, boardIndex;
6880     char promoChar;
6881     char *p, *q;
6882     char buf[MSG_SIZ];
6883
6884     if (appData.debugMode)
6885       fprintf(debugFP, "Parsing game history: %s\n", game);
6886
6887     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6888     gameInfo.site = StrSave(appData.icsHost);
6889     gameInfo.date = PGNDate();
6890     gameInfo.round = StrSave("-");
6891
6892     /* Parse out names of players */
6893     while (*game == ' ') game++;
6894     p = buf;
6895     while (*game != ' ') *p++ = *game++;
6896     *p = NULLCHAR;
6897     gameInfo.white = StrSave(buf);
6898     while (*game == ' ') game++;
6899     p = buf;
6900     while (*game != ' ' && *game != '\n') *p++ = *game++;
6901     *p = NULLCHAR;
6902     gameInfo.black = StrSave(buf);
6903
6904     /* Parse moves */
6905     boardIndex = blackPlaysFirst ? 1 : 0;
6906     yynewstr(game);
6907     for (;;) {
6908         yyboardindex = boardIndex;
6909         moveType = (ChessMove) yylex();
6910         switch (moveType) {
6911           case IllegalMove:             /* maybe suicide chess, etc. */
6912   if (appData.debugMode) {
6913     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6914     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6915     setbuf(debugFP, NULL);
6916   }
6917           case WhitePromotionChancellor:
6918           case BlackPromotionChancellor:
6919           case WhitePromotionArchbishop:
6920           case BlackPromotionArchbishop:
6921           case WhitePromotionQueen:
6922           case BlackPromotionQueen:
6923           case WhitePromotionRook:
6924           case BlackPromotionRook:
6925           case WhitePromotionBishop:
6926           case BlackPromotionBishop:
6927           case WhitePromotionKnight:
6928           case BlackPromotionKnight:
6929           case WhitePromotionKing:
6930           case BlackPromotionKing:
6931           case NormalMove:
6932           case WhiteCapturesEnPassant:
6933           case BlackCapturesEnPassant:
6934           case WhiteKingSideCastle:
6935           case WhiteQueenSideCastle:
6936           case BlackKingSideCastle:
6937           case BlackQueenSideCastle:
6938           case WhiteKingSideCastleWild:
6939           case WhiteQueenSideCastleWild:
6940           case BlackKingSideCastleWild:
6941           case BlackQueenSideCastleWild:
6942           /* PUSH Fabien */
6943           case WhiteHSideCastleFR:
6944           case WhiteASideCastleFR:
6945           case BlackHSideCastleFR:
6946           case BlackASideCastleFR:
6947           /* POP Fabien */
6948             fromX = currentMoveString[0] - AAA;
6949             fromY = currentMoveString[1] - ONE;
6950             toX = currentMoveString[2] - AAA;
6951             toY = currentMoveString[3] - ONE;
6952             promoChar = currentMoveString[4];
6953             break;
6954           case WhiteDrop:
6955           case BlackDrop:
6956             fromX = moveType == WhiteDrop ?
6957               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6958             (int) CharToPiece(ToLower(currentMoveString[0]));
6959             fromY = DROP_RANK;
6960             toX = currentMoveString[2] - AAA;
6961             toY = currentMoveString[3] - ONE;
6962             promoChar = NULLCHAR;
6963             break;
6964           case AmbiguousMove:
6965             /* bug? */
6966             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6967   if (appData.debugMode) {
6968     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6969     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6970     setbuf(debugFP, NULL);
6971   }
6972             DisplayError(buf, 0);
6973             return;
6974           case ImpossibleMove:
6975             /* bug? */
6976             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6977   if (appData.debugMode) {
6978     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6979     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6980     setbuf(debugFP, NULL);
6981   }
6982             DisplayError(buf, 0);
6983             return;
6984           case (ChessMove) 0:   /* end of file */
6985             if (boardIndex < backwardMostMove) {
6986                 /* Oops, gap.  How did that happen? */
6987                 DisplayError(_("Gap in move list"), 0);
6988                 return;
6989             }
6990             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6991             if (boardIndex > forwardMostMove) {
6992                 forwardMostMove = boardIndex;
6993             }
6994             return;
6995           case ElapsedTime:
6996             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6997                 strcat(parseList[boardIndex-1], " ");
6998                 strcat(parseList[boardIndex-1], yy_text);
6999             }
7000             continue;
7001           case Comment:
7002           case PGNTag:
7003           case NAG:
7004           default:
7005             /* ignore */
7006             continue;
7007           case WhiteWins:
7008           case BlackWins:
7009           case GameIsDrawn:
7010           case GameUnfinished:
7011             if (gameMode == IcsExamining) {
7012                 if (boardIndex < backwardMostMove) {
7013                     /* Oops, gap.  How did that happen? */
7014                     return;
7015                 }
7016                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7017                 return;
7018             }
7019             gameInfo.result = moveType;
7020             p = strchr(yy_text, '{');
7021             if (p == NULL) p = strchr(yy_text, '(');
7022             if (p == NULL) {
7023                 p = yy_text;
7024                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7025             } else {
7026                 q = strchr(p, *p == '{' ? '}' : ')');
7027                 if (q != NULL) *q = NULLCHAR;
7028                 p++;
7029             }
7030             gameInfo.resultDetails = StrSave(p);
7031             continue;
7032         }
7033         if (boardIndex >= forwardMostMove &&
7034             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7035             backwardMostMove = blackPlaysFirst ? 1 : 0;
7036             return;
7037         }
7038         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7039                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7040                                  parseList[boardIndex]);
7041         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7042         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7043         /* currentMoveString is set as a side-effect of yylex */
7044         strcpy(moveList[boardIndex], currentMoveString);
7045         strcat(moveList[boardIndex], "\n");
7046         boardIndex++;
7047         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7048                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7049         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7050                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7051           case MT_NONE:
7052           case MT_STALEMATE:
7053           default:
7054             break;
7055           case MT_CHECK:
7056             if(gameInfo.variant != VariantShogi)
7057                 strcat(parseList[boardIndex - 1], "+");
7058             break;
7059           case MT_CHECKMATE:
7060           case MT_STAINMATE:
7061             strcat(parseList[boardIndex - 1], "#");
7062             break;
7063         }
7064     }
7065 }
7066
7067
7068 /* Apply a move to the given board  */
7069 void
7070 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7071      int fromX, fromY, toX, toY;
7072      int promoChar;
7073      Board board;
7074      char *castling;
7075      char *ep;
7076 {
7077   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7078
7079     /* [HGM] compute & store e.p. status and castling rights for new position */
7080     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7081     { int i;
7082
7083       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7084       oldEP = *ep;
7085       *ep = EP_NONE;
7086
7087       if( board[toY][toX] != EmptySquare )
7088            *ep = EP_CAPTURE;
7089
7090       if( board[fromY][fromX] == WhitePawn ) {
7091            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7092                *ep = EP_PAWN_MOVE;
7093            if( toY-fromY==2) {
7094                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7095                         gameInfo.variant != VariantBerolina || toX < fromX)
7096                       *ep = toX | berolina;
7097                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7098                         gameInfo.variant != VariantBerolina || toX > fromX)
7099                       *ep = toX;
7100            }
7101       } else
7102       if( board[fromY][fromX] == BlackPawn ) {
7103            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7104                *ep = EP_PAWN_MOVE;
7105            if( toY-fromY== -2) {
7106                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7107                         gameInfo.variant != VariantBerolina || toX < fromX)
7108                       *ep = toX | berolina;
7109                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7110                         gameInfo.variant != VariantBerolina || toX > fromX)
7111                       *ep = toX;
7112            }
7113        }
7114
7115        for(i=0; i<nrCastlingRights; i++) {
7116            if(castling[i] == fromX && castlingRank[i] == fromY ||
7117               castling[i] == toX   && castlingRank[i] == toY
7118              ) castling[i] = -1; // revoke for moved or captured piece
7119        }
7120
7121     }
7122
7123   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7124   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7125        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7126
7127   if (fromX == toX && fromY == toY) return;
7128
7129   if (fromY == DROP_RANK) {
7130         /* must be first */
7131         piece = board[toY][toX] = (ChessSquare) fromX;
7132   } else {
7133      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7134      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7135      if(gameInfo.variant == VariantKnightmate)
7136          king += (int) WhiteUnicorn - (int) WhiteKing;
7137
7138     /* Code added by Tord: */
7139     /* FRC castling assumed when king captures friendly rook. */
7140     if (board[fromY][fromX] == WhiteKing &&
7141              board[toY][toX] == WhiteRook) {
7142       board[fromY][fromX] = EmptySquare;
7143       board[toY][toX] = EmptySquare;
7144       if(toX > fromX) {
7145         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7146       } else {
7147         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7148       }
7149     } else if (board[fromY][fromX] == BlackKing &&
7150                board[toY][toX] == BlackRook) {
7151       board[fromY][fromX] = EmptySquare;
7152       board[toY][toX] = EmptySquare;
7153       if(toX > fromX) {
7154         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7155       } else {
7156         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7157       }
7158     /* End of code added by Tord */
7159
7160     } else if (board[fromY][fromX] == king
7161         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7162         && toY == fromY && toX > fromX+1) {
7163         board[fromY][fromX] = EmptySquare;
7164         board[toY][toX] = king;
7165         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7166         board[fromY][BOARD_RGHT-1] = EmptySquare;
7167     } else if (board[fromY][fromX] == king
7168         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7169                && toY == fromY && toX < fromX-1) {
7170         board[fromY][fromX] = EmptySquare;
7171         board[toY][toX] = king;
7172         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7173         board[fromY][BOARD_LEFT] = EmptySquare;
7174     } else if (board[fromY][fromX] == WhitePawn
7175                && toY == BOARD_HEIGHT-1
7176                && gameInfo.variant != VariantXiangqi
7177                ) {
7178         /* white pawn promotion */
7179         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7180         if (board[toY][toX] == EmptySquare) {
7181             board[toY][toX] = WhiteQueen;
7182         }
7183         if(gameInfo.variant==VariantBughouse ||
7184            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7185             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7186         board[fromY][fromX] = EmptySquare;
7187     } else if ((fromY == BOARD_HEIGHT-4)
7188                && (toX != fromX)
7189                && gameInfo.variant != VariantXiangqi
7190                && gameInfo.variant != VariantBerolina
7191                && (board[fromY][fromX] == WhitePawn)
7192                && (board[toY][toX] == EmptySquare)) {
7193         board[fromY][fromX] = EmptySquare;
7194         board[toY][toX] = WhitePawn;
7195         captured = board[toY - 1][toX];
7196         board[toY - 1][toX] = EmptySquare;
7197     } else if ((fromY == BOARD_HEIGHT-4)
7198                && (toX == fromX)
7199                && gameInfo.variant == VariantBerolina
7200                && (board[fromY][fromX] == WhitePawn)
7201                && (board[toY][toX] == EmptySquare)) {
7202         board[fromY][fromX] = EmptySquare;
7203         board[toY][toX] = WhitePawn;
7204         if(oldEP & EP_BEROLIN_A) {
7205                 captured = board[fromY][fromX-1];
7206                 board[fromY][fromX-1] = EmptySquare;
7207         }else{  captured = board[fromY][fromX+1];
7208                 board[fromY][fromX+1] = EmptySquare;
7209         }
7210     } else if (board[fromY][fromX] == king
7211         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7212                && toY == fromY && toX > fromX+1) {
7213         board[fromY][fromX] = EmptySquare;
7214         board[toY][toX] = king;
7215         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7216         board[fromY][BOARD_RGHT-1] = EmptySquare;
7217     } else if (board[fromY][fromX] == king
7218         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7219                && toY == fromY && toX < fromX-1) {
7220         board[fromY][fromX] = EmptySquare;
7221         board[toY][toX] = king;
7222         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7223         board[fromY][BOARD_LEFT] = EmptySquare;
7224     } else if (fromY == 7 && fromX == 3
7225                && board[fromY][fromX] == BlackKing
7226                && toY == 7 && toX == 5) {
7227         board[fromY][fromX] = EmptySquare;
7228         board[toY][toX] = BlackKing;
7229         board[fromY][7] = EmptySquare;
7230         board[toY][4] = BlackRook;
7231     } else if (fromY == 7 && fromX == 3
7232                && board[fromY][fromX] == BlackKing
7233                && toY == 7 && toX == 1) {
7234         board[fromY][fromX] = EmptySquare;
7235         board[toY][toX] = BlackKing;
7236         board[fromY][0] = EmptySquare;
7237         board[toY][2] = BlackRook;
7238     } else if (board[fromY][fromX] == BlackPawn
7239                && toY == 0
7240                && gameInfo.variant != VariantXiangqi
7241                ) {
7242         /* black pawn promotion */
7243         board[0][toX] = CharToPiece(ToLower(promoChar));
7244         if (board[0][toX] == EmptySquare) {
7245             board[0][toX] = BlackQueen;
7246         }
7247         if(gameInfo.variant==VariantBughouse ||
7248            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7249             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7250         board[fromY][fromX] = EmptySquare;
7251     } else if ((fromY == 3)
7252                && (toX != fromX)
7253                && gameInfo.variant != VariantXiangqi
7254                && gameInfo.variant != VariantBerolina
7255                && (board[fromY][fromX] == BlackPawn)
7256                && (board[toY][toX] == EmptySquare)) {
7257         board[fromY][fromX] = EmptySquare;
7258         board[toY][toX] = BlackPawn;
7259         captured = board[toY + 1][toX];
7260         board[toY + 1][toX] = EmptySquare;
7261     } else if ((fromY == 3)
7262                && (toX == fromX)
7263                && gameInfo.variant == VariantBerolina
7264                && (board[fromY][fromX] == BlackPawn)
7265                && (board[toY][toX] == EmptySquare)) {
7266         board[fromY][fromX] = EmptySquare;
7267         board[toY][toX] = BlackPawn;
7268         if(oldEP & EP_BEROLIN_A) {
7269                 captured = board[fromY][fromX-1];
7270                 board[fromY][fromX-1] = EmptySquare;
7271         }else{  captured = board[fromY][fromX+1];
7272                 board[fromY][fromX+1] = EmptySquare;
7273         }
7274     } else {
7275         board[toY][toX] = board[fromY][fromX];
7276         board[fromY][fromX] = EmptySquare;
7277     }
7278
7279     /* [HGM] now we promote for Shogi, if needed */
7280     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7281         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7282   }
7283
7284     if (gameInfo.holdingsWidth != 0) {
7285
7286       /* !!A lot more code needs to be written to support holdings  */
7287       /* [HGM] OK, so I have written it. Holdings are stored in the */
7288       /* penultimate board files, so they are automaticlly stored   */
7289       /* in the game history.                                       */
7290       if (fromY == DROP_RANK) {
7291         /* Delete from holdings, by decreasing count */
7292         /* and erasing image if necessary            */
7293         p = (int) fromX;
7294         if(p < (int) BlackPawn) { /* white drop */
7295              p -= (int)WhitePawn;
7296              if(p >= gameInfo.holdingsSize) p = 0;
7297              if(--board[p][BOARD_WIDTH-2] == 0)
7298                   board[p][BOARD_WIDTH-1] = EmptySquare;
7299         } else {                  /* black drop */
7300              p -= (int)BlackPawn;
7301              if(p >= gameInfo.holdingsSize) p = 0;
7302              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7303                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7304         }
7305       }
7306       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7307           && gameInfo.variant != VariantBughouse        ) {
7308         /* [HGM] holdings: Add to holdings, if holdings exist */
7309         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7310                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7311                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7312         }
7313         p = (int) captured;
7314         if (p >= (int) BlackPawn) {
7315           p -= (int)BlackPawn;
7316           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7317                   /* in Shogi restore piece to its original  first */
7318                   captured = (ChessSquare) (DEMOTED captured);
7319                   p = DEMOTED p;
7320           }
7321           p = PieceToNumber((ChessSquare)p);
7322           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7323           board[p][BOARD_WIDTH-2]++;
7324           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7325         } else {
7326           p -= (int)WhitePawn;
7327           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7328                   captured = (ChessSquare) (DEMOTED captured);
7329                   p = DEMOTED p;
7330           }
7331           p = PieceToNumber((ChessSquare)p);
7332           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7333           board[BOARD_HEIGHT-1-p][1]++;
7334           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7335         }
7336       }
7337
7338     } else if (gameInfo.variant == VariantAtomic) {
7339       if (captured != EmptySquare) {
7340         int y, x;
7341         for (y = toY-1; y <= toY+1; y++) {
7342           for (x = toX-1; x <= toX+1; x++) {
7343             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7344                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7345               board[y][x] = EmptySquare;
7346             }
7347           }
7348         }
7349         board[toY][toX] = EmptySquare;
7350       }
7351     }
7352     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7353         /* [HGM] Shogi promotions */
7354         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7355     }
7356
7357     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7358                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7359         // [HGM] superchess: take promotion piece out of holdings
7360         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7361         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7362             if(!--board[k][BOARD_WIDTH-2])
7363                 board[k][BOARD_WIDTH-1] = EmptySquare;
7364         } else {
7365             if(!--board[BOARD_HEIGHT-1-k][1])
7366                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7367         }
7368     }
7369
7370 }
7371
7372 /* Updates forwardMostMove */
7373 void
7374 MakeMove(fromX, fromY, toX, toY, promoChar)
7375      int fromX, fromY, toX, toY;
7376      int promoChar;
7377 {
7378 //    forwardMostMove++; // [HGM] bare: moved downstream
7379
7380     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7381         int timeLeft; static int lastLoadFlag=0; int king, piece;
7382         piece = boards[forwardMostMove][fromY][fromX];
7383         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7384         if(gameInfo.variant == VariantKnightmate)
7385             king += (int) WhiteUnicorn - (int) WhiteKing;
7386         if(forwardMostMove == 0) {
7387             if(blackPlaysFirst)
7388                 fprintf(serverMoves, "%s;", second.tidy);
7389             fprintf(serverMoves, "%s;", first.tidy);
7390             if(!blackPlaysFirst)
7391                 fprintf(serverMoves, "%s;", second.tidy);
7392         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7393         lastLoadFlag = loadFlag;
7394         // print base move
7395         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7396         // print castling suffix
7397         if( toY == fromY && piece == king ) {
7398             if(toX-fromX > 1)
7399                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7400             if(fromX-toX >1)
7401                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7402         }
7403         // e.p. suffix
7404         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7405              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7406              boards[forwardMostMove][toY][toX] == EmptySquare
7407              && fromX != toX )
7408                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7409         // promotion suffix
7410         if(promoChar != NULLCHAR)
7411                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7412         if(!loadFlag) {
7413             fprintf(serverMoves, "/%d/%d",
7414                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7415             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7416             else                      timeLeft = blackTimeRemaining/1000;
7417             fprintf(serverMoves, "/%d", timeLeft);
7418         }
7419         fflush(serverMoves);
7420     }
7421
7422     if (forwardMostMove+1 >= MAX_MOVES) {
7423       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7424                         0, 1);
7425       return;
7426     }
7427     SwitchClocks();
7428     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7429     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7430     if (commentList[forwardMostMove+1] != NULL) {
7431         free(commentList[forwardMostMove+1]);
7432         commentList[forwardMostMove+1] = NULL;
7433     }
7434     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7435     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7436     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7437                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7438     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7439     gameInfo.result = GameUnfinished;
7440     if (gameInfo.resultDetails != NULL) {
7441         free(gameInfo.resultDetails);
7442         gameInfo.resultDetails = NULL;
7443     }
7444     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7445                               moveList[forwardMostMove - 1]);
7446     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7447                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7448                              fromY, fromX, toY, toX, promoChar,
7449                              parseList[forwardMostMove - 1]);
7450     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7451                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7452                             castlingRights[forwardMostMove]) ) {
7453       case MT_NONE:
7454       case MT_STALEMATE:
7455       default:
7456         break;
7457       case MT_CHECK:
7458         if(gameInfo.variant != VariantShogi)
7459             strcat(parseList[forwardMostMove - 1], "+");
7460         break;
7461       case MT_CHECKMATE:
7462       case MT_STAINMATE:
7463         strcat(parseList[forwardMostMove - 1], "#");
7464         break;
7465     }
7466     if (appData.debugMode) {
7467         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7468     }
7469
7470 }
7471
7472 /* Updates currentMove if not pausing */
7473 void
7474 ShowMove(fromX, fromY, toX, toY)
7475 {
7476     int instant = (gameMode == PlayFromGameFile) ?
7477         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7478
7479     if(appData.noGUI) return;
7480
7481     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7482       {
7483         if (!instant)
7484           {
7485             if (forwardMostMove == currentMove + 1)
7486               {
7487 //TODO
7488 //              AnimateMove(boards[forwardMostMove - 1],
7489 //                          fromX, fromY, toX, toY);
7490               }
7491             if (appData.highlightLastMove)
7492               {
7493                 SetHighlights(fromX, fromY, toX, toY);
7494               }
7495           }
7496         currentMove = forwardMostMove;
7497     }
7498
7499     if (instant) return;
7500
7501     DisplayMove(currentMove - 1);
7502     DrawPosition(FALSE, boards[currentMove]);
7503     DisplayBothClocks();
7504     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7505
7506     return;
7507 }
7508
7509 void SendEgtPath(ChessProgramState *cps)
7510 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7511         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7512
7513         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7514
7515         while(*p) {
7516             char c, *q = name+1, *r, *s;
7517
7518             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7519             while(*p && *p != ',') *q++ = *p++;
7520             *q++ = ':'; *q = 0;
7521             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7522                 strcmp(name, ",nalimov:") == 0 ) {
7523                 // take nalimov path from the menu-changeable option first, if it is defined
7524                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7525                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7526             } else
7527             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7528                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7529                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7530                 s = r = StrStr(s, ":") + 1; // beginning of path info
7531                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7532                 c = *r; *r = 0;             // temporarily null-terminate path info
7533                     *--q = 0;               // strip of trailig ':' from name
7534                     sprintf(buf, "egtbpath %s %s\n", name+1, s);
7535                 *r = c;
7536                 SendToProgram(buf,cps);     // send egtbpath command for this format
7537             }
7538             if(*p == ',') p++; // read away comma to position for next format name
7539         }
7540 }
7541
7542 void
7543 InitChessProgram(cps, setup)
7544      ChessProgramState *cps;
7545      int setup; /* [HGM] needed to setup FRC opening position */
7546 {
7547     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7548     if (appData.noChessProgram) return;
7549     hintRequested = FALSE;
7550     bookRequested = FALSE;
7551
7552     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7553     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7554     if(cps->memSize) { /* [HGM] memory */
7555         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7556         SendToProgram(buf, cps);
7557     }
7558     SendEgtPath(cps); /* [HGM] EGT */
7559     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7560         sprintf(buf, "cores %d\n", appData.smpCores);
7561         SendToProgram(buf, cps);
7562     }
7563
7564     SendToProgram(cps->initString, cps);
7565     if (gameInfo.variant != VariantNormal &&
7566         gameInfo.variant != VariantLoadable
7567         /* [HGM] also send variant if board size non-standard */
7568         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7569                                             ) {
7570       char *v = VariantName(gameInfo.variant);
7571       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7572         /* [HGM] in protocol 1 we have to assume all variants valid */
7573         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7574         DisplayFatalError(buf, 0, 1);
7575         return;
7576       }
7577
7578       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7579       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7580       if( gameInfo.variant == VariantXiangqi )
7581            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7582       if( gameInfo.variant == VariantShogi )
7583            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7584       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7585            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7586       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7587                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7588            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7589       if( gameInfo.variant == VariantCourier )
7590            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7591       if( gameInfo.variant == VariantSuper )
7592            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7593       if( gameInfo.variant == VariantGreat )
7594            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7595
7596       if(overruled) {
7597            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7598                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7599            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7600            if(StrStr(cps->variants, b) == NULL) {
7601                // specific sized variant not known, check if general sizing allowed
7602                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7603                    if(StrStr(cps->variants, "boardsize") == NULL) {
7604                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7605                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7606                        DisplayFatalError(buf, 0, 1);
7607                        return;
7608                    }
7609                    /* [HGM] here we really should compare with the maximum supported board size */
7610                }
7611            }
7612       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7613       sprintf(buf, "variant %s\n", b);
7614       SendToProgram(buf, cps);
7615     }
7616     currentlyInitializedVariant = gameInfo.variant;
7617
7618     /* [HGM] send opening position in FRC to first engine */
7619     if(setup) {
7620           SendToProgram("force\n", cps);
7621           SendBoard(cps, 0);
7622           /* engine is now in force mode! Set flag to wake it up after first move. */
7623           setboardSpoiledMachineBlack = 1;
7624     }
7625
7626     if (cps->sendICS) {
7627       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7628       SendToProgram(buf, cps);
7629     }
7630     cps->maybeThinking = FALSE;
7631     cps->offeredDraw = 0;
7632     if (!appData.icsActive) {
7633         SendTimeControl(cps, movesPerSession, timeControl,
7634                         timeIncrement, appData.searchDepth,
7635                         searchTime);
7636     }
7637     if (appData.showThinking
7638         // [HGM] thinking: four options require thinking output to be sent
7639         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7640                                 ) {
7641         SendToProgram("post\n", cps);
7642     }
7643     SendToProgram("hard\n", cps);
7644     if (!appData.ponderNextMove) {
7645         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7646            it without being sure what state we are in first.  "hard"
7647            is not a toggle, so that one is OK.
7648          */
7649         SendToProgram("easy\n", cps);
7650     }
7651     if (cps->usePing) {
7652       sprintf(buf, "ping %d\n", ++cps->lastPing);
7653       SendToProgram(buf, cps);
7654     }
7655     cps->initDone = TRUE;
7656 }
7657
7658
7659 void
7660 StartChessProgram(cps)
7661      ChessProgramState *cps;
7662 {
7663     char buf[MSG_SIZ];
7664     int err;
7665
7666     if (appData.noChessProgram) return;
7667     cps->initDone = FALSE;
7668
7669     if (strcmp(cps->host, "localhost") == 0) {
7670         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7671     } else if (*appData.remoteShell == NULLCHAR) {
7672         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7673     } else {
7674         if (*appData.remoteUser == NULLCHAR) {
7675           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7676                     cps->program);
7677         } else {
7678           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7679                     cps->host, appData.remoteUser, cps->program);
7680         }
7681         err = StartChildProcess(buf, "", &cps->pr);
7682     }
7683
7684     if (err != 0) {
7685         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7686         DisplayFatalError(buf, err, 1);
7687         cps->pr = NoProc;
7688         cps->isr = NULL;
7689         return;
7690     }
7691
7692     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7693     if (cps->protocolVersion > 1) {
7694       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7695       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7696       cps->comboCnt = 0;  //                and values of combo boxes
7697       SendToProgram(buf, cps);
7698     } else {
7699       SendToProgram("xboard\n", cps);
7700     }
7701 }
7702
7703
7704 void
7705 TwoMachinesEventIfReady P((void))
7706 {
7707   if (first.lastPing != first.lastPong) {
7708     DisplayMessage("", _("Waiting for first chess program"));
7709     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7710     return;
7711   }
7712   if (second.lastPing != second.lastPong) {
7713     DisplayMessage("", _("Waiting for second chess program"));
7714     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7715     return;
7716   }
7717   ThawUI();
7718   TwoMachinesEvent();
7719 }
7720
7721 void
7722 NextMatchGame P((void))
7723 {
7724     int index; /* [HGM] autoinc: step lod index during match */
7725     Reset(FALSE, TRUE);
7726     if (*appData.loadGameFile != NULLCHAR) {
7727         index = appData.loadGameIndex;
7728         if(index < 0) { // [HGM] autoinc
7729             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7730             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7731         }
7732         LoadGameFromFile(appData.loadGameFile,
7733                          index,
7734                          appData.loadGameFile, FALSE);
7735     } else if (*appData.loadPositionFile != NULLCHAR) {
7736         index = appData.loadPositionIndex;
7737         if(index < 0) { // [HGM] autoinc
7738             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7739             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7740         }
7741         LoadPositionFromFile(appData.loadPositionFile,
7742                              index,
7743                              appData.loadPositionFile);
7744     }
7745     TwoMachinesEventIfReady();
7746 }
7747
7748 void UserAdjudicationEvent( int result )
7749 {
7750     ChessMove gameResult = GameIsDrawn;
7751
7752     if( result > 0 ) {
7753         gameResult = WhiteWins;
7754     }
7755     else if( result < 0 ) {
7756         gameResult = BlackWins;
7757     }
7758
7759     if( gameMode == TwoMachinesPlay ) {
7760         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7761     }
7762 }
7763
7764
7765 void
7766 GameEnds(result, resultDetails, whosays)
7767      ChessMove result;
7768      char *resultDetails;
7769      int whosays;
7770 {
7771     GameMode nextGameMode;
7772     int isIcsGame;
7773     char buf[MSG_SIZ];
7774
7775     if(endingGame) return; /* [HGM] crash: forbid recursion */
7776     endingGame = 1;
7777
7778     if (appData.debugMode) {
7779       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7780               result, resultDetails ? resultDetails : "(null)", whosays);
7781     }
7782
7783     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7784         /* If we are playing on ICS, the server decides when the
7785            game is over, but the engine can offer to draw, claim
7786            a draw, or resign.
7787          */
7788 #if ZIPPY
7789         if (appData.zippyPlay && first.initDone) {
7790             if (result == GameIsDrawn) {
7791                 /* In case draw still needs to be claimed */
7792                 SendToICS(ics_prefix);
7793                 SendToICS("draw\n");
7794             } else if (StrCaseStr(resultDetails, "resign")) {
7795                 SendToICS(ics_prefix);
7796                 SendToICS("resign\n");
7797             }
7798         }
7799 #endif
7800         endingGame = 0; /* [HGM] crash */
7801         return;
7802     }
7803
7804     /* If we're loading the game from a file, stop */
7805     if (whosays == GE_FILE) {
7806       (void) StopLoadGameTimer();
7807       gameFileFP = NULL;
7808     }
7809
7810     /* Cancel draw offers */
7811     first.offeredDraw = second.offeredDraw = 0;
7812
7813     /* If this is an ICS game, only ICS can really say it's done;
7814        if not, anyone can. */
7815     isIcsGame = (gameMode == IcsPlayingWhite ||
7816                  gameMode == IcsPlayingBlack ||
7817                  gameMode == IcsObserving    ||
7818                  gameMode == IcsExamining);
7819
7820     if (!isIcsGame || whosays == GE_ICS) {
7821         /* OK -- not an ICS game, or ICS said it was done */
7822         StopClocks();
7823         if (!isIcsGame && !appData.noChessProgram)
7824           SetUserThinkingEnables();
7825
7826         /* [HGM] if a machine claims the game end we verify this claim */
7827         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7828             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7829                 char claimer;
7830                 ChessMove trueResult = (ChessMove) -1;
7831
7832                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7833                                             first.twoMachinesColor[0] :
7834                                             second.twoMachinesColor[0] ;
7835
7836                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7837                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7838                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7839                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7840                 } else
7841                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7842                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7843                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7844                 } else
7845                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7846                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7847                 }
7848
7849                 // now verify win claims, but not in drop games, as we don't understand those yet
7850                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7851                                                  || gameInfo.variant == VariantGreat) &&
7852                     (result == WhiteWins && claimer == 'w' ||
7853                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7854                       if (appData.debugMode) {
7855                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7856                                 result, epStatus[forwardMostMove], forwardMostMove);
7857                       }
7858                       if(result != trueResult) {
7859                               sprintf(buf, "False win claim: '%s'", resultDetails);
7860                               result = claimer == 'w' ? BlackWins : WhiteWins;
7861                               resultDetails = buf;
7862                       }
7863                 } else
7864                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7865                     && (forwardMostMove <= backwardMostMove ||
7866                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7867                         (claimer=='b')==(forwardMostMove&1))
7868                                                                                   ) {
7869                       /* [HGM] verify: draws that were not flagged are false claims */
7870                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7871                       result = claimer == 'w' ? BlackWins : WhiteWins;
7872                       resultDetails = buf;
7873                 }
7874                 /* (Claiming a loss is accepted no questions asked!) */
7875             }
7876
7877             /* [HGM] bare: don't allow bare King to win */
7878             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7879                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7880                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7881                && result != GameIsDrawn)
7882             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7883                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7884                         int p = (int)boards[forwardMostMove][i][j] - color;
7885                         if(p >= 0 && p <= (int)WhiteKing) k++;
7886                 }
7887                 if (appData.debugMode) {
7888                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7889                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7890                 }
7891                 if(k <= 1) {
7892                         result = GameIsDrawn;
7893                         sprintf(buf, "%s but bare king", resultDetails);
7894                         resultDetails = buf;
7895                 }
7896             }
7897         }
7898
7899         if(serverMoves != NULL && !loadFlag) { char c = '=';
7900             if(result==WhiteWins) c = '+';
7901             if(result==BlackWins) c = '-';
7902             if(resultDetails != NULL)
7903                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7904         }
7905         if (resultDetails != NULL) {
7906             gameInfo.result = result;
7907             gameInfo.resultDetails = StrSave(resultDetails);
7908
7909             /* display last move only if game was not loaded from file */
7910             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7911                 DisplayMove(currentMove - 1);
7912
7913             if (forwardMostMove != 0) {
7914                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
7915                     if (*appData.saveGameFile != NULLCHAR) {
7916                         SaveGameToFile(appData.saveGameFile, TRUE);
7917                     } else if (appData.autoSaveGames) {
7918                         AutoSaveGame();
7919                     }
7920                     if (*appData.savePositionFile != NULLCHAR) {
7921                         SavePositionToFile(appData.savePositionFile);
7922                     }
7923                 }
7924             }
7925
7926             /* Tell program how game ended in case it is learning */
7927             /* [HGM] Moved this to after saving the PGN, just in case */
7928             /* engine died and we got here through time loss. In that */
7929             /* case we will get a fatal error writing the pipe, which */
7930             /* would otherwise lose us the PGN.                       */
7931             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7932             /* output during GameEnds should never be fatal anymore   */
7933             if (gameMode == MachinePlaysWhite ||
7934                 gameMode == MachinePlaysBlack ||
7935                 gameMode == TwoMachinesPlay ||
7936                 gameMode == IcsPlayingWhite ||
7937                 gameMode == IcsPlayingBlack ||
7938                 gameMode == BeginningOfGame) {
7939                 char buf[MSG_SIZ];
7940                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7941                         resultDetails);
7942                 if (first.pr != NoProc) {
7943                     SendToProgram(buf, &first);
7944                 }
7945                 if (second.pr != NoProc &&
7946                     gameMode == TwoMachinesPlay) {
7947                     SendToProgram(buf, &second);
7948                 }
7949             }
7950         }
7951
7952         if (appData.icsActive) {
7953             if (appData.quietPlay &&
7954                 (gameMode == IcsPlayingWhite ||
7955                  gameMode == IcsPlayingBlack)) {
7956                 SendToICS(ics_prefix);
7957                 SendToICS("set shout 1\n");
7958             }
7959             nextGameMode = IcsIdle;
7960             ics_user_moved = FALSE;
7961             /* clean up premove.  It's ugly when the game has ended and the
7962              * premove highlights are still on the board.
7963              */
7964             if (gotPremove) {
7965               gotPremove = FALSE;
7966               ClearPremoveHighlights();
7967               DrawPosition(FALSE, boards[currentMove]);
7968             }
7969             if (whosays == GE_ICS) {
7970                 switch (result) {
7971                 case WhiteWins:
7972                     if (gameMode == IcsPlayingWhite)
7973                         PlayIcsWinSound();
7974                     else if(gameMode == IcsPlayingBlack)
7975                         PlayIcsLossSound();
7976                     break;
7977                 case BlackWins:
7978                     if (gameMode == IcsPlayingBlack)
7979                         PlayIcsWinSound();
7980                     else if(gameMode == IcsPlayingWhite)
7981                         PlayIcsLossSound();
7982                     break;
7983                 case GameIsDrawn:
7984                     PlayIcsDrawSound();
7985                     break;
7986                 default:
7987                     PlayIcsUnfinishedSound();
7988                 }
7989             }
7990         } else if (gameMode == EditGame ||
7991                    gameMode == PlayFromGameFile ||
7992                    gameMode == AnalyzeMode ||
7993                    gameMode == AnalyzeFile) {
7994             nextGameMode = gameMode;
7995         } else {
7996             nextGameMode = EndOfGame;
7997         }
7998         pausing = FALSE;
7999         ModeHighlight();
8000     } else {
8001         nextGameMode = gameMode;
8002     }
8003
8004     if (appData.noChessProgram) {
8005         gameMode = nextGameMode;
8006         ModeHighlight();
8007         endingGame = 0; /* [HGM] crash */
8008         return;
8009     }
8010
8011     if (first.reuse) {
8012         /* Put first chess program into idle state */
8013         if (first.pr != NoProc &&
8014             (gameMode == MachinePlaysWhite ||
8015              gameMode == MachinePlaysBlack ||
8016              gameMode == TwoMachinesPlay ||
8017              gameMode == IcsPlayingWhite ||
8018              gameMode == IcsPlayingBlack ||
8019              gameMode == BeginningOfGame)) {
8020             SendToProgram("force\n", &first);
8021             if (first.usePing) {
8022               char buf[MSG_SIZ];
8023               sprintf(buf, "ping %d\n", ++first.lastPing);
8024               SendToProgram(buf, &first);
8025             }
8026         }
8027     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8028         /* Kill off first chess program */
8029         if (first.isr != NULL)
8030           RemoveInputSource(first.isr);
8031         first.isr = NULL;
8032
8033         if (first.pr != NoProc) {
8034             ExitAnalyzeMode();
8035             DoSleep( appData.delayBeforeQuit );
8036             SendToProgram("quit\n", &first);
8037             DoSleep( appData.delayAfterQuit );
8038             DestroyChildProcess(first.pr, first.useSigterm);
8039         }
8040         first.pr = NoProc;
8041     }
8042     if (second.reuse) {
8043         /* Put second chess program into idle state */
8044         if (second.pr != NoProc &&
8045             gameMode == TwoMachinesPlay) {
8046             SendToProgram("force\n", &second);
8047             if (second.usePing) {
8048               char buf[MSG_SIZ];
8049               sprintf(buf, "ping %d\n", ++second.lastPing);
8050               SendToProgram(buf, &second);
8051             }
8052         }
8053     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8054         /* Kill off second chess program */
8055         if (second.isr != NULL)
8056           RemoveInputSource(second.isr);
8057         second.isr = NULL;
8058
8059         if (second.pr != NoProc) {
8060             DoSleep( appData.delayBeforeQuit );
8061             SendToProgram("quit\n", &second);
8062             DoSleep( appData.delayAfterQuit );
8063             DestroyChildProcess(second.pr, second.useSigterm);
8064         }
8065         second.pr = NoProc;
8066     }
8067
8068     if (matchMode && gameMode == TwoMachinesPlay) {
8069         switch (result) {
8070         case WhiteWins:
8071           if (first.twoMachinesColor[0] == 'w') {
8072             first.matchWins++;
8073           } else {
8074             second.matchWins++;
8075           }
8076           break;
8077         case BlackWins:
8078           if (first.twoMachinesColor[0] == 'b') {
8079             first.matchWins++;
8080           } else {
8081             second.matchWins++;
8082           }
8083           break;
8084         default:
8085           break;
8086         }
8087         if (matchGame < appData.matchGames) {
8088             char *tmp;
8089             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8090                 tmp = first.twoMachinesColor;
8091                 first.twoMachinesColor = second.twoMachinesColor;
8092                 second.twoMachinesColor = tmp;
8093             }
8094             gameMode = nextGameMode;
8095             matchGame++;
8096             if(appData.matchPause>10000 || appData.matchPause<10)
8097                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8098             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8099             endingGame = 0; /* [HGM] crash */
8100             return;
8101         } else {
8102             char buf[MSG_SIZ];
8103             gameMode = nextGameMode;
8104             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8105                     first.tidy, second.tidy,
8106                     first.matchWins, second.matchWins,
8107                     appData.matchGames - (first.matchWins + second.matchWins));
8108             DisplayFatalError(buf, 0, 0);
8109         }
8110     }
8111     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8112         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8113       ExitAnalyzeMode();
8114     gameMode = nextGameMode;
8115     ModeHighlight();
8116     endingGame = 0;  /* [HGM] crash */
8117 }
8118
8119 /* Assumes program was just initialized (initString sent).
8120    Leaves program in force mode. */
8121 void
8122 FeedMovesToProgram(cps, upto)
8123      ChessProgramState *cps;
8124      int upto;
8125 {
8126     int i;
8127
8128     if (appData.debugMode)
8129       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8130               startedFromSetupPosition ? "position and " : "",
8131               backwardMostMove, upto, cps->which);
8132     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8133         // [HGM] variantswitch: make engine aware of new variant
8134         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8135                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8136         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8137         SendToProgram(buf, cps);
8138         currentlyInitializedVariant = gameInfo.variant;
8139     }
8140     SendToProgram("force\n", cps);
8141     if (startedFromSetupPosition) {
8142         SendBoard(cps, backwardMostMove);
8143     if (appData.debugMode) {
8144         fprintf(debugFP, "feedMoves\n");
8145     }
8146     }
8147     for (i = backwardMostMove; i < upto; i++) {
8148         SendMoveToProgram(i, cps);
8149     }
8150 }
8151
8152
8153 void
8154 ResurrectChessProgram()
8155 {
8156      /* The chess program may have exited.
8157         If so, restart it and feed it all the moves made so far. */
8158
8159     if (appData.noChessProgram || first.pr != NoProc) return;
8160
8161     StartChessProgram(&first);
8162     InitChessProgram(&first, FALSE);
8163     FeedMovesToProgram(&first, currentMove);
8164
8165     if (!first.sendTime) {
8166         /* can't tell gnuchess what its clock should read,
8167            so we bow to its notion. */
8168         ResetClocks();
8169         timeRemaining[0][currentMove] = whiteTimeRemaining;
8170         timeRemaining[1][currentMove] = blackTimeRemaining;
8171     }
8172
8173     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8174                 appData.icsEngineAnalyze) && first.analysisSupport) {
8175       SendToProgram("analyze\n", &first);
8176       first.analyzing = TRUE;
8177     }
8178 }
8179
8180 /*
8181  * Button procedures
8182  */
8183 void
8184 Reset(redraw, init)
8185      int redraw, init;
8186 {
8187     int i;
8188
8189     if (appData.debugMode) {
8190         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8191                 redraw, init, gameMode);
8192     }
8193     pausing = pauseExamInvalid = FALSE;
8194     startedFromSetupPosition = blackPlaysFirst = FALSE;
8195     firstMove = TRUE;
8196     whiteFlag = blackFlag = FALSE;
8197     userOfferedDraw = FALSE;
8198     hintRequested = bookRequested = FALSE;
8199     first.maybeThinking = FALSE;
8200     second.maybeThinking = FALSE;
8201     first.bookSuspend = FALSE; // [HGM] book
8202     second.bookSuspend = FALSE;
8203     thinkOutput[0] = NULLCHAR;
8204     lastHint[0] = NULLCHAR;
8205     ClearGameInfo(&gameInfo);
8206     gameInfo.variant = StringToVariant(appData.variant);
8207     ics_user_moved = ics_clock_paused = FALSE;
8208     ics_getting_history = H_FALSE;
8209     ics_gamenum = -1;
8210     white_holding[0] = black_holding[0] = NULLCHAR;
8211     ClearProgramStats();
8212     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8213
8214     ResetFrontEnd();
8215     ClearHighlights();
8216     flipView = appData.flipView;
8217     ClearPremoveHighlights();
8218     gotPremove = FALSE;
8219     alarmSounded = FALSE;
8220
8221     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8222     if(appData.serverMovesName != NULL) {
8223         /* [HGM] prepare to make moves file for broadcasting */
8224         clock_t t = clock();
8225         if(serverMoves != NULL) fclose(serverMoves);
8226         serverMoves = fopen(appData.serverMovesName, "r");
8227         if(serverMoves != NULL) {
8228             fclose(serverMoves);
8229             /* delay 15 sec before overwriting, so all clients can see end */
8230             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8231         }
8232         serverMoves = fopen(appData.serverMovesName, "w");
8233     }
8234
8235     ExitAnalyzeMode();
8236     gameMode = BeginningOfGame;
8237     ModeHighlight();
8238
8239     if(appData.icsActive) gameInfo.variant = VariantNormal;
8240     InitPosition(redraw);
8241     for (i = 0; i < MAX_MOVES; i++) {
8242         if (commentList[i] != NULL) {
8243             free(commentList[i]);
8244             commentList[i] = NULL;
8245         }
8246     }
8247
8248     ResetClocks();
8249     timeRemaining[0][0] = whiteTimeRemaining;
8250     timeRemaining[1][0] = blackTimeRemaining;
8251     if (first.pr == NULL) {
8252         StartChessProgram(&first);
8253     }
8254     if (init) {
8255             InitChessProgram(&first, startedFromSetupPosition);
8256     }
8257
8258     DisplayTitle("");
8259     DisplayMessage("", "");
8260     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8261
8262     return;
8263 }
8264
8265 void
8266 AutoPlayGameLoop()
8267 {
8268     for (;;) {
8269         if (!AutoPlayOneMove())
8270           return;
8271         if (matchMode || appData.timeDelay == 0)
8272           continue;
8273         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8274           return;
8275         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8276         break;
8277     }
8278 }
8279
8280
8281 int
8282 AutoPlayOneMove()
8283 {
8284     int fromX, fromY, toX, toY;
8285
8286     if (appData.debugMode) {
8287       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8288     }
8289
8290     if (gameMode != PlayFromGameFile)
8291       return FALSE;
8292
8293     if (currentMove >= forwardMostMove) {
8294       gameMode = EditGame;
8295       ModeHighlight();
8296
8297       /* [AS] Clear current move marker at the end of a game */
8298       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8299
8300       return FALSE;
8301     }
8302
8303     toX = moveList[currentMove][2] - AAA;
8304     toY = moveList[currentMove][3] - ONE;
8305
8306     if (moveList[currentMove][1] == '@') {
8307         if (appData.highlightLastMove) {
8308             SetHighlights(-1, -1, toX, toY);
8309         }
8310     } else {
8311         fromX = moveList[currentMove][0] - AAA;
8312         fromY = moveList[currentMove][1] - ONE;
8313
8314         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8315
8316         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8317
8318         if (appData.highlightLastMove) {
8319             SetHighlights(fromX, fromY, toX, toY);
8320         }
8321     }
8322     DisplayMove(currentMove);
8323     SendMoveToProgram(currentMove++, &first);
8324     DisplayBothClocks();
8325     DrawPosition(FALSE, boards[currentMove]);
8326     // [HGM] PV info: always display, routine tests if empty
8327     DisplayComment(currentMove - 1, commentList[currentMove]);
8328     return TRUE;
8329 }
8330
8331
8332 int
8333 LoadGameOneMove(readAhead)
8334      ChessMove readAhead;
8335 {
8336     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8337     char promoChar = NULLCHAR;
8338     ChessMove moveType;
8339     char move[MSG_SIZ];
8340     char *p, *q;
8341
8342     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8343         gameMode != AnalyzeMode && gameMode != Training) {
8344         gameFileFP = NULL;
8345         return FALSE;
8346     }
8347
8348     yyboardindex = forwardMostMove;
8349     if (readAhead != (ChessMove)0) {
8350       moveType = readAhead;
8351     } else {
8352       if (gameFileFP == NULL)
8353           return FALSE;
8354       moveType = (ChessMove) yylex();
8355     }
8356
8357     done = FALSE;
8358     switch (moveType) {
8359       case Comment:
8360         if (appData.debugMode)
8361           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8362         p = yy_text;
8363         if (*p == '{' || *p == '[' || *p == '(') {
8364             p[strlen(p) - 1] = NULLCHAR;
8365             p++;
8366         }
8367
8368         /* append the comment but don't display it */
8369         while (*p == '\n') p++;
8370         AppendComment(currentMove, p);
8371         return TRUE;
8372
8373       case WhiteCapturesEnPassant:
8374       case BlackCapturesEnPassant:
8375       case WhitePromotionChancellor:
8376       case BlackPromotionChancellor:
8377       case WhitePromotionArchbishop:
8378       case BlackPromotionArchbishop:
8379       case WhitePromotionCentaur:
8380       case BlackPromotionCentaur:
8381       case WhitePromotionQueen:
8382       case BlackPromotionQueen:
8383       case WhitePromotionRook:
8384       case BlackPromotionRook:
8385       case WhitePromotionBishop:
8386       case BlackPromotionBishop:
8387       case WhitePromotionKnight:
8388       case BlackPromotionKnight:
8389       case WhitePromotionKing:
8390       case BlackPromotionKing:
8391       case NormalMove:
8392       case WhiteKingSideCastle:
8393       case WhiteQueenSideCastle:
8394       case BlackKingSideCastle:
8395       case BlackQueenSideCastle:
8396       case WhiteKingSideCastleWild:
8397       case WhiteQueenSideCastleWild:
8398       case BlackKingSideCastleWild:
8399       case BlackQueenSideCastleWild:
8400       /* PUSH Fabien */
8401       case WhiteHSideCastleFR:
8402       case WhiteASideCastleFR:
8403       case BlackHSideCastleFR:
8404       case BlackASideCastleFR:
8405       /* POP Fabien */
8406         if (appData.debugMode)
8407           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8408         fromX = currentMoveString[0] - AAA;
8409         fromY = currentMoveString[1] - ONE;
8410         toX = currentMoveString[2] - AAA;
8411         toY = currentMoveString[3] - ONE;
8412         promoChar = currentMoveString[4];
8413         break;
8414
8415       case WhiteDrop:
8416       case BlackDrop:
8417         if (appData.debugMode)
8418           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8419         fromX = moveType == WhiteDrop ?
8420           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8421         (int) CharToPiece(ToLower(currentMoveString[0]));
8422         fromY = DROP_RANK;
8423         toX = currentMoveString[2] - AAA;
8424         toY = currentMoveString[3] - ONE;
8425         break;
8426
8427       case WhiteWins:
8428       case BlackWins:
8429       case GameIsDrawn:
8430       case GameUnfinished:
8431         if (appData.debugMode)
8432           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8433         p = strchr(yy_text, '{');
8434         if (p == NULL) p = strchr(yy_text, '(');
8435         if (p == NULL) {
8436             p = yy_text;
8437             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8438         } else {
8439             q = strchr(p, *p == '{' ? '}' : ')');
8440             if (q != NULL) *q = NULLCHAR;
8441             p++;
8442         }
8443         GameEnds(moveType, p, GE_FILE);
8444         done = TRUE;
8445         if (cmailMsgLoaded) {
8446             ClearHighlights();
8447             flipView = WhiteOnMove(currentMove);
8448             if (moveType == GameUnfinished) flipView = !flipView;
8449             if (appData.debugMode)
8450               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8451         }
8452         break;
8453
8454       case (ChessMove) 0:       /* end of file */
8455         if (appData.debugMode)
8456           fprintf(debugFP, "Parser hit end of file\n");
8457         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8458                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8459           case MT_NONE:
8460           case MT_CHECK:
8461             break;
8462           case MT_CHECKMATE:
8463           case MT_STAINMATE:
8464             if (WhiteOnMove(currentMove)) {
8465                 GameEnds(BlackWins, "Black mates", GE_FILE);
8466             } else {
8467                 GameEnds(WhiteWins, "White mates", GE_FILE);
8468             }
8469             break;
8470           case MT_STALEMATE:
8471             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8472             break;
8473         }
8474         done = TRUE;
8475         break;
8476
8477       case MoveNumberOne:
8478         if (lastLoadGameStart == GNUChessGame) {
8479             /* GNUChessGames have numbers, but they aren't move numbers */
8480             if (appData.debugMode)
8481               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8482                       yy_text, (int) moveType);
8483             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8484         }
8485         /* else fall thru */
8486
8487       case XBoardGame:
8488       case GNUChessGame:
8489       case PGNTag:
8490         /* Reached start of next game in file */
8491         if (appData.debugMode)
8492           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8493         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8494                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8495           case MT_NONE:
8496           case MT_CHECK:
8497             break;
8498           case MT_CHECKMATE:
8499           case MT_STAINMATE:
8500             if (WhiteOnMove(currentMove)) {
8501                 GameEnds(BlackWins, "Black mates", GE_FILE);
8502             } else {
8503                 GameEnds(WhiteWins, "White mates", GE_FILE);
8504             }
8505             break;
8506           case MT_STALEMATE:
8507             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8508             break;
8509         }
8510         done = TRUE;
8511         break;
8512
8513       case PositionDiagram:     /* should not happen; ignore */
8514       case ElapsedTime:         /* ignore */
8515       case NAG:                 /* ignore */
8516         if (appData.debugMode)
8517           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8518                   yy_text, (int) moveType);
8519         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8520
8521       case IllegalMove:
8522         if (appData.testLegality) {
8523             if (appData.debugMode)
8524               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8525             sprintf(move, _("Illegal move: %d.%s%s"),
8526                     (forwardMostMove / 2) + 1,
8527                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8528             DisplayError(move, 0);
8529             done = TRUE;
8530         } else {
8531             if (appData.debugMode)
8532               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8533                       yy_text, currentMoveString);
8534             fromX = currentMoveString[0] - AAA;
8535             fromY = currentMoveString[1] - ONE;
8536             toX = currentMoveString[2] - AAA;
8537             toY = currentMoveString[3] - ONE;
8538             promoChar = currentMoveString[4];
8539         }
8540         break;
8541
8542       case AmbiguousMove:
8543         if (appData.debugMode)
8544           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8545         sprintf(move, _("Ambiguous move: %d.%s%s"),
8546                 (forwardMostMove / 2) + 1,
8547                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8548         DisplayError(move, 0);
8549         done = TRUE;
8550         break;
8551
8552       default:
8553       case ImpossibleMove:
8554         if (appData.debugMode)
8555           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8556         sprintf(move, _("Illegal move: %d.%s%s"),
8557                 (forwardMostMove / 2) + 1,
8558                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8559         DisplayError(move, 0);
8560         done = TRUE;
8561         break;
8562     }
8563
8564     if (done) {
8565         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8566             DrawPosition(FALSE, boards[currentMove]);
8567             DisplayBothClocks();
8568             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8569               DisplayComment(currentMove - 1, commentList[currentMove]);
8570         }
8571         (void) StopLoadGameTimer();
8572         gameFileFP = NULL;
8573         cmailOldMove = forwardMostMove;
8574         return FALSE;
8575     } else {
8576         /* currentMoveString is set as a side-effect of yylex */
8577         strcat(currentMoveString, "\n");
8578         strcpy(moveList[forwardMostMove], currentMoveString);
8579
8580         thinkOutput[0] = NULLCHAR;
8581         MakeMove(fromX, fromY, toX, toY, promoChar);
8582         currentMove = forwardMostMove;
8583         return TRUE;
8584     }
8585 }
8586
8587 /* Load the nth game from the given file */
8588 int
8589 LoadGameFromFile(filename, n, title, useList)
8590      char *filename;
8591      int n;
8592      char *title;
8593      /*Boolean*/ int useList;
8594 {
8595     FILE *f;
8596     char buf[MSG_SIZ];
8597
8598     if (strcmp(filename, "-") == 0) {
8599         f = stdin;
8600         title = "stdin";
8601     } else {
8602         f = fopen(filename, "rb");
8603         if (f == NULL) {
8604           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8605             DisplayError(buf, errno);
8606             return FALSE;
8607         }
8608     }
8609     if (fseek(f, 0, 0) == -1) {
8610         /* f is not seekable; probably a pipe */
8611         useList = FALSE;
8612     }
8613     if (useList && n == 0) {
8614         int error = GameListBuild(f);
8615         if (error) {
8616             DisplayError(_("Cannot build game list"), error);
8617         } else if (!ListEmpty(&gameList) &&
8618                    ((ListGame *) gameList.tailPred)->number > 1) {
8619             GameListPopUp(f, title);
8620             return TRUE;
8621         }
8622         GameListDestroy();
8623         n = 1;
8624     }
8625     if (n == 0) n = 1;
8626     return LoadGame(f, n, title, FALSE);
8627 }
8628
8629
8630 void
8631 MakeRegisteredMove()
8632 {
8633     int fromX, fromY, toX, toY;
8634     char promoChar;
8635     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8636         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8637           case CMAIL_MOVE:
8638           case CMAIL_DRAW:
8639             if (appData.debugMode)
8640               fprintf(debugFP, "Restoring %s for game %d\n",
8641                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8642
8643             thinkOutput[0] = NULLCHAR;
8644             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8645             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8646             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8647             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8648             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8649             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8650             MakeMove(fromX, fromY, toX, toY, promoChar);
8651             ShowMove(fromX, fromY, toX, toY);
8652
8653             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8654                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8655               case MT_NONE:
8656               case MT_CHECK:
8657                 break;
8658
8659               case MT_CHECKMATE:
8660               case MT_STAINMATE:
8661                 if (WhiteOnMove(currentMove)) {
8662                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8663                 } else {
8664                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8665                 }
8666                 break;
8667
8668               case MT_STALEMATE:
8669                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8670                 break;
8671             }
8672
8673             break;
8674
8675           case CMAIL_RESIGN:
8676             if (WhiteOnMove(currentMove)) {
8677                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8678             } else {
8679                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8680             }
8681             break;
8682
8683           case CMAIL_ACCEPT:
8684             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8685             break;
8686
8687           default:
8688             break;
8689         }
8690     }
8691
8692     return;
8693 }
8694
8695 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8696 int
8697 CmailLoadGame(f, gameNumber, title, useList)
8698      FILE *f;
8699      int gameNumber;
8700      char *title;
8701      int useList;
8702 {
8703     int retVal;
8704
8705     if (gameNumber > nCmailGames) {
8706         DisplayError(_("No more games in this message"), 0);
8707         return FALSE;
8708     }
8709     if (f == lastLoadGameFP) {
8710         int offset = gameNumber - lastLoadGameNumber;
8711         if (offset == 0) {
8712             cmailMsg[0] = NULLCHAR;
8713             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8714                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8715                 nCmailMovesRegistered--;
8716             }
8717             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8718             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8719                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8720             }
8721         } else {
8722             if (! RegisterMove()) return FALSE;
8723         }
8724     }
8725
8726     retVal = LoadGame(f, gameNumber, title, useList);
8727
8728     /* Make move registered during previous look at this game, if any */
8729     MakeRegisteredMove();
8730
8731     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8732         commentList[currentMove]
8733           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8734         DisplayComment(currentMove - 1, commentList[currentMove]);
8735     }
8736
8737     return retVal;
8738 }
8739
8740 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8741 int
8742 ReloadGame(offset)
8743      int offset;
8744 {
8745     int gameNumber = lastLoadGameNumber + offset;
8746     if (lastLoadGameFP == NULL) {
8747         DisplayError(_("No game has been loaded yet"), 0);
8748         return FALSE;
8749     }
8750     if (gameNumber <= 0) {
8751         DisplayError(_("Can't back up any further"), 0);
8752         return FALSE;
8753     }
8754     if (cmailMsgLoaded) {
8755         return CmailLoadGame(lastLoadGameFP, gameNumber,
8756                              lastLoadGameTitle, lastLoadGameUseList);
8757     } else {
8758         return LoadGame(lastLoadGameFP, gameNumber,
8759                         lastLoadGameTitle, lastLoadGameUseList);
8760     }
8761 }
8762
8763
8764
8765 /* Load the nth game from open file f */
8766 int
8767 LoadGame(f, gameNumber, title, useList)
8768      FILE *f;
8769      int gameNumber;
8770      char *title;
8771      int useList;
8772 {
8773     ChessMove cm;
8774     char buf[MSG_SIZ];
8775     int gn = gameNumber;
8776     ListGame *lg = NULL;
8777     int numPGNTags = 0;
8778     int err;
8779     GameMode oldGameMode;
8780     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8781
8782     if (appData.debugMode)
8783         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8784
8785     if (gameMode == Training )
8786         SetTrainingModeOff();
8787
8788     oldGameMode = gameMode;
8789     if (gameMode != BeginningOfGame) {
8790       Reset(FALSE, TRUE);
8791     }
8792
8793     gameFileFP = f;
8794     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8795         fclose(lastLoadGameFP);
8796     }
8797
8798     if (useList) {
8799         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8800
8801         if (lg) {
8802             fseek(f, lg->offset, 0);
8803             GameListHighlight(gameNumber);
8804             gn = 1;
8805         }
8806         else {
8807             DisplayError(_("Game number out of range"), 0);
8808             return FALSE;
8809         }
8810     } else {
8811         GameListDestroy();
8812         if (fseek(f, 0, 0) == -1) {
8813             if (f == lastLoadGameFP ?
8814                 gameNumber == lastLoadGameNumber + 1 :
8815                 gameNumber == 1) {
8816                 gn = 1;
8817             } else {
8818                 DisplayError(_("Can't seek on game file"), 0);
8819                 return FALSE;
8820             }
8821         }
8822     }
8823     lastLoadGameFP = f;
8824     lastLoadGameNumber = gameNumber;
8825     strcpy(lastLoadGameTitle, title);
8826     lastLoadGameUseList = useList;
8827
8828     yynewfile(f);
8829
8830     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8831       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8832                 lg->gameInfo.black);
8833             DisplayTitle(buf);
8834     } else if (*title != NULLCHAR) {
8835         if (gameNumber > 1) {
8836             sprintf(buf, "%s %d", title, gameNumber);
8837             DisplayTitle(buf);
8838         } else {
8839             DisplayTitle(title);
8840         }
8841     }
8842
8843     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8844         gameMode = PlayFromGameFile;
8845         ModeHighlight();
8846     }
8847
8848     currentMove = forwardMostMove = backwardMostMove = 0;
8849     CopyBoard(boards[0], initialPosition);
8850     StopClocks();
8851
8852     /*
8853      * Skip the first gn-1 games in the file.
8854      * Also skip over anything that precedes an identifiable
8855      * start of game marker, to avoid being confused by
8856      * garbage at the start of the file.  Currently
8857      * recognized start of game markers are the move number "1",
8858      * the pattern "gnuchess .* game", the pattern
8859      * "^[#;%] [^ ]* game file", and a PGN tag block.
8860      * A game that starts with one of the latter two patterns
8861      * will also have a move number 1, possibly
8862      * following a position diagram.
8863      * 5-4-02: Let's try being more lenient and allowing a game to
8864      * start with an unnumbered move.  Does that break anything?
8865      */
8866     cm = lastLoadGameStart = (ChessMove) 0;
8867     while (gn > 0) {
8868         yyboardindex = forwardMostMove;
8869         cm = (ChessMove) yylex();
8870         switch (cm) {
8871           case (ChessMove) 0:
8872             if (cmailMsgLoaded) {
8873                 nCmailGames = CMAIL_MAX_GAMES - gn;
8874             } else {
8875                 Reset(TRUE, TRUE);
8876                 DisplayError(_("Game not found in file"), 0);
8877             }
8878             return FALSE;
8879
8880           case GNUChessGame:
8881           case XBoardGame:
8882             gn--;
8883             lastLoadGameStart = cm;
8884             break;
8885
8886           case MoveNumberOne:
8887             switch (lastLoadGameStart) {
8888               case GNUChessGame:
8889               case XBoardGame:
8890               case PGNTag:
8891                 break;
8892               case MoveNumberOne:
8893               case (ChessMove) 0:
8894                 gn--;           /* count this game */
8895                 lastLoadGameStart = cm;
8896                 break;
8897               default:
8898                 /* impossible */
8899                 break;
8900             }
8901             break;
8902
8903           case PGNTag:
8904             switch (lastLoadGameStart) {
8905               case GNUChessGame:
8906               case PGNTag:
8907               case MoveNumberOne:
8908               case (ChessMove) 0:
8909                 gn--;           /* count this game */
8910                 lastLoadGameStart = cm;
8911                 break;
8912               case XBoardGame:
8913                 lastLoadGameStart = cm; /* game counted already */
8914                 break;
8915               default:
8916                 /* impossible */
8917                 break;
8918             }
8919             if (gn > 0) {
8920                 do {
8921                     yyboardindex = forwardMostMove;
8922                     cm = (ChessMove) yylex();
8923                 } while (cm == PGNTag || cm == Comment);
8924             }
8925             break;
8926
8927           case WhiteWins:
8928           case BlackWins:
8929           case GameIsDrawn:
8930             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8931                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8932                     != CMAIL_OLD_RESULT) {
8933                     nCmailResults ++ ;
8934                     cmailResult[  CMAIL_MAX_GAMES
8935                                 - gn - 1] = CMAIL_OLD_RESULT;
8936                 }
8937             }
8938             break;
8939
8940           case NormalMove:
8941             /* Only a NormalMove can be at the start of a game
8942              * without a position diagram. */
8943             if (lastLoadGameStart == (ChessMove) 0) {
8944               gn--;
8945               lastLoadGameStart = MoveNumberOne;
8946             }
8947             break;
8948
8949           default:
8950             break;
8951         }
8952     }
8953
8954     if (appData.debugMode)
8955       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8956
8957     if (cm == XBoardGame) {
8958         /* Skip any header junk before position diagram and/or move 1 */
8959         for (;;) {
8960             yyboardindex = forwardMostMove;
8961             cm = (ChessMove) yylex();
8962
8963             if (cm == (ChessMove) 0 ||
8964                 cm == GNUChessGame || cm == XBoardGame) {
8965                 /* Empty game; pretend end-of-file and handle later */
8966                 cm = (ChessMove) 0;
8967                 break;
8968             }
8969
8970             if (cm == MoveNumberOne || cm == PositionDiagram ||
8971                 cm == PGNTag || cm == Comment)
8972               break;
8973         }
8974     } else if (cm == GNUChessGame) {
8975         if (gameInfo.event != NULL) {
8976             free(gameInfo.event);
8977         }
8978         gameInfo.event = StrSave(yy_text);
8979     }
8980
8981     startedFromSetupPosition = FALSE;
8982     while (cm == PGNTag) {
8983         if (appData.debugMode)
8984           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8985         err = ParsePGNTag(yy_text, &gameInfo);
8986         if (!err) numPGNTags++;
8987
8988         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8989         if(gameInfo.variant != oldVariant) {
8990             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8991             InitPosition(TRUE);
8992             oldVariant = gameInfo.variant;
8993             if (appData.debugMode)
8994               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8995         }
8996
8997
8998         if (gameInfo.fen != NULL) {
8999           Board initial_position;
9000           startedFromSetupPosition = TRUE;
9001           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9002             Reset(TRUE, TRUE);
9003             DisplayError(_("Bad FEN position in file"), 0);
9004             return FALSE;
9005           }
9006           CopyBoard(boards[0], initial_position);
9007           if (blackPlaysFirst) {
9008             currentMove = forwardMostMove = backwardMostMove = 1;
9009             CopyBoard(boards[1], initial_position);
9010             strcpy(moveList[0], "");
9011             strcpy(parseList[0], "");
9012             timeRemaining[0][1] = whiteTimeRemaining;
9013             timeRemaining[1][1] = blackTimeRemaining;
9014             if (commentList[0] != NULL) {
9015               commentList[1] = commentList[0];
9016               commentList[0] = NULL;
9017             }
9018           } else {
9019             currentMove = forwardMostMove = backwardMostMove = 0;
9020           }
9021           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9022           {   int i;
9023               initialRulePlies = FENrulePlies;
9024               epStatus[forwardMostMove] = FENepStatus;
9025               for( i=0; i< nrCastlingRights; i++ )
9026                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9027           }
9028           yyboardindex = forwardMostMove;
9029           free(gameInfo.fen);
9030           gameInfo.fen = NULL;
9031         }
9032
9033         yyboardindex = forwardMostMove;
9034         cm = (ChessMove) yylex();
9035
9036         /* Handle comments interspersed among the tags */
9037         while (cm == Comment) {
9038             char *p;
9039             if (appData.debugMode)
9040               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9041             p = yy_text;
9042             if (*p == '{' || *p == '[' || *p == '(') {
9043                 p[strlen(p) - 1] = NULLCHAR;
9044                 p++;
9045             }
9046             while (*p == '\n') p++;
9047             AppendComment(currentMove, p);
9048             yyboardindex = forwardMostMove;
9049             cm = (ChessMove) yylex();
9050         }
9051     }
9052
9053     /* don't rely on existence of Event tag since if game was
9054      * pasted from clipboard the Event tag may not exist
9055      */
9056     if (numPGNTags > 0){
9057         char *tags;
9058         if (gameInfo.variant == VariantNormal) {
9059           gameInfo.variant = StringToVariant(gameInfo.event);
9060         }
9061         if (!matchMode) {
9062           if( appData.autoDisplayTags ) {
9063             tags = PGNTags(&gameInfo);
9064             TagsPopUp(tags, CmailMsg());
9065             free(tags);
9066           }
9067         }
9068     } else {
9069         /* Make something up, but don't display it now */
9070         SetGameInfo();
9071         TagsPopDown();
9072     }
9073
9074     if (cm == PositionDiagram) {
9075         int i, j;
9076         char *p;
9077         Board initial_position;
9078
9079         if (appData.debugMode)
9080           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9081
9082         if (!startedFromSetupPosition) {
9083             p = yy_text;
9084             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9085               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9086                 switch (*p) {
9087                   case '[':
9088                   case '-':
9089                   case ' ':
9090                   case '\t':
9091                   case '\n':
9092                   case '\r':
9093                     break;
9094                   default:
9095                     initial_position[i][j++] = CharToPiece(*p);
9096                     break;
9097                 }
9098             while (*p == ' ' || *p == '\t' ||
9099                    *p == '\n' || *p == '\r') p++;
9100
9101             if (strncmp(p, "black", strlen("black"))==0)
9102               blackPlaysFirst = TRUE;
9103             else
9104               blackPlaysFirst = FALSE;
9105             startedFromSetupPosition = TRUE;
9106
9107             CopyBoard(boards[0], initial_position);
9108             if (blackPlaysFirst) {
9109                 currentMove = forwardMostMove = backwardMostMove = 1;
9110                 CopyBoard(boards[1], initial_position);
9111                 strcpy(moveList[0], "");
9112                 strcpy(parseList[0], "");
9113                 timeRemaining[0][1] = whiteTimeRemaining;
9114                 timeRemaining[1][1] = blackTimeRemaining;
9115                 if (commentList[0] != NULL) {
9116                     commentList[1] = commentList[0];
9117                     commentList[0] = NULL;
9118                 }
9119             } else {
9120                 currentMove = forwardMostMove = backwardMostMove = 0;
9121             }
9122         }
9123         yyboardindex = forwardMostMove;
9124         cm = (ChessMove) yylex();
9125     }
9126
9127     if (first.pr == NoProc) {
9128         StartChessProgram(&first);
9129     }
9130     InitChessProgram(&first, FALSE);
9131     SendToProgram("force\n", &first);
9132     if (startedFromSetupPosition) {
9133         SendBoard(&first, forwardMostMove);
9134     if (appData.debugMode) {
9135         fprintf(debugFP, "Load Game\n");
9136     }
9137         DisplayBothClocks();
9138     }
9139
9140     /* [HGM] server: flag to write setup moves in broadcast file as one */
9141     loadFlag = appData.suppressLoadMoves;
9142
9143     while (cm == Comment) {
9144         char *p;
9145         if (appData.debugMode)
9146           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9147         p = yy_text;
9148         if (*p == '{' || *p == '[' || *p == '(') {
9149             p[strlen(p) - 1] = NULLCHAR;
9150             p++;
9151         }
9152         while (*p == '\n') p++;
9153         AppendComment(currentMove, p);
9154         yyboardindex = forwardMostMove;
9155         cm = (ChessMove) yylex();
9156     }
9157
9158     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9159         cm == WhiteWins || cm == BlackWins ||
9160         cm == GameIsDrawn || cm == GameUnfinished) {
9161         DisplayMessage("", _("No moves in game"));
9162         if (cmailMsgLoaded) {
9163             if (appData.debugMode)
9164               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9165             ClearHighlights();
9166             flipView = FALSE;
9167         }
9168         DrawPosition(FALSE, boards[currentMove]);
9169         DisplayBothClocks();
9170         gameMode = EditGame;
9171         ModeHighlight();
9172         gameFileFP = NULL;
9173         cmailOldMove = 0;
9174         return TRUE;
9175     }
9176
9177     // [HGM] PV info: routine tests if comment empty
9178     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9179         DisplayComment(currentMove - 1, commentList[currentMove]);
9180     }
9181     if (!matchMode && appData.timeDelay != 0)
9182       DrawPosition(FALSE, boards[currentMove]);
9183
9184     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9185       programStats.ok_to_send = 1;
9186     }
9187
9188     /* if the first token after the PGN tags is a move
9189      * and not move number 1, retrieve it from the parser
9190      */
9191     if (cm != MoveNumberOne)
9192         LoadGameOneMove(cm);
9193
9194     /* load the remaining moves from the file */
9195     while (LoadGameOneMove((ChessMove)0)) {
9196       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9197       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9198     }
9199
9200     /* rewind to the start of the game */
9201     currentMove = backwardMostMove;
9202
9203     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9204
9205     if (oldGameMode == AnalyzeFile ||
9206         oldGameMode == AnalyzeMode) {
9207       AnalyzeFileEvent();
9208     }
9209
9210     if (matchMode || appData.timeDelay == 0) {
9211       ToEndEvent();
9212       gameMode = EditGame;
9213       ModeHighlight();
9214     } else if (appData.timeDelay > 0) {
9215       AutoPlayGameLoop();
9216     }
9217
9218     if (appData.debugMode)
9219         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9220
9221     loadFlag = 0; /* [HGM] true game starts */
9222     return TRUE;
9223 }
9224
9225 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9226 int
9227 ReloadPosition(offset)
9228      int offset;
9229 {
9230     int positionNumber = lastLoadPositionNumber + offset;
9231     if (lastLoadPositionFP == NULL) {
9232         DisplayError(_("No position has been loaded yet"), 0);
9233         return FALSE;
9234     }
9235     if (positionNumber <= 0) {
9236         DisplayError(_("Can't back up any further"), 0);
9237         return FALSE;
9238     }
9239     return LoadPosition(lastLoadPositionFP, positionNumber,
9240                         lastLoadPositionTitle);
9241 }
9242
9243 /* Load the nth position from the given file */
9244 int
9245 LoadPositionFromFile(filename, n, title)
9246      char *filename;
9247      int n;
9248      char *title;
9249 {
9250     FILE *f;
9251     char buf[MSG_SIZ];
9252
9253     if (strcmp(filename, "-") == 0) {
9254         return LoadPosition(stdin, n, "stdin");
9255     } else {
9256         f = fopen(filename, "rb");
9257         if (f == NULL) {
9258             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9259             DisplayError(buf, errno);
9260             return FALSE;
9261         } else {
9262             return LoadPosition(f, n, title);
9263         }
9264     }
9265 }
9266
9267 /* Load the nth position from the given open file, and close it */
9268 int
9269 LoadPosition(f, positionNumber, title)
9270      FILE *f;
9271      int positionNumber;
9272      char *title;
9273 {
9274     char *p, line[MSG_SIZ];
9275     Board initial_position;
9276     int i, j, fenMode, pn;
9277
9278     if (gameMode == Training )
9279         SetTrainingModeOff();
9280
9281     if (gameMode != BeginningOfGame) {
9282         Reset(FALSE, TRUE);
9283     }
9284     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9285         fclose(lastLoadPositionFP);
9286     }
9287     if (positionNumber == 0) positionNumber = 1;
9288     lastLoadPositionFP = f;
9289     lastLoadPositionNumber = positionNumber;
9290     strcpy(lastLoadPositionTitle, title);
9291     if (first.pr == NoProc) {
9292       StartChessProgram(&first);
9293       InitChessProgram(&first, FALSE);
9294     }
9295     pn = positionNumber;
9296     if (positionNumber < 0) {
9297         /* Negative position number means to seek to that byte offset */
9298         if (fseek(f, -positionNumber, 0) == -1) {
9299             DisplayError(_("Can't seek on position file"), 0);
9300             return FALSE;
9301         };
9302         pn = 1;
9303     } else {
9304         if (fseek(f, 0, 0) == -1) {
9305             if (f == lastLoadPositionFP ?
9306                 positionNumber == lastLoadPositionNumber + 1 :
9307                 positionNumber == 1) {
9308                 pn = 1;
9309             } else {
9310                 DisplayError(_("Can't seek on position file"), 0);
9311                 return FALSE;
9312             }
9313         }
9314     }
9315     /* See if this file is FEN or old-style xboard */
9316     if (fgets(line, MSG_SIZ, f) == NULL) {
9317         DisplayError(_("Position not found in file"), 0);
9318         return FALSE;
9319     }
9320 #if 0
9321     switch (line[0]) {
9322       case '#':  case 'x':
9323       default:
9324         fenMode = FALSE;
9325         break;
9326       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9327       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9328       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9329       case '7':  case '8':  case '9':
9330       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9331       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9332       case 'C':  case 'W':             case 'c':  case 'w':
9333         fenMode = TRUE;
9334         break;
9335     }
9336 #else
9337     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9338     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9339 #endif
9340
9341     if (pn >= 2) {
9342         if (fenMode || line[0] == '#') pn--;
9343         while (pn > 0) {
9344             /* skip positions before number pn */
9345             if (fgets(line, MSG_SIZ, f) == NULL) {
9346                 Reset(TRUE, TRUE);
9347                 DisplayError(_("Position not found in file"), 0);
9348                 return FALSE;
9349             }
9350             if (fenMode || line[0] == '#') pn--;
9351         }
9352     }
9353
9354     if (fenMode) {
9355         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9356             DisplayError(_("Bad FEN position in file"), 0);
9357             return FALSE;
9358         }
9359     } else {
9360         (void) fgets(line, MSG_SIZ, f);
9361         (void) fgets(line, MSG_SIZ, f);
9362
9363         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9364             (void) fgets(line, MSG_SIZ, f);
9365             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9366                 if (*p == ' ')
9367                   continue;
9368                 initial_position[i][j++] = CharToPiece(*p);
9369             }
9370         }
9371
9372         blackPlaysFirst = FALSE;
9373         if (!feof(f)) {
9374             (void) fgets(line, MSG_SIZ, f);
9375             if (strncmp(line, "black", strlen("black"))==0)
9376               blackPlaysFirst = TRUE;
9377         }
9378     }
9379     startedFromSetupPosition = TRUE;
9380
9381     SendToProgram("force\n", &first);
9382     CopyBoard(boards[0], initial_position);
9383     if (blackPlaysFirst) {
9384         currentMove = forwardMostMove = backwardMostMove = 1;
9385         strcpy(moveList[0], "");
9386         strcpy(parseList[0], "");
9387         CopyBoard(boards[1], initial_position);
9388         DisplayMessage("", _("Black to play"));
9389     } else {
9390         currentMove = forwardMostMove = backwardMostMove = 0;
9391         DisplayMessage("", _("White to play"));
9392     }
9393           /* [HGM] copy FEN attributes as well */
9394           {   int i;
9395               initialRulePlies = FENrulePlies;
9396               epStatus[forwardMostMove] = FENepStatus;
9397               for( i=0; i< nrCastlingRights; i++ )
9398                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9399           }
9400     SendBoard(&first, forwardMostMove);
9401     if (appData.debugMode) {
9402 int i, j;
9403   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9404   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9405         fprintf(debugFP, "Load Position\n");
9406     }
9407
9408     if (positionNumber > 1) {
9409         sprintf(line, "%s %d", title, positionNumber);
9410         DisplayTitle(line);
9411     } else {
9412         DisplayTitle(title);
9413     }
9414     gameMode = EditGame;
9415     ModeHighlight();
9416     ResetClocks();
9417     timeRemaining[0][1] = whiteTimeRemaining;
9418     timeRemaining[1][1] = blackTimeRemaining;
9419     DrawPosition(FALSE, boards[currentMove]);
9420
9421     return TRUE;
9422 }
9423
9424
9425 void
9426 CopyPlayerNameIntoFileName(dest, src)
9427      char **dest, *src;
9428 {
9429     while (*src != NULLCHAR && *src != ',') {
9430         if (*src == ' ') {
9431             *(*dest)++ = '_';
9432             src++;
9433         } else {
9434             *(*dest)++ = *src++;
9435         }
9436     }
9437 }
9438
9439 char *DefaultFileName(ext)
9440      char *ext;
9441 {
9442     static char def[MSG_SIZ];
9443     char *p;
9444
9445     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9446         p = def;
9447         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9448         *p++ = '-';
9449         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9450         *p++ = '.';
9451         strcpy(p, ext);
9452     } else {
9453         def[0] = NULLCHAR;
9454     }
9455     return def;
9456 }
9457
9458 /* Save the current game to the given file */
9459 int
9460 SaveGameToFile(filename, append)
9461      char *filename;
9462      int append;
9463 {
9464     FILE *f;
9465     char buf[MSG_SIZ];
9466
9467     if (strcmp(filename, "-") == 0) {
9468         return SaveGame(stdout, 0, NULL);
9469     } else {
9470         f = fopen(filename, append ? "a" : "w");
9471         if (f == NULL) {
9472             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9473             DisplayError(buf, errno);
9474             return FALSE;
9475         } else {
9476             return SaveGame(f, 0, NULL);
9477         }
9478     }
9479 }
9480
9481 char *
9482 SavePart(str)
9483      char *str;
9484 {
9485     static char buf[MSG_SIZ];
9486     char *p;
9487
9488     p = strchr(str, ' ');
9489     if (p == NULL) return str;
9490     strncpy(buf, str, p - str);
9491     buf[p - str] = NULLCHAR;
9492     return buf;
9493 }
9494
9495 #define PGN_MAX_LINE 75
9496
9497 #define PGN_SIDE_WHITE  0
9498 #define PGN_SIDE_BLACK  1
9499
9500 /* [AS] */
9501 static int FindFirstMoveOutOfBook( int side )
9502 {
9503     int result = -1;
9504
9505     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9506         int index = backwardMostMove;
9507         int has_book_hit = 0;
9508
9509         if( (index % 2) != side ) {
9510             index++;
9511         }
9512
9513         while( index < forwardMostMove ) {
9514             /* Check to see if engine is in book */
9515             int depth = pvInfoList[index].depth;
9516             int score = pvInfoList[index].score;
9517             int in_book = 0;
9518
9519             if( depth <= 2 ) {
9520                 in_book = 1;
9521             }
9522             else if( score == 0 && depth == 63 ) {
9523                 in_book = 1; /* Zappa */
9524             }
9525             else if( score == 2 && depth == 99 ) {
9526                 in_book = 1; /* Abrok */
9527             }
9528
9529             has_book_hit += in_book;
9530
9531             if( ! in_book ) {
9532                 result = index;
9533
9534                 break;
9535             }
9536
9537             index += 2;
9538         }
9539     }
9540
9541     return result;
9542 }
9543
9544 /* [AS] */
9545 void GetOutOfBookInfo( char * buf )
9546 {
9547     int oob[2];
9548     int i;
9549     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9550
9551     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9552     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9553
9554     *buf = '\0';
9555
9556     if( oob[0] >= 0 || oob[1] >= 0 ) {
9557         for( i=0; i<2; i++ ) {
9558             int idx = oob[i];
9559
9560             if( idx >= 0 ) {
9561                 if( i > 0 && oob[0] >= 0 ) {
9562                     strcat( buf, "   " );
9563                 }
9564
9565                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9566                 sprintf( buf+strlen(buf), "%s%.2f",
9567                     pvInfoList[idx].score >= 0 ? "+" : "",
9568                     pvInfoList[idx].score / 100.0 );
9569             }
9570         }
9571     }
9572 }
9573
9574 /* Save game in PGN style and close the file */
9575 int
9576 SaveGamePGN(f)
9577      FILE *f;
9578 {
9579     int i, offset, linelen, newblock;
9580     time_t tm;
9581 //    char *movetext;
9582     char numtext[32];
9583     int movelen, numlen, blank;
9584     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9585
9586     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9587
9588     tm = time((time_t *) NULL);
9589
9590     PrintPGNTags(f, &gameInfo);
9591
9592     if (backwardMostMove > 0 || startedFromSetupPosition) {
9593         char *fen = PositionToFEN(backwardMostMove, NULL);
9594         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9595         fprintf(f, "\n{--------------\n");
9596         PrintPosition(f, backwardMostMove);
9597         fprintf(f, "--------------}\n");
9598         free(fen);
9599     }
9600     else {
9601         /* [AS] Out of book annotation */
9602         if( appData.saveOutOfBookInfo ) {
9603             char buf[64];
9604
9605             GetOutOfBookInfo( buf );
9606
9607             if( buf[0] != '\0' ) {
9608                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9609             }
9610         }
9611
9612         fprintf(f, "\n");
9613     }
9614
9615     i = backwardMostMove;
9616     linelen = 0;
9617     newblock = TRUE;
9618
9619     while (i < forwardMostMove) {
9620         /* Print comments preceding this move */
9621         if (commentList[i] != NULL) {
9622             if (linelen > 0) fprintf(f, "\n");
9623             fprintf(f, "{\n%s}\n", commentList[i]);
9624             linelen = 0;
9625             newblock = TRUE;
9626         }
9627
9628         /* Format move number */
9629         if ((i % 2) == 0) {
9630             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9631         } else {
9632             if (newblock) {
9633                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9634             } else {
9635                 numtext[0] = NULLCHAR;
9636             }
9637         }
9638         numlen = strlen(numtext);
9639         newblock = FALSE;
9640
9641         /* Print move number */
9642         blank = linelen > 0 && numlen > 0;
9643         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9644             fprintf(f, "\n");
9645             linelen = 0;
9646             blank = 0;
9647         }
9648         if (blank) {
9649             fprintf(f, " ");
9650             linelen++;
9651         }
9652         fprintf(f, numtext);
9653         linelen += numlen;
9654
9655         /* Get move */
9656         strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited
9657         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9658         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9659                 int p = movelen - 1;
9660                 if(move_buffer[p] == ' ') p--;
9661                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9662                     while(p && move_buffer[--p] != '(');
9663                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9664                 }
9665         }
9666
9667         /* Print move */
9668         blank = linelen > 0 && movelen > 0;
9669         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9670             fprintf(f, "\n");
9671             linelen = 0;
9672             blank = 0;
9673         }
9674         if (blank) {
9675             fprintf(f, " ");
9676             linelen++;
9677         }
9678         fprintf(f, move_buffer);
9679         linelen += movelen;
9680
9681         /* [AS] Add PV info if present */
9682         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9683             /* [HGM] add time */
9684             char buf[MSG_SIZ]; int seconds = 0;
9685
9686 #if 1
9687             if(i >= backwardMostMove) {
9688                 if(WhiteOnMove(i))
9689                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9690                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9691                 else
9692                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9693                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9694             }
9695             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9696 #else
9697             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9698 #endif
9699
9700             if( seconds <= 0) buf[0] = 0; else
9701             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9702                 seconds = (seconds + 4)/10; // round to full seconds
9703                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9704                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9705             }
9706
9707             sprintf( move_buffer, "{%s%.2f/%d%s}",
9708                 pvInfoList[i].score >= 0 ? "+" : "",
9709                 pvInfoList[i].score / 100.0,
9710                 pvInfoList[i].depth,
9711                 buf );
9712
9713             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9714
9715             /* Print score/depth */
9716             blank = linelen > 0 && movelen > 0;
9717             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9718                 fprintf(f, "\n");
9719                 linelen = 0;
9720                 blank = 0;
9721             }
9722             if (blank) {
9723                 fprintf(f, " ");
9724                 linelen++;
9725             }
9726             fprintf(f, move_buffer);
9727             linelen += movelen;
9728         }
9729
9730         i++;
9731     }
9732
9733     /* Start a new line */
9734     if (linelen > 0) fprintf(f, "\n");
9735
9736     /* Print comments after last move */
9737     if (commentList[i] != NULL) {
9738         fprintf(f, "{\n%s}\n", commentList[i]);
9739     }
9740
9741     /* Print result */
9742     if (gameInfo.resultDetails != NULL &&
9743         gameInfo.resultDetails[0] != NULLCHAR) {
9744         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9745                 PGNResult(gameInfo.result));
9746     } else {
9747         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9748     }
9749
9750     fclose(f);
9751     return TRUE;
9752 }
9753
9754 /* Save game in old style and close the file */
9755 int
9756 SaveGameOldStyle(f)
9757      FILE *f;
9758 {
9759     int i, offset;
9760     time_t tm;
9761
9762     tm = time((time_t *) NULL);
9763
9764     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9765     PrintOpponents(f);
9766
9767     if (backwardMostMove > 0 || startedFromSetupPosition) {
9768         fprintf(f, "\n[--------------\n");
9769         PrintPosition(f, backwardMostMove);
9770         fprintf(f, "--------------]\n");
9771     } else {
9772         fprintf(f, "\n");
9773     }
9774
9775     i = backwardMostMove;
9776     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9777
9778     while (i < forwardMostMove) {
9779         if (commentList[i] != NULL) {
9780             fprintf(f, "[%s]\n", commentList[i]);
9781         }
9782
9783         if ((i % 2) == 1) {
9784             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9785             i++;
9786         } else {
9787             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9788             i++;
9789             if (commentList[i] != NULL) {
9790                 fprintf(f, "\n");
9791                 continue;
9792             }
9793             if (i >= forwardMostMove) {
9794                 fprintf(f, "\n");
9795                 break;
9796             }
9797             fprintf(f, "%s\n", parseList[i]);
9798             i++;
9799         }
9800     }
9801
9802     if (commentList[i] != NULL) {
9803         fprintf(f, "[%s]\n", commentList[i]);
9804     }
9805
9806     /* This isn't really the old style, but it's close enough */
9807     if (gameInfo.resultDetails != NULL &&
9808         gameInfo.resultDetails[0] != NULLCHAR) {
9809         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9810                 gameInfo.resultDetails);
9811     } else {
9812         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9813     }
9814
9815     fclose(f);
9816     return TRUE;
9817 }
9818
9819 /* Save the current game to open file f and close the file */
9820 int
9821 SaveGame(f, dummy, dummy2)
9822      FILE *f;
9823      int dummy;
9824      char *dummy2;
9825 {
9826     if (gameMode == EditPosition) EditPositionDone();
9827     if (appData.oldSaveStyle)
9828       return SaveGameOldStyle(f);
9829     else
9830       return SaveGamePGN(f);
9831 }
9832
9833 /* Save the current position to the given file */
9834 int
9835 SavePositionToFile(filename)
9836      char *filename;
9837 {
9838     FILE *f;
9839     char buf[MSG_SIZ];
9840
9841     if (strcmp(filename, "-") == 0) {
9842         return SavePosition(stdout, 0, NULL);
9843     } else {
9844         f = fopen(filename, "a");
9845         if (f == NULL) {
9846             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9847             DisplayError(buf, errno);
9848             return FALSE;
9849         } else {
9850             SavePosition(f, 0, NULL);
9851             return TRUE;
9852         }
9853     }
9854 }
9855
9856 /* Save the current position to the given open file and close the file */
9857 int
9858 SavePosition(f, dummy, dummy2)
9859      FILE *f;
9860      int dummy;
9861      char *dummy2;
9862 {
9863     time_t tm;
9864     char *fen;
9865
9866     if (appData.oldSaveStyle) {
9867         tm = time((time_t *) NULL);
9868
9869         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9870         PrintOpponents(f);
9871         fprintf(f, "[--------------\n");
9872         PrintPosition(f, currentMove);
9873         fprintf(f, "--------------]\n");
9874     } else {
9875         fen = PositionToFEN(currentMove, NULL);
9876         fprintf(f, "%s\n", fen);
9877         free(fen);
9878     }
9879     fclose(f);
9880     return TRUE;
9881 }
9882
9883 void
9884 ReloadCmailMsgEvent(unregister)
9885      int unregister;
9886 {
9887 #if !WIN32
9888     static char *inFilename = NULL;
9889     static char *outFilename;
9890     int i;
9891     struct stat inbuf, outbuf;
9892     int status;
9893
9894     /* Any registered moves are unregistered if unregister is set, */
9895     /* i.e. invoked by the signal handler */
9896     if (unregister) {
9897         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9898             cmailMoveRegistered[i] = FALSE;
9899             if (cmailCommentList[i] != NULL) {
9900                 free(cmailCommentList[i]);
9901                 cmailCommentList[i] = NULL;
9902             }
9903         }
9904         nCmailMovesRegistered = 0;
9905     }
9906
9907     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9908         cmailResult[i] = CMAIL_NOT_RESULT;
9909     }
9910     nCmailResults = 0;
9911
9912     if (inFilename == NULL) {
9913         /* Because the filenames are static they only get malloced once  */
9914         /* and they never get freed                                      */
9915         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9916         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9917
9918         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9919         sprintf(outFilename, "%s.out", appData.cmailGameName);
9920     }
9921
9922     status = stat(outFilename, &outbuf);
9923     if (status < 0) {
9924         cmailMailedMove = FALSE;
9925     } else {
9926         status = stat(inFilename, &inbuf);
9927         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9928     }
9929
9930     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9931        counts the games, notes how each one terminated, etc.
9932
9933        It would be nice to remove this kludge and instead gather all
9934        the information while building the game list.  (And to keep it
9935        in the game list nodes instead of having a bunch of fixed-size
9936        parallel arrays.)  Note this will require getting each game's
9937        termination from the PGN tags, as the game list builder does
9938        not process the game moves.  --mann
9939        */
9940     cmailMsgLoaded = TRUE;
9941     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9942
9943     /* Load first game in the file or popup game menu */
9944     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9945
9946 #endif /* !WIN32 */
9947     return;
9948 }
9949
9950 int
9951 RegisterMove()
9952 {
9953     FILE *f;
9954     char string[MSG_SIZ];
9955
9956     if (   cmailMailedMove
9957         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9958         return TRUE;            /* Allow free viewing  */
9959     }
9960
9961     /* Unregister move to ensure that we don't leave RegisterMove        */
9962     /* with the move registered when the conditions for registering no   */
9963     /* longer hold                                                       */
9964     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9965         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9966         nCmailMovesRegistered --;
9967
9968         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9969           {
9970               free(cmailCommentList[lastLoadGameNumber - 1]);
9971               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9972           }
9973     }
9974
9975     if (cmailOldMove == -1) {
9976         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9977         return FALSE;
9978     }
9979
9980     if (currentMove > cmailOldMove + 1) {
9981         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9982         return FALSE;
9983     }
9984
9985     if (currentMove < cmailOldMove) {
9986         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9987         return FALSE;
9988     }
9989
9990     if (forwardMostMove > currentMove) {
9991         /* Silently truncate extra moves */
9992         TruncateGame();
9993     }
9994
9995     if (   (currentMove == cmailOldMove + 1)
9996         || (   (currentMove == cmailOldMove)
9997             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9998                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9999         if (gameInfo.result != GameUnfinished) {
10000             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10001         }
10002
10003         if (commentList[currentMove] != NULL) {
10004             cmailCommentList[lastLoadGameNumber - 1]
10005               = StrSave(commentList[currentMove]);
10006         }
10007         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10008
10009         if (appData.debugMode)
10010           fprintf(debugFP, "Saving %s for game %d\n",
10011                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10012
10013         sprintf(string,
10014                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10015
10016         f = fopen(string, "w");
10017         if (appData.oldSaveStyle) {
10018             SaveGameOldStyle(f); /* also closes the file */
10019
10020             sprintf(string, "%s.pos.out", appData.cmailGameName);
10021             f = fopen(string, "w");
10022             SavePosition(f, 0, NULL); /* also closes the file */
10023         } else {
10024             fprintf(f, "{--------------\n");
10025             PrintPosition(f, currentMove);
10026             fprintf(f, "--------------}\n\n");
10027
10028             SaveGame(f, 0, NULL); /* also closes the file*/
10029         }
10030
10031         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10032         nCmailMovesRegistered ++;
10033     } else if (nCmailGames == 1) {
10034         DisplayError(_("You have not made a move yet"), 0);
10035         return FALSE;
10036     }
10037
10038     return TRUE;
10039 }
10040
10041 void
10042 MailMoveEvent()
10043 {
10044 #if !WIN32
10045     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10046     FILE *commandOutput;
10047     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10048     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10049     int nBuffers;
10050     int i;
10051     int archived;
10052     char *arcDir;
10053
10054     if (! cmailMsgLoaded) {
10055         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10056         return;
10057     }
10058
10059     if (nCmailGames == nCmailResults) {
10060         DisplayError(_("No unfinished games"), 0);
10061         return;
10062     }
10063
10064 #if CMAIL_PROHIBIT_REMAIL
10065     if (cmailMailedMove) {
10066         sprintf(msg, _("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);
10067         DisplayError(msg, 0);
10068         return;
10069     }
10070 #endif
10071
10072     if (! (cmailMailedMove || RegisterMove())) return;
10073
10074     if (   cmailMailedMove
10075         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10076         sprintf(string, partCommandString,
10077                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10078         commandOutput = popen(string, "r");
10079
10080         if (commandOutput == NULL) {
10081             DisplayError(_("Failed to invoke cmail"), 0);
10082         } else {
10083             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10084                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10085             }
10086             if (nBuffers > 1) {
10087                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10088                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10089                 nBytes = MSG_SIZ - 1;
10090             } else {
10091                 (void) memcpy(msg, buffer, nBytes);
10092             }
10093             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10094
10095             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10096                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10097
10098                 archived = TRUE;
10099                 for (i = 0; i < nCmailGames; i ++) {
10100                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10101                         archived = FALSE;
10102                     }
10103                 }
10104                 if (   archived
10105                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10106                         != NULL)) {
10107                     sprintf(buffer, "%s/%s.%s.archive",
10108                             arcDir,
10109                             appData.cmailGameName,
10110                             gameInfo.date);
10111                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10112                     cmailMsgLoaded = FALSE;
10113                 }
10114             }
10115
10116             DisplayInformation(msg);
10117             pclose(commandOutput);
10118         }
10119     } else {
10120         if ((*cmailMsg) != '\0') {
10121             DisplayInformation(cmailMsg);
10122         }
10123     }
10124
10125     return;
10126 #endif /* !WIN32 */
10127 }
10128
10129 char *
10130 CmailMsg()
10131 {
10132 #if WIN32
10133     return NULL;
10134 #else
10135     int  prependComma = 0;
10136     char number[5];
10137     char string[MSG_SIZ];       /* Space for game-list */
10138     int  i;
10139
10140     if (!cmailMsgLoaded) return "";
10141
10142     if (cmailMailedMove) {
10143         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10144     } else {
10145         /* Create a list of games left */
10146         sprintf(string, "[");
10147         for (i = 0; i < nCmailGames; i ++) {
10148             if (! (   cmailMoveRegistered[i]
10149                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10150                 if (prependComma) {
10151                     sprintf(number, ",%d", i + 1);
10152                 } else {
10153                     sprintf(number, "%d", i + 1);
10154                     prependComma = 1;
10155                 }
10156
10157                 strcat(string, number);
10158             }
10159         }
10160         strcat(string, "]");
10161
10162         if (nCmailMovesRegistered + nCmailResults == 0) {
10163             switch (nCmailGames) {
10164               case 1:
10165                 sprintf(cmailMsg,
10166                         _("Still need to make move for game\n"));
10167                 break;
10168
10169               case 2:
10170                 sprintf(cmailMsg,
10171                         _("Still need to make moves for both games\n"));
10172                 break;
10173
10174               default:
10175                 sprintf(cmailMsg,
10176                         _("Still need to make moves for all %d games\n"),
10177                         nCmailGames);
10178                 break;
10179             }
10180         } else {
10181             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10182               case 1:
10183                 sprintf(cmailMsg,
10184                         _("Still need to make a move for game %s\n"),
10185                         string);
10186                 break;
10187
10188               case 0:
10189                 if (nCmailResults == nCmailGames) {
10190                     sprintf(cmailMsg, _("No unfinished games\n"));
10191                 } else {
10192                     sprintf(cmailMsg, _("Ready to send mail\n"));
10193                 }
10194                 break;
10195
10196               default:
10197                 sprintf(cmailMsg,
10198                         _("Still need to make moves for games %s\n"),
10199                         string);
10200             }
10201         }
10202     }
10203     return cmailMsg;
10204 #endif /* WIN32 */
10205 }
10206
10207 void
10208 ResetGameEvent()
10209 {
10210     if (gameMode == Training)
10211       SetTrainingModeOff();
10212
10213     Reset(TRUE, TRUE);
10214     cmailMsgLoaded = FALSE;
10215     if (appData.icsActive) {
10216       SendToICS(ics_prefix);
10217       SendToICS("refresh\n");
10218     }
10219 }
10220
10221 void
10222 ExitEvent(status)
10223      int status;
10224 {
10225     exiting++;
10226     if (exiting > 2) {
10227       /* Give up on clean exit */
10228       exit(status);
10229     }
10230     if (exiting > 1) {
10231       /* Keep trying for clean exit */
10232       return;
10233     }
10234
10235     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10236
10237     if (telnetISR != NULL) {
10238       RemoveInputSource(telnetISR);
10239     }
10240     if (icsPR != NoProc) {
10241       DestroyChildProcess(icsPR, TRUE);
10242     }
10243 #if 0
10244     /* Save game if resource set and not already saved by GameEnds() */
10245     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10246                              && forwardMostMove > 0) {
10247       if (*appData.saveGameFile != NULLCHAR) {
10248         SaveGameToFile(appData.saveGameFile, TRUE);
10249       } else if (appData.autoSaveGames) {
10250         AutoSaveGame();
10251       }
10252       if (*appData.savePositionFile != NULLCHAR) {
10253         SavePositionToFile(appData.savePositionFile);
10254       }
10255     }
10256     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10257 #else
10258     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10259     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10260 #endif
10261     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10262     /* make sure this other one finishes before killing it!                  */
10263     if(endingGame) { int count = 0;
10264         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10265         while(endingGame && count++ < 10) DoSleep(1);
10266         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10267     }
10268
10269     /* Kill off chess programs */
10270     if (first.pr != NoProc) {
10271         ExitAnalyzeMode();
10272
10273         DoSleep( appData.delayBeforeQuit );
10274         SendToProgram("quit\n", &first);
10275         DoSleep( appData.delayAfterQuit );
10276         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10277     }
10278     if (second.pr != NoProc) {
10279         DoSleep( appData.delayBeforeQuit );
10280         SendToProgram("quit\n", &second);
10281         DoSleep( appData.delayAfterQuit );
10282         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10283     }
10284     if (first.isr != NULL) {
10285         RemoveInputSource(first.isr);
10286     }
10287     if (second.isr != NULL) {
10288         RemoveInputSource(second.isr);
10289     }
10290
10291     ShutDownFrontEnd();
10292     exit(status);
10293 }
10294
10295 void
10296 PauseEvent()
10297 {
10298     if (appData.debugMode)
10299         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10300     if (pausing) {
10301         pausing = FALSE;
10302         ModeHighlight();
10303         if (gameMode == MachinePlaysWhite ||
10304             gameMode == MachinePlaysBlack) {
10305             StartClocks();
10306         } else {
10307             DisplayBothClocks();
10308         }
10309         if (gameMode == PlayFromGameFile) {
10310             if (appData.timeDelay >= 0)
10311                 AutoPlayGameLoop();
10312         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10313             Reset(FALSE, TRUE);
10314             SendToICS(ics_prefix);
10315             SendToICS("refresh\n");
10316         } else if (currentMove < forwardMostMove) {
10317             ForwardInner(forwardMostMove);
10318         }
10319         pauseExamInvalid = FALSE;
10320     } else {
10321         switch (gameMode) {
10322           default:
10323             return;
10324           case IcsExamining:
10325             pauseExamForwardMostMove = forwardMostMove;
10326             pauseExamInvalid = FALSE;
10327             /* fall through */
10328           case IcsObserving:
10329           case IcsPlayingWhite:
10330           case IcsPlayingBlack:
10331             pausing = TRUE;
10332             ModeHighlight();
10333             return;
10334           case PlayFromGameFile:
10335             (void) StopLoadGameTimer();
10336             pausing = TRUE;
10337             ModeHighlight();
10338             break;
10339           case BeginningOfGame:
10340             if (appData.icsActive) return;
10341             /* else fall through */
10342           case MachinePlaysWhite:
10343           case MachinePlaysBlack:
10344           case TwoMachinesPlay:
10345             if (forwardMostMove == 0)
10346               return;           /* don't pause if no one has moved */
10347             if ((gameMode == MachinePlaysWhite &&
10348                  !WhiteOnMove(forwardMostMove)) ||
10349                 (gameMode == MachinePlaysBlack &&
10350                  WhiteOnMove(forwardMostMove))) {
10351                 StopClocks();
10352             }
10353             pausing = TRUE;
10354             ModeHighlight();
10355             break;
10356         }
10357     }
10358 }
10359
10360 void
10361 EditCommentEvent()
10362 {
10363     char title[MSG_SIZ];
10364
10365     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10366         strcpy(title, _("Edit comment"));
10367     } else {
10368         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10369                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10370                 parseList[currentMove - 1]);
10371     }
10372
10373     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10374 }
10375
10376
10377 void
10378 EditTagsEvent()
10379 {
10380     char *tags = PGNTags(&gameInfo);
10381     EditTagsPopUp(tags);
10382     free(tags);
10383 }
10384
10385 void
10386 AnalyzeModeEvent()
10387 {
10388     if (appData.noChessProgram || gameMode == AnalyzeMode)
10389       return;
10390
10391     if (gameMode != AnalyzeFile) {
10392         if (!appData.icsEngineAnalyze) {
10393                EditGameEvent();
10394                if (gameMode != EditGame) return;
10395         }
10396         ResurrectChessProgram();
10397         SendToProgram("analyze\n", &first);
10398         first.analyzing = TRUE;
10399         /*first.maybeThinking = TRUE;*/
10400         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10401         AnalysisPopUp(_("Analysis"),
10402                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10403     }
10404     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10405     pausing = FALSE;
10406     ModeHighlight();
10407     SetGameInfo();
10408
10409     StartAnalysisClock();
10410     GetTimeMark(&lastNodeCountTime);
10411     lastNodeCount = 0;
10412 }
10413
10414 void
10415 AnalyzeFileEvent()
10416 {
10417     if (appData.noChessProgram || gameMode == AnalyzeFile)
10418       return;
10419
10420     if (gameMode != AnalyzeMode) {
10421         EditGameEvent();
10422         if (gameMode != EditGame) return;
10423         ResurrectChessProgram();
10424         SendToProgram("analyze\n", &first);
10425         first.analyzing = TRUE;
10426         /*first.maybeThinking = TRUE;*/
10427         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10428         AnalysisPopUp(_("Analysis"),
10429                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10430     }
10431     gameMode = AnalyzeFile;
10432     pausing = FALSE;
10433     ModeHighlight();
10434     SetGameInfo();
10435
10436     StartAnalysisClock();
10437     GetTimeMark(&lastNodeCountTime);
10438     lastNodeCount = 0;
10439 }
10440
10441 void
10442 MachineWhiteEvent()
10443 {
10444     char buf[MSG_SIZ];
10445     char *bookHit = NULL;
10446
10447     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10448       return;
10449
10450
10451     if (gameMode == PlayFromGameFile ||
10452         gameMode == TwoMachinesPlay  ||
10453         gameMode == Training         ||
10454         gameMode == AnalyzeMode      ||
10455         gameMode == EndOfGame)
10456         EditGameEvent();
10457
10458     if (gameMode == EditPosition)
10459         EditPositionDone();
10460
10461     if (!WhiteOnMove(currentMove)) {
10462         DisplayError(_("It is not White's turn"), 0);
10463         return;
10464     }
10465
10466     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10467       ExitAnalyzeMode();
10468
10469     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10470         gameMode == AnalyzeFile)
10471         TruncateGame();
10472
10473     ResurrectChessProgram();    /* in case it isn't running */
10474     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10475         gameMode = MachinePlaysWhite;
10476         ResetClocks();
10477     } else
10478     gameMode = MachinePlaysWhite;
10479     pausing = FALSE;
10480     ModeHighlight();
10481     SetGameInfo();
10482     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10483     DisplayTitle(buf);
10484     if (first.sendName) {
10485       sprintf(buf, "name %s\n", gameInfo.black);
10486       SendToProgram(buf, &first);
10487     }
10488     if (first.sendTime) {
10489       if (first.useColors) {
10490         SendToProgram("black\n", &first); /*gnu kludge*/
10491       }
10492       SendTimeRemaining(&first, TRUE);
10493     }
10494     if (first.useColors) {
10495       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10496     }
10497     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10498     SetMachineThinkingEnables();
10499     first.maybeThinking = TRUE;
10500     StartClocks();
10501
10502     if (appData.autoFlipView && !flipView) {
10503       flipView = !flipView;
10504       DrawPosition(FALSE, NULL);
10505       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10506     }
10507
10508     if(bookHit) { // [HGM] book: simulate book reply
10509         static char bookMove[MSG_SIZ]; // a bit generous?
10510
10511         programStats.nodes = programStats.depth = programStats.time =
10512         programStats.score = programStats.got_only_move = 0;
10513         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10514
10515         strcpy(bookMove, "move ");
10516         strcat(bookMove, bookHit);
10517         HandleMachineMove(bookMove, &first);
10518     }
10519 }
10520
10521 void
10522 MachineBlackEvent()
10523 {
10524     char buf[MSG_SIZ];
10525    char *bookHit = NULL;
10526
10527     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10528         return;
10529
10530
10531     if (gameMode == PlayFromGameFile ||
10532         gameMode == TwoMachinesPlay  ||
10533         gameMode == Training         ||
10534         gameMode == AnalyzeMode      ||
10535         gameMode == EndOfGame)
10536         EditGameEvent();
10537
10538     if (gameMode == EditPosition)
10539         EditPositionDone();
10540
10541     if (WhiteOnMove(currentMove)) {
10542         DisplayError(_("It is not Black's turn"), 0);
10543         return;
10544     }
10545
10546     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10547       ExitAnalyzeMode();
10548
10549     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10550         gameMode == AnalyzeFile)
10551         TruncateGame();
10552
10553     ResurrectChessProgram();    /* in case it isn't running */
10554     gameMode = MachinePlaysBlack;
10555     pausing = FALSE;
10556     ModeHighlight();
10557     SetGameInfo();
10558     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10559     DisplayTitle(buf);
10560     if (first.sendName) {
10561       sprintf(buf, "name %s\n", gameInfo.white);
10562       SendToProgram(buf, &first);
10563     }
10564     if (first.sendTime) {
10565       if (first.useColors) {
10566         SendToProgram("white\n", &first); /*gnu kludge*/
10567       }
10568       SendTimeRemaining(&first, FALSE);
10569     }
10570     if (first.useColors) {
10571       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10572     }
10573     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10574     SetMachineThinkingEnables();
10575     first.maybeThinking = TRUE;
10576     StartClocks();
10577
10578     if (appData.autoFlipView && flipView) {
10579       flipView = !flipView;
10580       DrawPosition(FALSE, NULL);
10581       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10582     }
10583     if(bookHit) { // [HGM] book: simulate book reply
10584         static char bookMove[MSG_SIZ]; // a bit generous?
10585
10586         programStats.nodes = programStats.depth = programStats.time =
10587         programStats.score = programStats.got_only_move = 0;
10588         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10589
10590         strcpy(bookMove, "move ");
10591         strcat(bookMove, bookHit);
10592         HandleMachineMove(bookMove, &first);
10593     }
10594 }
10595
10596
10597 void
10598 DisplayTwoMachinesTitle()
10599 {
10600     char buf[MSG_SIZ];
10601     if (appData.matchGames > 0) {
10602         if (first.twoMachinesColor[0] == 'w') {
10603             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10604                     gameInfo.white, gameInfo.black,
10605                     first.matchWins, second.matchWins,
10606                     matchGame - 1 - (first.matchWins + second.matchWins));
10607         } else {
10608             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10609                     gameInfo.white, gameInfo.black,
10610                     second.matchWins, first.matchWins,
10611                     matchGame - 1 - (first.matchWins + second.matchWins));
10612         }
10613     } else {
10614         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10615     }
10616     DisplayTitle(buf);
10617 }
10618
10619 void
10620 TwoMachinesEvent P((void))
10621 {
10622     int i;
10623     char buf[MSG_SIZ];
10624     ChessProgramState *onmove;
10625     char *bookHit = NULL;
10626
10627     if (appData.noChessProgram) return;
10628
10629     switch (gameMode) {
10630       case TwoMachinesPlay:
10631         return;
10632       case MachinePlaysWhite:
10633       case MachinePlaysBlack:
10634         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10635             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10636             return;
10637         }
10638         /* fall through */
10639       case BeginningOfGame:
10640       case PlayFromGameFile:
10641       case EndOfGame:
10642         EditGameEvent();
10643         if (gameMode != EditGame) return;
10644         break;
10645       case EditPosition:
10646         EditPositionDone();
10647         break;
10648       case AnalyzeMode:
10649       case AnalyzeFile:
10650         ExitAnalyzeMode();
10651         break;
10652       case EditGame:
10653       default:
10654         break;
10655     }
10656
10657     forwardMostMove = currentMove;
10658     ResurrectChessProgram();    /* in case first program isn't running */
10659
10660     if (second.pr == NULL) {
10661         StartChessProgram(&second);
10662         if (second.protocolVersion == 1) {
10663           TwoMachinesEventIfReady();
10664         } else {
10665           /* kludge: allow timeout for initial "feature" command */
10666           FreezeUI();
10667           DisplayMessage("", _("Starting second chess program"));
10668           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10669         }
10670         return;
10671     }
10672     DisplayMessage("", "");
10673     InitChessProgram(&second, FALSE);
10674     SendToProgram("force\n", &second);
10675     if (startedFromSetupPosition) {
10676         SendBoard(&second, backwardMostMove);
10677     if (appData.debugMode) {
10678         fprintf(debugFP, "Two Machines\n");
10679     }
10680     }
10681     for (i = backwardMostMove; i < forwardMostMove; i++) {
10682         SendMoveToProgram(i, &second);
10683     }
10684
10685     gameMode = TwoMachinesPlay;
10686     pausing = FALSE;
10687     ModeHighlight();
10688     SetGameInfo();
10689     DisplayTwoMachinesTitle();
10690     firstMove = TRUE;
10691     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10692         onmove = &first;
10693     } else {
10694         onmove = &second;
10695     }
10696
10697     SendToProgram(first.computerString, &first);
10698     if (first.sendName) {
10699       sprintf(buf, "name %s\n", second.tidy);
10700       SendToProgram(buf, &first);
10701     }
10702     SendToProgram(second.computerString, &second);
10703     if (second.sendName) {
10704       sprintf(buf, "name %s\n", first.tidy);
10705       SendToProgram(buf, &second);
10706     }
10707
10708     ResetClocks();
10709     if (!first.sendTime || !second.sendTime) {
10710         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10711         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10712     }
10713     if (onmove->sendTime) {
10714       if (onmove->useColors) {
10715         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10716       }
10717       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10718     }
10719     if (onmove->useColors) {
10720       SendToProgram(onmove->twoMachinesColor, onmove);
10721     }
10722     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10723 //    SendToProgram("go\n", onmove);
10724     onmove->maybeThinking = TRUE;
10725     SetMachineThinkingEnables();
10726
10727     StartClocks();
10728
10729     if(bookHit) { // [HGM] book: simulate book reply
10730         static char bookMove[MSG_SIZ]; // a bit generous?
10731
10732         programStats.nodes = programStats.depth = programStats.time =
10733         programStats.score = programStats.got_only_move = 0;
10734         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10735
10736         strcpy(bookMove, "move ");
10737         strcat(bookMove, bookHit);
10738         HandleMachineMove(bookMove, &first);
10739     }
10740 }
10741
10742 void
10743 TrainingEvent()
10744 {
10745     if (gameMode == Training) {
10746       SetTrainingModeOff();
10747       gameMode = PlayFromGameFile;
10748       DisplayMessage("", _("Training mode off"));
10749     } else {
10750       gameMode = Training;
10751       animateTraining = appData.animate;
10752
10753       /* make sure we are not already at the end of the game */
10754       if (currentMove < forwardMostMove) {
10755         SetTrainingModeOn();
10756         DisplayMessage("", _("Training mode on"));
10757       } else {
10758         gameMode = PlayFromGameFile;
10759         DisplayError(_("Already at end of game"), 0);
10760       }
10761     }
10762     ModeHighlight();
10763 }
10764
10765 void
10766 IcsClientEvent()
10767 {
10768     if (!appData.icsActive) return;
10769     switch (gameMode) {
10770       case IcsPlayingWhite:
10771       case IcsPlayingBlack:
10772       case IcsObserving:
10773       case IcsIdle:
10774       case BeginningOfGame:
10775       case IcsExamining:
10776         return;
10777
10778       case EditGame:
10779         break;
10780
10781       case EditPosition:
10782         EditPositionDone();
10783         break;
10784
10785       case AnalyzeMode:
10786       case AnalyzeFile:
10787         ExitAnalyzeMode();
10788         break;
10789
10790       default:
10791         EditGameEvent();
10792         break;
10793     }
10794
10795     gameMode = IcsIdle;
10796     ModeHighlight();
10797     return;
10798 }
10799
10800
10801 void
10802 EditGameEvent()
10803 {
10804     int i;
10805
10806     switch (gameMode) {
10807       case Training:
10808         SetTrainingModeOff();
10809         break;
10810       case MachinePlaysWhite:
10811       case MachinePlaysBlack:
10812       case BeginningOfGame:
10813         SendToProgram("force\n", &first);
10814         SetUserThinkingEnables();
10815         break;
10816       case PlayFromGameFile:
10817         (void) StopLoadGameTimer();
10818         if (gameFileFP != NULL) {
10819             gameFileFP = NULL;
10820         }
10821         break;
10822       case EditPosition:
10823         EditPositionDone();
10824         break;
10825       case AnalyzeMode:
10826       case AnalyzeFile:
10827         ExitAnalyzeMode();
10828         SendToProgram("force\n", &first);
10829         break;
10830       case TwoMachinesPlay:
10831         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10832         ResurrectChessProgram();
10833         SetUserThinkingEnables();
10834         break;
10835       case EndOfGame:
10836         ResurrectChessProgram();
10837         break;
10838       case IcsPlayingBlack:
10839       case IcsPlayingWhite:
10840         DisplayError(_("Warning: You are still playing a game"), 0);
10841         break;
10842       case IcsObserving:
10843         DisplayError(_("Warning: You are still observing a game"), 0);
10844         break;
10845       case IcsExamining:
10846         DisplayError(_("Warning: You are still examining a game"), 0);
10847         break;
10848       case IcsIdle:
10849         break;
10850       case EditGame:
10851       default:
10852         return;
10853     }
10854
10855     pausing = FALSE;
10856     StopClocks();
10857     first.offeredDraw = second.offeredDraw = 0;
10858
10859     if (gameMode == PlayFromGameFile) {
10860         whiteTimeRemaining = timeRemaining[0][currentMove];
10861         blackTimeRemaining = timeRemaining[1][currentMove];
10862         DisplayTitle("");
10863     }
10864
10865     if (gameMode == MachinePlaysWhite ||
10866         gameMode == MachinePlaysBlack ||
10867         gameMode == TwoMachinesPlay ||
10868         gameMode == EndOfGame) {
10869         i = forwardMostMove;
10870         while (i > currentMove) {
10871             SendToProgram("undo\n", &first);
10872             i--;
10873         }
10874         whiteTimeRemaining = timeRemaining[0][currentMove];
10875         blackTimeRemaining = timeRemaining[1][currentMove];
10876         DisplayBothClocks();
10877         if (whiteFlag || blackFlag) {
10878             whiteFlag = blackFlag = 0;
10879         }
10880         DisplayTitle("");
10881     }
10882
10883     gameMode = EditGame;
10884     ModeHighlight();
10885     SetGameInfo();
10886 }
10887
10888
10889 void
10890 EditPositionEvent()
10891 {
10892     if (gameMode == EditPosition) {
10893         EditGameEvent();
10894         return;
10895     }
10896
10897     EditGameEvent();
10898     if (gameMode != EditGame) return;
10899
10900     gameMode = EditPosition;
10901     ModeHighlight();
10902     SetGameInfo();
10903     if (currentMove > 0)
10904       CopyBoard(boards[0], boards[currentMove]);
10905
10906     blackPlaysFirst = !WhiteOnMove(currentMove);
10907     ResetClocks();
10908     currentMove = forwardMostMove = backwardMostMove = 0;
10909     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10910     DisplayMove(-1);
10911 }
10912
10913 void
10914 ExitAnalyzeMode()
10915 {
10916     /* [DM] icsEngineAnalyze - possible call from other functions */
10917     if (appData.icsEngineAnalyze) {
10918         appData.icsEngineAnalyze = FALSE;
10919
10920         DisplayMessage("",_("Close ICS engine analyze..."));
10921     }
10922     if (first.analysisSupport && first.analyzing) {
10923       SendToProgram("exit\n", &first);
10924       first.analyzing = FALSE;
10925     }
10926     AnalysisPopDown();
10927     thinkOutput[0] = NULLCHAR;
10928 }
10929
10930 void
10931 EditPositionDone()
10932 {
10933     startedFromSetupPosition = TRUE;
10934     InitChessProgram(&first, FALSE);
10935     SendToProgram("force\n", &first);
10936     if (blackPlaysFirst) {
10937         strcpy(moveList[0], "");
10938         strcpy(parseList[0], "");
10939         currentMove = forwardMostMove = backwardMostMove = 1;
10940         CopyBoard(boards[1], boards[0]);
10941         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10942         { int i;
10943           epStatus[1] = epStatus[0];
10944           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10945         }
10946     } else {
10947         currentMove = forwardMostMove = backwardMostMove = 0;
10948     }
10949     SendBoard(&first, forwardMostMove);
10950     if (appData.debugMode) {
10951         fprintf(debugFP, "EditPosDone\n");
10952     }
10953     DisplayTitle("");
10954     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10955     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10956     gameMode = EditGame;
10957     ModeHighlight();
10958     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10959     ClearHighlights(); /* [AS] */
10960 }
10961
10962 /* Pause for `ms' milliseconds */
10963 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10964 void
10965 TimeDelay(ms)
10966      long ms;
10967 {
10968     TimeMark m1, m2;
10969
10970     GetTimeMark(&m1);
10971     do {
10972         GetTimeMark(&m2);
10973     } while (SubtractTimeMarks(&m2, &m1) < ms);
10974 }
10975
10976 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10977 void
10978 SendMultiLineToICS(buf)
10979      char *buf;
10980 {
10981     char temp[MSG_SIZ+1], *p;
10982     int len;
10983
10984     len = strlen(buf);
10985     if (len > MSG_SIZ)
10986       len = MSG_SIZ;
10987
10988     strncpy(temp, buf, len);
10989     temp[len] = 0;
10990
10991     p = temp;
10992     while (*p) {
10993         if (*p == '\n' || *p == '\r')
10994           *p = ' ';
10995         ++p;
10996     }
10997
10998     strcat(temp, "\n");
10999     SendToICS(temp);
11000     SendToPlayer(temp, strlen(temp));
11001 }
11002
11003 void
11004 SetWhiteToPlayEvent()
11005 {
11006     if (gameMode == EditPosition) {
11007         blackPlaysFirst = FALSE;
11008         DisplayBothClocks();    /* works because currentMove is 0 */
11009     } else if (gameMode == IcsExamining) {
11010         SendToICS(ics_prefix);
11011         SendToICS("tomove white\n");
11012     }
11013 }
11014
11015 void
11016 SetBlackToPlayEvent()
11017 {
11018     if (gameMode == EditPosition) {
11019         blackPlaysFirst = TRUE;
11020         currentMove = 1;        /* kludge */
11021         DisplayBothClocks();
11022         currentMove = 0;
11023     } else if (gameMode == IcsExamining) {
11024         SendToICS(ics_prefix);
11025         SendToICS("tomove black\n");
11026     }
11027 }
11028
11029 void
11030 EditPositionMenuEvent(selection, x, y)
11031      ChessSquare selection;
11032      int x, y;
11033 {
11034     char buf[MSG_SIZ];
11035     ChessSquare piece = boards[0][y][x];
11036
11037     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11038
11039     switch (selection) {
11040       case ClearBoard:
11041         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11042             SendToICS(ics_prefix);
11043             SendToICS("bsetup clear\n");
11044         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11045             SendToICS(ics_prefix);
11046             SendToICS("clearboard\n");
11047         } else {
11048             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11049                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11050                 for (y = 0; y < BOARD_HEIGHT; y++) {
11051                     if (gameMode == IcsExamining) {
11052                         if (boards[currentMove][y][x] != EmptySquare) {
11053                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11054                                     AAA + x, ONE + y);
11055                             SendToICS(buf);
11056                         }
11057                     } else {
11058                         boards[0][y][x] = p;
11059                     }
11060                 }
11061             }
11062         }
11063         if (gameMode == EditPosition) {
11064             DrawPosition(FALSE, boards[0]);
11065         }
11066         break;
11067
11068       case WhitePlay:
11069         SetWhiteToPlayEvent();
11070         break;
11071
11072       case BlackPlay:
11073         SetBlackToPlayEvent();
11074         break;
11075
11076       case EmptySquare:
11077         if (gameMode == IcsExamining) {
11078             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11079             SendToICS(buf);
11080         } else {
11081             boards[0][y][x] = EmptySquare;
11082             DrawPosition(FALSE, boards[0]);
11083         }
11084         break;
11085
11086       case PromotePiece:
11087         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11088            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11089             selection = (ChessSquare) (PROMOTED piece);
11090         } else if(piece == EmptySquare) selection = WhiteSilver;
11091         else selection = (ChessSquare)((int)piece - 1);
11092         goto defaultlabel;
11093
11094       case DemotePiece:
11095         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11096            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11097             selection = (ChessSquare) (DEMOTED piece);
11098         } else if(piece == EmptySquare) selection = BlackSilver;
11099         else selection = (ChessSquare)((int)piece + 1);
11100         goto defaultlabel;
11101
11102       case WhiteQueen:
11103       case BlackQueen:
11104         if(gameInfo.variant == VariantShatranj ||
11105            gameInfo.variant == VariantXiangqi  ||
11106            gameInfo.variant == VariantCourier    )
11107             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11108         goto defaultlabel;
11109
11110       case WhiteKing:
11111       case BlackKing:
11112         if(gameInfo.variant == VariantXiangqi)
11113             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11114         if(gameInfo.variant == VariantKnightmate)
11115             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11116       default:
11117         defaultlabel:
11118         if (gameMode == IcsExamining) {
11119             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11120                     PieceToChar(selection), AAA + x, ONE + y);
11121             SendToICS(buf);
11122         } else {
11123             boards[0][y][x] = selection;
11124             DrawPosition(FALSE, boards[0]);
11125         }
11126         break;
11127     }
11128 }
11129
11130
11131 void
11132 DropMenuEvent(selection, x, y)
11133      ChessSquare selection;
11134      int x, y;
11135 {
11136     ChessMove moveType;
11137
11138     switch (gameMode) {
11139       case IcsPlayingWhite:
11140       case MachinePlaysBlack:
11141         if (!WhiteOnMove(currentMove)) {
11142             DisplayMoveError(_("It is Black's turn"));
11143             return;
11144         }
11145         moveType = WhiteDrop;
11146         break;
11147       case IcsPlayingBlack:
11148       case MachinePlaysWhite:
11149         if (WhiteOnMove(currentMove)) {
11150             DisplayMoveError(_("It is White's turn"));
11151             return;
11152         }
11153         moveType = BlackDrop;
11154         break;
11155       case EditGame:
11156         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11157         break;
11158       default:
11159         return;
11160     }
11161
11162     if (moveType == BlackDrop && selection < BlackPawn) {
11163       selection = (ChessSquare) ((int) selection
11164                                  + (int) BlackPawn - (int) WhitePawn);
11165     }
11166     if (boards[currentMove][y][x] != EmptySquare) {
11167         DisplayMoveError(_("That square is occupied"));
11168         return;
11169     }
11170
11171     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11172 }
11173
11174 void
11175 AcceptEvent()
11176 {
11177     /* Accept a pending offer of any kind from opponent */
11178
11179     if (appData.icsActive) {
11180         SendToICS(ics_prefix);
11181         SendToICS("accept\n");
11182     } else if (cmailMsgLoaded) {
11183         if (currentMove == cmailOldMove &&
11184             commentList[cmailOldMove] != NULL &&
11185             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11186                    "Black offers a draw" : "White offers a draw")) {
11187             TruncateGame();
11188             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11189             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11190         } else {
11191             DisplayError(_("There is no pending offer on this move"), 0);
11192             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11193         }
11194     } else {
11195         /* Not used for offers from chess program */
11196     }
11197 }
11198
11199 void
11200 DeclineEvent()
11201 {
11202     /* Decline a pending offer of any kind from opponent */
11203
11204     if (appData.icsActive) {
11205         SendToICS(ics_prefix);
11206         SendToICS("decline\n");
11207     } else if (cmailMsgLoaded) {
11208         if (currentMove == cmailOldMove &&
11209             commentList[cmailOldMove] != NULL &&
11210             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11211                    "Black offers a draw" : "White offers a draw")) {
11212 #ifdef NOTDEF
11213             AppendComment(cmailOldMove, "Draw declined");
11214             DisplayComment(cmailOldMove - 1, "Draw declined");
11215 #endif /*NOTDEF*/
11216         } else {
11217             DisplayError(_("There is no pending offer on this move"), 0);
11218         }
11219     } else {
11220         /* Not used for offers from chess program */
11221     }
11222 }
11223
11224 void
11225 RematchEvent()
11226 {
11227     /* Issue ICS rematch command */
11228     if (appData.icsActive) {
11229         SendToICS(ics_prefix);
11230         SendToICS("rematch\n");
11231     }
11232 }
11233
11234 void
11235 CallFlagEvent()
11236 {
11237     /* Call your opponent's flag (claim a win on time) */
11238     if (appData.icsActive) {
11239         SendToICS(ics_prefix);
11240         SendToICS("flag\n");
11241     } else {
11242         switch (gameMode) {
11243           default:
11244             return;
11245           case MachinePlaysWhite:
11246             if (whiteFlag) {
11247                 if (blackFlag)
11248                   GameEnds(GameIsDrawn, "Both players ran out of time",
11249                            GE_PLAYER);
11250                 else
11251                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11252             } else {
11253                 DisplayError(_("Your opponent is not out of time"), 0);
11254             }
11255             break;
11256           case MachinePlaysBlack:
11257             if (blackFlag) {
11258                 if (whiteFlag)
11259                   GameEnds(GameIsDrawn, "Both players ran out of time",
11260                            GE_PLAYER);
11261                 else
11262                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11263             } else {
11264                 DisplayError(_("Your opponent is not out of time"), 0);
11265             }
11266             break;
11267         }
11268     }
11269 }
11270
11271 void
11272 DrawEvent()
11273 {
11274     /* Offer draw or accept pending draw offer from opponent */
11275
11276     if (appData.icsActive) {
11277         /* Note: tournament rules require draw offers to be
11278            made after you make your move but before you punch
11279            your clock.  Currently ICS doesn't let you do that;
11280            instead, you immediately punch your clock after making
11281            a move, but you can offer a draw at any time. */
11282
11283         SendToICS(ics_prefix);
11284         SendToICS("draw\n");
11285     } else if (cmailMsgLoaded) {
11286         if (currentMove == cmailOldMove &&
11287             commentList[cmailOldMove] != NULL &&
11288             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11289                    "Black offers a draw" : "White offers a draw")) {
11290             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11291             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11292         } else if (currentMove == cmailOldMove + 1) {
11293             char *offer = WhiteOnMove(cmailOldMove) ?
11294               "White offers a draw" : "Black offers a draw";
11295             AppendComment(currentMove, offer);
11296             DisplayComment(currentMove - 1, offer);
11297             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11298         } else {
11299             DisplayError(_("You must make your move before offering a draw"), 0);
11300             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11301         }
11302     } else if (first.offeredDraw) {
11303         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11304     } else {
11305         if (first.sendDrawOffers) {
11306             SendToProgram("draw\n", &first);
11307             userOfferedDraw = TRUE;
11308         }
11309     }
11310 }
11311
11312 void
11313 AdjournEvent()
11314 {
11315     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11316
11317     if (appData.icsActive) {
11318         SendToICS(ics_prefix);
11319         SendToICS("adjourn\n");
11320     } else {
11321         /* Currently GNU Chess doesn't offer or accept Adjourns */
11322     }
11323 }
11324
11325
11326 void
11327 AbortEvent()
11328 {
11329     /* Offer Abort or accept pending Abort offer from opponent */
11330
11331     if (appData.icsActive) {
11332         SendToICS(ics_prefix);
11333         SendToICS("abort\n");
11334     } else {
11335         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11336     }
11337 }
11338
11339 void
11340 ResignEvent()
11341 {
11342     /* Resign.  You can do this even if it's not your turn. */
11343
11344     if (appData.icsActive) {
11345         SendToICS(ics_prefix);
11346         SendToICS("resign\n");
11347     } else {
11348         switch (gameMode) {
11349           case MachinePlaysWhite:
11350             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11351             break;
11352           case MachinePlaysBlack:
11353             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11354             break;
11355           case EditGame:
11356             if (cmailMsgLoaded) {
11357                 TruncateGame();
11358                 if (WhiteOnMove(cmailOldMove)) {
11359                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11360                 } else {
11361                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11362                 }
11363                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11364             }
11365             break;
11366           default:
11367             break;
11368         }
11369     }
11370 }
11371
11372
11373 void
11374 StopObservingEvent()
11375 {
11376     /* Stop observing current games */
11377     SendToICS(ics_prefix);
11378     SendToICS("unobserve\n");
11379 }
11380
11381 void
11382 StopExaminingEvent()
11383 {
11384     /* Stop observing current game */
11385     SendToICS(ics_prefix);
11386     SendToICS("unexamine\n");
11387 }
11388
11389 void
11390 ForwardInner(target)
11391      int target;
11392 {
11393     int limit;
11394
11395     if (appData.debugMode)
11396         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11397                 target, currentMove, forwardMostMove);
11398
11399     if (gameMode == EditPosition)
11400       return;
11401
11402     if (gameMode == PlayFromGameFile && !pausing)
11403       PauseEvent();
11404
11405     if (gameMode == IcsExamining && pausing)
11406       limit = pauseExamForwardMostMove;
11407     else
11408       limit = forwardMostMove;
11409
11410     if (target > limit) target = limit;
11411
11412     if (target > 0 && moveList[target - 1][0]) {
11413         int fromX, fromY, toX, toY;
11414         toX = moveList[target - 1][2] - AAA;
11415         toY = moveList[target - 1][3] - ONE;
11416         if (moveList[target - 1][1] == '@') {
11417             if (appData.highlightLastMove) {
11418                 SetHighlights(-1, -1, toX, toY);
11419             }
11420         } else {
11421             fromX = moveList[target - 1][0] - AAA;
11422             fromY = moveList[target - 1][1] - ONE;
11423             if (target == currentMove + 1) {
11424                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11425             }
11426             if (appData.highlightLastMove) {
11427                 SetHighlights(fromX, fromY, toX, toY);
11428             }
11429         }
11430     }
11431     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11432         gameMode == Training || gameMode == PlayFromGameFile ||
11433         gameMode == AnalyzeFile) {
11434         while (currentMove < target) {
11435             SendMoveToProgram(currentMove++, &first);
11436         }
11437     } else {
11438         currentMove = target;
11439     }
11440
11441     if (gameMode == EditGame || gameMode == EndOfGame) {
11442         whiteTimeRemaining = timeRemaining[0][currentMove];
11443         blackTimeRemaining = timeRemaining[1][currentMove];
11444     }
11445     DisplayBothClocks();
11446     DisplayMove(currentMove - 1);
11447     DrawPosition(FALSE, boards[currentMove]);
11448     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11449     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11450         DisplayComment(currentMove - 1, commentList[currentMove]);
11451     }
11452 }
11453
11454
11455 void
11456 ForwardEvent()
11457 {
11458     if (gameMode == IcsExamining && !pausing) {
11459         SendToICS(ics_prefix);
11460         SendToICS("forward\n");
11461     } else {
11462         ForwardInner(currentMove + 1);
11463     }
11464 }
11465
11466 void
11467 ToEndEvent()
11468 {
11469     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11470         /* to optimze, we temporarily turn off analysis mode while we feed
11471          * the remaining moves to the engine. Otherwise we get analysis output
11472          * after each move.
11473          */
11474         if (first.analysisSupport) {
11475           SendToProgram("exit\nforce\n", &first);
11476           first.analyzing = FALSE;
11477         }
11478     }
11479
11480     if (gameMode == IcsExamining && !pausing) {
11481         SendToICS(ics_prefix);
11482         SendToICS("forward 999999\n");
11483     } else {
11484         ForwardInner(forwardMostMove);
11485     }
11486
11487     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11488         /* we have fed all the moves, so reactivate analysis mode */
11489         SendToProgram("analyze\n", &first);
11490         first.analyzing = TRUE;
11491         /*first.maybeThinking = TRUE;*/
11492         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11493     }
11494 }
11495
11496 void
11497 BackwardInner(target)
11498      int target;
11499 {
11500     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11501
11502     if (appData.debugMode)
11503         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11504                 target, currentMove, forwardMostMove);
11505
11506     if (gameMode == EditPosition) return;
11507     if (currentMove <= backwardMostMove) {
11508         ClearHighlights();
11509         DrawPosition(full_redraw, boards[currentMove]);
11510         return;
11511     }
11512     if (gameMode == PlayFromGameFile && !pausing)
11513       PauseEvent();
11514
11515     if (moveList[target][0]) {
11516         int fromX, fromY, toX, toY;
11517         toX = moveList[target][2] - AAA;
11518         toY = moveList[target][3] - ONE;
11519         if (moveList[target][1] == '@') {
11520             if (appData.highlightLastMove) {
11521                 SetHighlights(-1, -1, toX, toY);
11522             }
11523         } else {
11524             fromX = moveList[target][0] - AAA;
11525             fromY = moveList[target][1] - ONE;
11526             if (target == currentMove - 1) {
11527                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11528             }
11529             if (appData.highlightLastMove) {
11530                 SetHighlights(fromX, fromY, toX, toY);
11531             }
11532         }
11533     }
11534     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11535         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11536         while (currentMove > target) {
11537             SendToProgram("undo\n", &first);
11538             currentMove--;
11539         }
11540     } else {
11541         currentMove = target;
11542     }
11543
11544     if (gameMode == EditGame || gameMode == EndOfGame) {
11545         whiteTimeRemaining = timeRemaining[0][currentMove];
11546         blackTimeRemaining = timeRemaining[1][currentMove];
11547     }
11548     DisplayBothClocks();
11549     DisplayMove(currentMove - 1);
11550     DrawPosition(full_redraw, boards[currentMove]);
11551     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11552     // [HGM] PV info: routine tests if comment empty
11553     DisplayComment(currentMove - 1, commentList[currentMove]);
11554 }
11555
11556 void
11557 BackwardEvent()
11558 {
11559     if (gameMode == IcsExamining && !pausing) {
11560         SendToICS(ics_prefix);
11561         SendToICS("backward\n");
11562     } else {
11563         BackwardInner(currentMove - 1);
11564     }
11565 }
11566
11567 void
11568 ToStartEvent()
11569 {
11570     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11571         /* to optimze, we temporarily turn off analysis mode while we undo
11572          * all the moves. Otherwise we get analysis output after each undo.
11573          */
11574         if (first.analysisSupport) {
11575           SendToProgram("exit\nforce\n", &first);
11576           first.analyzing = FALSE;
11577         }
11578     }
11579
11580     if (gameMode == IcsExamining && !pausing) {
11581         SendToICS(ics_prefix);
11582         SendToICS("backward 999999\n");
11583     } else {
11584         BackwardInner(backwardMostMove);
11585     }
11586
11587     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11588         /* we have fed all the moves, so reactivate analysis mode */
11589         SendToProgram("analyze\n", &first);
11590         first.analyzing = TRUE;
11591         /*first.maybeThinking = TRUE;*/
11592         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11593     }
11594 }
11595
11596 void
11597 ToNrEvent(int to)
11598 {
11599   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11600   if (to >= forwardMostMove) to = forwardMostMove;
11601   if (to <= backwardMostMove) to = backwardMostMove;
11602   if (to < currentMove) {
11603     BackwardInner(to);
11604   } else {
11605     ForwardInner(to);
11606   }
11607 }
11608
11609 void
11610 RevertEvent()
11611 {
11612     if (gameMode != IcsExamining) {
11613         DisplayError(_("You are not examining a game"), 0);
11614         return;
11615     }
11616     if (pausing) {
11617         DisplayError(_("You can't revert while pausing"), 0);
11618         return;
11619     }
11620     SendToICS(ics_prefix);
11621     SendToICS("revert\n");
11622 }
11623
11624 void
11625 RetractMoveEvent()
11626 {
11627     switch (gameMode) {
11628       case MachinePlaysWhite:
11629       case MachinePlaysBlack:
11630         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11631             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11632             return;
11633         }
11634         if (forwardMostMove < 2) return;
11635         currentMove = forwardMostMove = forwardMostMove - 2;
11636         whiteTimeRemaining = timeRemaining[0][currentMove];
11637         blackTimeRemaining = timeRemaining[1][currentMove];
11638         DisplayBothClocks();
11639         DisplayMove(currentMove - 1);
11640         ClearHighlights();/*!! could figure this out*/
11641         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11642         SendToProgram("remove\n", &first);
11643         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11644         break;
11645
11646       case BeginningOfGame:
11647       default:
11648         break;
11649
11650       case IcsPlayingWhite:
11651       case IcsPlayingBlack:
11652         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11653             SendToICS(ics_prefix);
11654             SendToICS("takeback 2\n");
11655         } else {
11656             SendToICS(ics_prefix);
11657             SendToICS("takeback 1\n");
11658         }
11659         break;
11660     }
11661 }
11662
11663 void
11664 MoveNowEvent()
11665 {
11666     ChessProgramState *cps;
11667
11668     switch (gameMode) {
11669       case MachinePlaysWhite:
11670         if (!WhiteOnMove(forwardMostMove)) {
11671             DisplayError(_("It is your turn"), 0);
11672             return;
11673         }
11674         cps = &first;
11675         break;
11676       case MachinePlaysBlack:
11677         if (WhiteOnMove(forwardMostMove)) {
11678             DisplayError(_("It is your turn"), 0);
11679             return;
11680         }
11681         cps = &first;
11682         break;
11683       case TwoMachinesPlay:
11684         if (WhiteOnMove(forwardMostMove) ==
11685             (first.twoMachinesColor[0] == 'w')) {
11686             cps = &first;
11687         } else {
11688             cps = &second;
11689         }
11690         break;
11691       case BeginningOfGame:
11692       default:
11693         return;
11694     }
11695     SendToProgram("?\n", cps);
11696 }
11697
11698 void
11699 TruncateGameEvent()
11700 {
11701     EditGameEvent();
11702     if (gameMode != EditGame) return;
11703     TruncateGame();
11704 }
11705
11706 void
11707 TruncateGame()
11708 {
11709     if (forwardMostMove > currentMove) {
11710         if (gameInfo.resultDetails != NULL) {
11711             free(gameInfo.resultDetails);
11712             gameInfo.resultDetails = NULL;
11713             gameInfo.result = GameUnfinished;
11714         }
11715         forwardMostMove = currentMove;
11716         HistorySet(parseList, backwardMostMove, forwardMostMove,
11717                    currentMove-1);
11718     }
11719 }
11720
11721 void
11722 HintEvent()
11723 {
11724     if (appData.noChessProgram) return;
11725     switch (gameMode) {
11726       case MachinePlaysWhite:
11727         if (WhiteOnMove(forwardMostMove)) {
11728             DisplayError(_("Wait until your turn"), 0);
11729             return;
11730         }
11731         break;
11732       case BeginningOfGame:
11733       case MachinePlaysBlack:
11734         if (!WhiteOnMove(forwardMostMove)) {
11735             DisplayError(_("Wait until your turn"), 0);
11736             return;
11737         }
11738         break;
11739       default:
11740         DisplayError(_("No hint available"), 0);
11741         return;
11742     }
11743     SendToProgram("hint\n", &first);
11744     hintRequested = TRUE;
11745 }
11746
11747 void
11748 BookEvent()
11749 {
11750     if (appData.noChessProgram) return;
11751     switch (gameMode) {
11752       case MachinePlaysWhite:
11753         if (WhiteOnMove(forwardMostMove)) {
11754             DisplayError(_("Wait until your turn"), 0);
11755             return;
11756         }
11757         break;
11758       case BeginningOfGame:
11759       case MachinePlaysBlack:
11760         if (!WhiteOnMove(forwardMostMove)) {
11761             DisplayError(_("Wait until your turn"), 0);
11762             return;
11763         }
11764         break;
11765       case EditPosition:
11766         EditPositionDone();
11767         break;
11768       case TwoMachinesPlay:
11769         return;
11770       default:
11771         break;
11772     }
11773     SendToProgram("bk\n", &first);
11774     bookOutput[0] = NULLCHAR;
11775     bookRequested = TRUE;
11776 }
11777
11778 void
11779 AboutGameEvent()
11780 {
11781     char *tags = PGNTags(&gameInfo);
11782     TagsPopUp(tags, CmailMsg());
11783     free(tags);
11784 }
11785
11786 /* end button procedures */
11787
11788 void
11789 PrintPosition(fp, move)
11790      FILE *fp;
11791      int move;
11792 {
11793     int i, j;
11794
11795     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11796         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11797             char c = PieceToChar(boards[move][i][j]);
11798             fputc(c == 'x' ? '.' : c, fp);
11799             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11800         }
11801     }
11802     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11803       fprintf(fp, "white to play\n");
11804     else
11805       fprintf(fp, "black to play\n");
11806 }
11807
11808 void
11809 PrintOpponents(fp)
11810      FILE *fp;
11811 {
11812     if (gameInfo.white != NULL) {
11813         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11814     } else {
11815         fprintf(fp, "\n");
11816     }
11817 }
11818
11819 /* Find last component of program's own name, using some heuristics */
11820 void
11821 TidyProgramName(prog, host, buf)
11822      char *prog, *host, buf[MSG_SIZ];
11823 {
11824     char *p, *q;
11825     int local = (strcmp(host, "localhost") == 0);
11826     while (!local && (p = strchr(prog, ';')) != NULL) {
11827         p++;
11828         while (*p == ' ') p++;
11829         prog = p;
11830     }
11831     if (*prog == '"' || *prog == '\'') {
11832         q = strchr(prog + 1, *prog);
11833     } else {
11834         q = strchr(prog, ' ');
11835     }
11836     if (q == NULL) q = prog + strlen(prog);
11837     p = q;
11838     while (p >= prog && *p != '/' && *p != '\\') p--;
11839     p++;
11840     if(p == prog && *p == '"') p++;
11841     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11842     memcpy(buf, p, q - p);
11843     buf[q - p] = NULLCHAR;
11844     if (!local) {
11845         strcat(buf, "@");
11846         strcat(buf, host);
11847     }
11848 }
11849
11850 char *
11851 TimeControlTagValue()
11852 {
11853     char buf[MSG_SIZ];
11854     if (!appData.clockMode) {
11855         strcpy(buf, "-");
11856     } else if (movesPerSession > 0) {
11857         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11858     } else if (timeIncrement == 0) {
11859         sprintf(buf, "%ld", timeControl/1000);
11860     } else {
11861         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11862     }
11863     return StrSave(buf);
11864 }
11865
11866 void
11867 SetGameInfo()
11868 {
11869     /* This routine is used only for certain modes */
11870     VariantClass v = gameInfo.variant;
11871     ClearGameInfo(&gameInfo);
11872     gameInfo.variant = v;
11873
11874     switch (gameMode) {
11875       case MachinePlaysWhite:
11876         gameInfo.event = StrSave( appData.pgnEventHeader );
11877         gameInfo.site = StrSave(HostName());
11878         gameInfo.date = PGNDate();
11879         gameInfo.round = StrSave("-");
11880         gameInfo.white = StrSave(first.tidy);
11881         gameInfo.black = StrSave(UserName());
11882         gameInfo.timeControl = TimeControlTagValue();
11883         break;
11884
11885       case MachinePlaysBlack:
11886         gameInfo.event = StrSave( appData.pgnEventHeader );
11887         gameInfo.site = StrSave(HostName());
11888         gameInfo.date = PGNDate();
11889         gameInfo.round = StrSave("-");
11890         gameInfo.white = StrSave(UserName());
11891         gameInfo.black = StrSave(first.tidy);
11892         gameInfo.timeControl = TimeControlTagValue();
11893         break;
11894
11895       case TwoMachinesPlay:
11896         gameInfo.event = StrSave( appData.pgnEventHeader );
11897         gameInfo.site = StrSave(HostName());
11898         gameInfo.date = PGNDate();
11899         if (matchGame > 0) {
11900             char buf[MSG_SIZ];
11901             sprintf(buf, "%d", matchGame);
11902             gameInfo.round = StrSave(buf);
11903         } else {
11904             gameInfo.round = StrSave("-");
11905         }
11906         if (first.twoMachinesColor[0] == 'w') {
11907             gameInfo.white = StrSave(first.tidy);
11908             gameInfo.black = StrSave(second.tidy);
11909         } else {
11910             gameInfo.white = StrSave(second.tidy);
11911             gameInfo.black = StrSave(first.tidy);
11912         }
11913         gameInfo.timeControl = TimeControlTagValue();
11914         break;
11915
11916       case EditGame:
11917         gameInfo.event = StrSave("Edited game");
11918         gameInfo.site = StrSave(HostName());
11919         gameInfo.date = PGNDate();
11920         gameInfo.round = StrSave("-");
11921         gameInfo.white = StrSave("-");
11922         gameInfo.black = StrSave("-");
11923         break;
11924
11925       case EditPosition:
11926         gameInfo.event = StrSave("Edited position");
11927         gameInfo.site = StrSave(HostName());
11928         gameInfo.date = PGNDate();
11929         gameInfo.round = StrSave("-");
11930         gameInfo.white = StrSave("-");
11931         gameInfo.black = StrSave("-");
11932         break;
11933
11934       case IcsPlayingWhite:
11935       case IcsPlayingBlack:
11936       case IcsObserving:
11937       case IcsExamining:
11938         break;
11939
11940       case PlayFromGameFile:
11941         gameInfo.event = StrSave("Game from non-PGN file");
11942         gameInfo.site = StrSave(HostName());
11943         gameInfo.date = PGNDate();
11944         gameInfo.round = StrSave("-");
11945         gameInfo.white = StrSave("?");
11946         gameInfo.black = StrSave("?");
11947         break;
11948
11949       default:
11950         break;
11951     }
11952 }
11953
11954 void
11955 ReplaceComment(index, text)
11956      int index;
11957      char *text;
11958 {
11959     int len;
11960
11961     while (*text == '\n') text++;
11962     len = strlen(text);
11963     while (len > 0 && text[len - 1] == '\n') len--;
11964
11965     if (commentList[index] != NULL)
11966       free(commentList[index]);
11967
11968     if (len == 0) {
11969         commentList[index] = NULL;
11970         return;
11971     }
11972     commentList[index] = (char *) malloc(len + 2);
11973     strncpy(commentList[index], text, len);
11974     commentList[index][len] = '\n';
11975     commentList[index][len + 1] = NULLCHAR;
11976 }
11977
11978 void
11979 CrushCRs(text)
11980      char *text;
11981 {
11982   char *p = text;
11983   char *q = text;
11984   char ch;
11985
11986   do {
11987     ch = *p++;
11988     if (ch == '\r') continue;
11989     *q++ = ch;
11990   } while (ch != '\0');
11991 }
11992
11993 void
11994 AppendComment(index, text)
11995      int index;
11996      char *text;
11997 {
11998     int oldlen, len;
11999     char *old;
12000
12001     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12002
12003     CrushCRs(text);
12004     while (*text == '\n') text++;
12005     len = strlen(text);
12006     while (len > 0 && text[len - 1] == '\n') len--;
12007
12008     if (len == 0) return;
12009
12010     if (commentList[index] != NULL) {
12011         old = commentList[index];
12012         oldlen = strlen(old);
12013         commentList[index] = (char *) malloc(oldlen + len + 2);
12014         strcpy(commentList[index], old);
12015         free(old);
12016         strncpy(&commentList[index][oldlen], text, len);
12017         commentList[index][oldlen + len] = '\n';
12018         commentList[index][oldlen + len + 1] = NULLCHAR;
12019     } else {
12020         commentList[index] = (char *) malloc(len + 2);
12021         strncpy(commentList[index], text, len);
12022         commentList[index][len] = '\n';
12023         commentList[index][len + 1] = NULLCHAR;
12024     }
12025 }
12026
12027 static char * FindStr( char * text, char * sub_text )
12028 {
12029     char * result = strstr( text, sub_text );
12030
12031     if( result != NULL ) {
12032         result += strlen( sub_text );
12033     }
12034
12035     return result;
12036 }
12037
12038 /* [AS] Try to extract PV info from PGN comment */
12039 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12040 char *GetInfoFromComment( int index, char * text )
12041 {
12042     char * sep = text;
12043
12044     if( text != NULL && index > 0 ) {
12045         int score = 0;
12046         int depth = 0;
12047         int time = -1, sec = 0, deci;
12048         char * s_eval = FindStr( text, "[%eval " );
12049         char * s_emt = FindStr( text, "[%emt " );
12050
12051         if( s_eval != NULL || s_emt != NULL ) {
12052             /* New style */
12053             char delim;
12054
12055             if( s_eval != NULL ) {
12056                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12057                     return text;
12058                 }
12059
12060                 if( delim != ']' ) {
12061                     return text;
12062                 }
12063             }
12064
12065             if( s_emt != NULL ) {
12066             }
12067         }
12068         else {
12069             /* We expect something like: [+|-]nnn.nn/dd */
12070             int score_lo = 0;
12071
12072             sep = strchr( text, '/' );
12073             if( sep == NULL || sep < (text+4) ) {
12074                 return text;
12075             }
12076
12077             time = -1; sec = -1; deci = -1;
12078             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12079                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12080                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12081                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12082                 return text;
12083             }
12084
12085             if( score_lo < 0 || score_lo >= 100 ) {
12086                 return text;
12087             }
12088
12089             if(sec >= 0) time = 600*time + 10*sec; else
12090             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12091
12092             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12093
12094             /* [HGM] PV time: now locate end of PV info */
12095             while( *++sep >= '0' && *sep <= '9'); // strip depth
12096             if(time >= 0)
12097             while( *++sep >= '0' && *sep <= '9'); // strip time
12098             if(sec >= 0)
12099             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12100             if(deci >= 0)
12101             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12102             while(*sep == ' ') sep++;
12103         }
12104
12105         if( depth <= 0 ) {
12106             return text;
12107         }
12108
12109         if( time < 0 ) {
12110             time = -1;
12111         }
12112
12113         pvInfoList[index-1].depth = depth;
12114         pvInfoList[index-1].score = score;
12115         pvInfoList[index-1].time  = 10*time; // centi-sec
12116     }
12117     return sep;
12118 }
12119
12120 void
12121 SendToProgram(message, cps)
12122      char *message;
12123      ChessProgramState *cps;
12124 {
12125     int count, outCount, error;
12126     char buf[MSG_SIZ];
12127
12128     if (cps->pr == NULL) return;
12129     Attention(cps);
12130
12131     if (appData.debugMode) {
12132         TimeMark now;
12133         GetTimeMark(&now);
12134         fprintf(debugFP, "%ld >%-6s: %s",
12135                 SubtractTimeMarks(&now, &programStartTime),
12136                 cps->which, message);
12137     }
12138
12139     count = strlen(message);
12140     outCount = OutputToProcess(cps->pr, message, count, &error);
12141     if (outCount < count && !exiting
12142                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12143         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12144         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12145             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12146                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12147                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12148             } else {
12149                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12150             }
12151             gameInfo.resultDetails = buf;
12152         }
12153         DisplayFatalError(buf, error, 1);
12154     }
12155 }
12156
12157 void
12158 ReceiveFromProgram(isr, closure, message, count, error)
12159      InputSourceRef isr;
12160      VOIDSTAR closure;
12161      char *message;
12162      int count;
12163      int error;
12164 {
12165     char *end_str;
12166     char buf[MSG_SIZ];
12167     ChessProgramState *cps = (ChessProgramState *)closure;
12168
12169     if (isr != cps->isr) return; /* Killed intentionally */
12170     if (count <= 0) {
12171         if (count == 0) {
12172             sprintf(buf,
12173                     _("Error: %s chess program (%s) exited unexpectedly"),
12174                     cps->which, cps->program);
12175         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12176                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12177                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12178                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12179                 } else {
12180                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12181                 }
12182                 gameInfo.resultDetails = buf;
12183             }
12184             RemoveInputSource(cps->isr);
12185             DisplayFatalError(buf, 0, 1);
12186         } else {
12187             sprintf(buf,
12188                     _("Error reading from %s chess program (%s)"),
12189                     cps->which, cps->program);
12190             RemoveInputSource(cps->isr);
12191
12192             /* [AS] Program is misbehaving badly... kill it */
12193             if( count == -2 ) {
12194                 DestroyChildProcess( cps->pr, 9 );
12195                 cps->pr = NoProc;
12196             }
12197
12198             DisplayFatalError(buf, error, 1);
12199         }
12200         return;
12201     }
12202
12203     if ((end_str = strchr(message, '\r')) != NULL)
12204       *end_str = NULLCHAR;
12205     if ((end_str = strchr(message, '\n')) != NULL)
12206       *end_str = NULLCHAR;
12207
12208     if (appData.debugMode) {
12209         TimeMark now; int print = 1;
12210         char *quote = ""; char c; int i;
12211
12212         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12213                 char start = message[0];
12214                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12215                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12216                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12217                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12218                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12219                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12220                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12221                         { quote = "# "; print = (appData.engineComments == 2); }
12222                 message[0] = start; // restore original message
12223         }
12224         if(print) {
12225                 GetTimeMark(&now);
12226                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12227                         SubtractTimeMarks(&now, &programStartTime), cps->which,
12228                         quote,
12229                         message);
12230         }
12231     }
12232
12233     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12234     if (appData.icsEngineAnalyze) {
12235         if (strstr(message, "whisper") != NULL ||
12236              strstr(message, "kibitz") != NULL ||
12237             strstr(message, "tellics") != NULL) return;
12238     }
12239
12240     HandleMachineMove(message, cps);
12241 }
12242
12243
12244 void
12245 SendTimeControl(cps, mps, tc, inc, sd, st)
12246      ChessProgramState *cps;
12247      int mps, inc, sd, st;
12248      long tc;
12249 {
12250     char buf[MSG_SIZ];
12251     int seconds;
12252
12253     if( timeControl_2 > 0 ) {
12254         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12255             tc = timeControl_2;
12256         }
12257     }
12258     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12259     inc /= cps->timeOdds;
12260     st  /= cps->timeOdds;
12261
12262     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12263
12264     if (st > 0) {
12265       /* Set exact time per move, normally using st command */
12266       if (cps->stKludge) {
12267         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12268         seconds = st % 60;
12269         if (seconds == 0) {
12270           sprintf(buf, "level 1 %d\n", st/60);
12271         } else {
12272           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12273         }
12274       } else {
12275         sprintf(buf, "st %d\n", st);
12276       }
12277     } else {
12278       /* Set conventional or incremental time control, using level command */
12279       if (seconds == 0) {
12280         /* Note old gnuchess bug -- minutes:seconds used to not work.
12281            Fixed in later versions, but still avoid :seconds
12282            when seconds is 0. */
12283         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12284       } else {
12285         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12286                 seconds, inc/1000);
12287       }
12288     }
12289     SendToProgram(buf, cps);
12290
12291     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12292     /* Orthogonally, limit search to given depth */
12293     if (sd > 0) {
12294       if (cps->sdKludge) {
12295         sprintf(buf, "depth\n%d\n", sd);
12296       } else {
12297         sprintf(buf, "sd %d\n", sd);
12298       }
12299       SendToProgram(buf, cps);
12300     }
12301
12302     if(cps->nps > 0) { /* [HGM] nps */
12303         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12304         else {
12305                 sprintf(buf, "nps %d\n", cps->nps);
12306               SendToProgram(buf, cps);
12307         }
12308     }
12309 }
12310
12311 ChessProgramState *WhitePlayer()
12312 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12313 {
12314     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12315        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12316         return &second;
12317     return &first;
12318 }
12319
12320 void
12321 SendTimeRemaining(cps, machineWhite)
12322      ChessProgramState *cps;
12323      int /*boolean*/ machineWhite;
12324 {
12325     char message[MSG_SIZ];
12326     long time, otime;
12327
12328     /* Note: this routine must be called when the clocks are stopped
12329        or when they have *just* been set or switched; otherwise
12330        it will be off by the time since the current tick started.
12331     */
12332     if (machineWhite) {
12333         time = whiteTimeRemaining / 10;
12334         otime = blackTimeRemaining / 10;
12335     } else {
12336         time = blackTimeRemaining / 10;
12337         otime = whiteTimeRemaining / 10;
12338     }
12339     /* [HGM] translate opponent's time by time-odds factor */
12340     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12341     if (appData.debugMode) {
12342         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12343     }
12344
12345     if (time <= 0) time = 1;
12346     if (otime <= 0) otime = 1;
12347
12348     sprintf(message, "time %ld\n", time);
12349     SendToProgram(message, cps);
12350
12351     sprintf(message, "otim %ld\n", otime);
12352     SendToProgram(message, cps);
12353 }
12354
12355 int
12356 BoolFeature(p, name, loc, cps)
12357      char **p;
12358      char *name;
12359      int *loc;
12360      ChessProgramState *cps;
12361 {
12362   char buf[MSG_SIZ];
12363   int len = strlen(name);
12364   int val;
12365   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12366     (*p) += len + 1;
12367     sscanf(*p, "%d", &val);
12368     *loc = (val != 0);
12369     while (**p && **p != ' ') (*p)++;
12370     sprintf(buf, "accepted %s\n", name);
12371     SendToProgram(buf, cps);
12372     return TRUE;
12373   }
12374   return FALSE;
12375 }
12376
12377 int
12378 IntFeature(p, name, loc, cps)
12379      char **p;
12380      char *name;
12381      int *loc;
12382      ChessProgramState *cps;
12383 {
12384   char buf[MSG_SIZ];
12385   int len = strlen(name);
12386   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12387     (*p) += len + 1;
12388     sscanf(*p, "%d", loc);
12389     while (**p && **p != ' ') (*p)++;
12390     sprintf(buf, "accepted %s\n", name);
12391     SendToProgram(buf, cps);
12392     return TRUE;
12393   }
12394   return FALSE;
12395 }
12396
12397 int
12398 StringFeature(p, name, loc, cps)
12399      char **p;
12400      char *name;
12401      char loc[];
12402      ChessProgramState *cps;
12403 {
12404   char buf[MSG_SIZ];
12405   int len = strlen(name);
12406   if (strncmp((*p), name, len) == 0
12407       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12408     (*p) += len + 2;
12409     sscanf(*p, "%[^\"]", loc);
12410     while (**p && **p != '\"') (*p)++;
12411     if (**p == '\"') (*p)++;
12412     sprintf(buf, "accepted %s\n", name);
12413     SendToProgram(buf, cps);
12414     return TRUE;
12415   }
12416   return FALSE;
12417 }
12418
12419 int
12420 ParseOption(Option *opt, ChessProgramState *cps)
12421 // [HGM] options: process the string that defines an engine option, and determine
12422 // name, type, default value, and allowed value range
12423 {
12424         char *p, *q, buf[MSG_SIZ];
12425         int n, min = (-1)<<31, max = 1<<31, def;
12426
12427         if(p = strstr(opt->name, " -spin ")) {
12428             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12429             if(max < min) max = min; // enforce consistency
12430             if(def < min) def = min;
12431             if(def > max) def = max;
12432             opt->value = def;
12433             opt->min = min;
12434             opt->max = max;
12435             opt->type = Spin;
12436         } else if(p = strstr(opt->name, " -string ")) {
12437             opt->textValue = p+9;
12438             opt->type = TextBox;
12439         } else if(p = strstr(opt->name, " -check ")) {
12440             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12441             opt->value = (def != 0);
12442             opt->type = CheckBox;
12443         } else if(p = strstr(opt->name, " -combo ")) {
12444             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12445             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12446             opt->value = n = 0;
12447             while(q = StrStr(q, " /// ")) {
12448                 n++; *q = 0;    // count choices, and null-terminate each of them
12449                 q += 5;
12450                 if(*q == '*') { // remember default, which is marked with * prefix
12451                     q++;
12452                     opt->value = n;
12453                 }
12454                 cps->comboList[cps->comboCnt++] = q;
12455             }
12456             cps->comboList[cps->comboCnt++] = NULL;
12457             opt->max = n + 1;
12458             opt->type = ComboBox;
12459         } else if(p = strstr(opt->name, " -button")) {
12460             opt->type = Button;
12461         } else if(p = strstr(opt->name, " -save")) {
12462             opt->type = SaveButton;
12463         } else return FALSE;
12464         *p = 0; // terminate option name
12465         // now look if the command-line options define a setting for this engine option.
12466         if(cps->optionSettings && cps->optionSettings[0])
12467             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12468         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12469                 sprintf(buf, "option %s", p);
12470                 if(p = strstr(buf, ",")) *p = 0;
12471                 strcat(buf, "\n");
12472                 SendToProgram(buf, cps);
12473         }
12474         return TRUE;
12475 }
12476
12477 void
12478 FeatureDone(cps, val)
12479      ChessProgramState* cps;
12480      int val;
12481 {
12482   DelayedEventCallback cb = GetDelayedEvent();
12483   if ((cb == InitBackEnd3 && cps == &first) ||
12484       (cb == TwoMachinesEventIfReady && cps == &second)) {
12485     CancelDelayedEvent();
12486     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12487   }
12488   cps->initDone = val;
12489 }
12490
12491 /* Parse feature command from engine */
12492 void
12493 ParseFeatures(args, cps)
12494      char* args;
12495      ChessProgramState *cps;
12496 {
12497   char *p = args;
12498   char *q;
12499   int val;
12500   char buf[MSG_SIZ];
12501
12502   for (;;) {
12503     while (*p == ' ') p++;
12504     if (*p == NULLCHAR) return;
12505
12506     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12507     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12508     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12509     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12510     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12511     if (BoolFeature(&p, "reuse", &val, cps)) {
12512       /* Engine can disable reuse, but can't enable it if user said no */
12513       if (!val) cps->reuse = FALSE;
12514       continue;
12515     }
12516     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12517     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12518       if (gameMode == TwoMachinesPlay) {
12519         DisplayTwoMachinesTitle();
12520       } else {
12521         DisplayTitle("");
12522       }
12523       continue;
12524     }
12525     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12526     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12527     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12528     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12529     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12530     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12531     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12532     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12533     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12534     if (IntFeature(&p, "done", &val, cps)) {
12535       FeatureDone(cps, val);
12536       continue;
12537     }
12538     /* Added by Tord: */
12539     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12540     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12541     /* End of additions by Tord */
12542
12543     /* [HGM] added features: */
12544     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12545     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12546     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12547     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12548     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12549     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12550     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12551         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
12552         if(cps->nrOptions >= MAX_OPTIONS) {
12553             cps->nrOptions--;
12554             sprintf(buf, "%s engine has too many options\n", cps->which);
12555             DisplayError(buf, 0);
12556         }
12557         continue;
12558     }
12559     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12560     /* End of additions by HGM */
12561
12562     /* unknown feature: complain and skip */
12563     q = p;
12564     while (*q && *q != '=') q++;
12565     sprintf(buf, "rejected %.*s\n", q-p, p);
12566     SendToProgram(buf, cps);
12567     p = q;
12568     if (*p == '=') {
12569       p++;
12570       if (*p == '\"') {
12571         p++;
12572         while (*p && *p != '\"') p++;
12573         if (*p == '\"') p++;
12574       } else {
12575         while (*p && *p != ' ') p++;
12576       }
12577     }
12578   }
12579
12580 }
12581
12582 void
12583 PeriodicUpdatesEvent(newState)
12584      int newState;
12585 {
12586     if (newState == appData.periodicUpdates)
12587       return;
12588
12589     appData.periodicUpdates=newState;
12590
12591     /* Display type changes, so update it now */
12592     DisplayAnalysis();
12593
12594     /* Get the ball rolling again... */
12595     if (newState) {
12596         AnalysisPeriodicEvent(1);
12597         StartAnalysisClock();
12598     }
12599 }
12600
12601 void
12602 PonderNextMoveEvent(newState)
12603      int newState;
12604 {
12605     if (newState == appData.ponderNextMove) return;
12606     if (gameMode == EditPosition) EditPositionDone();
12607     if (newState) {
12608         SendToProgram("hard\n", &first);
12609         if (gameMode == TwoMachinesPlay) {
12610             SendToProgram("hard\n", &second);
12611         }
12612     } else {
12613         SendToProgram("easy\n", &first);
12614         thinkOutput[0] = NULLCHAR;
12615         if (gameMode == TwoMachinesPlay) {
12616             SendToProgram("easy\n", &second);
12617         }
12618     }
12619     appData.ponderNextMove = newState;
12620 }
12621
12622 void
12623 NewSettingEvent(option, command, value)
12624      char *command;
12625      int option, value;
12626 {
12627     char buf[MSG_SIZ];
12628
12629     if (gameMode == EditPosition) EditPositionDone();
12630     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12631     SendToProgram(buf, &first);
12632     if (gameMode == TwoMachinesPlay) {
12633         SendToProgram(buf, &second);
12634     }
12635 }
12636
12637 void
12638 ShowThinkingEvent()
12639 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12640 {
12641     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12642     int newState = appData.showThinking
12643         // [HGM] thinking: other features now need thinking output as well
12644         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12645
12646     if (oldState == newState) return;
12647     oldState = newState;
12648     if (gameMode == EditPosition) EditPositionDone();
12649     if (oldState) {
12650         SendToProgram("post\n", &first);
12651         if (gameMode == TwoMachinesPlay) {
12652             SendToProgram("post\n", &second);
12653         }
12654     } else {
12655         SendToProgram("nopost\n", &first);
12656         thinkOutput[0] = NULLCHAR;
12657         if (gameMode == TwoMachinesPlay) {
12658             SendToProgram("nopost\n", &second);
12659         }
12660     }
12661 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12662 }
12663
12664 void
12665 AskQuestionEvent(title, question, replyPrefix, which)
12666      char *title; char *question; char *replyPrefix; char *which;
12667 {
12668   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12669   if (pr == NoProc) return;
12670   AskQuestion(title, question, replyPrefix, pr);
12671 }
12672
12673 void
12674 DisplayMove(moveNumber)
12675      int moveNumber;
12676 {
12677     char message[MSG_SIZ];
12678     char res[MSG_SIZ];
12679     char cpThinkOutput[MSG_SIZ];
12680
12681     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12682
12683     if (moveNumber == forwardMostMove - 1 ||
12684         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12685
12686         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12687
12688         if (strchr(cpThinkOutput, '\n')) {
12689             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12690         }
12691     } else {
12692         *cpThinkOutput = NULLCHAR;
12693     }
12694
12695     /* [AS] Hide thinking from human user */
12696     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12697         *cpThinkOutput = NULLCHAR;
12698         if( thinkOutput[0] != NULLCHAR ) {
12699             int i;
12700
12701             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12702                 cpThinkOutput[i] = '.';
12703             }
12704             cpThinkOutput[i] = NULLCHAR;
12705             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12706         }
12707     }
12708
12709     if (moveNumber == forwardMostMove - 1 &&
12710         gameInfo.resultDetails != NULL) {
12711         if (gameInfo.resultDetails[0] == NULLCHAR) {
12712             sprintf(res, " %s", PGNResult(gameInfo.result));
12713         } else {
12714             sprintf(res, " {%s} %s",
12715                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12716         }
12717     } else {
12718         res[0] = NULLCHAR;
12719     }
12720
12721     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12722         DisplayMessage(res, cpThinkOutput);
12723     } else {
12724         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12725                 WhiteOnMove(moveNumber) ? " " : ".. ",
12726                 parseList[moveNumber], res);
12727         DisplayMessage(message, cpThinkOutput);
12728     }
12729 }
12730
12731 void
12732 DisplayAnalysisText(text)
12733      char *text;
12734 {
12735     char buf[MSG_SIZ];
12736
12737     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12738                || appData.icsEngineAnalyze) {
12739         sprintf(buf, "Analysis (%s)", first.tidy);
12740         AnalysisPopUp(buf, text);
12741     }
12742 }
12743
12744 static int
12745 only_one_move(str)
12746      char *str;
12747 {
12748     while (*str && isspace(*str)) ++str;
12749     while (*str && !isspace(*str)) ++str;
12750     if (!*str) return 1;
12751     while (*str && isspace(*str)) ++str;
12752     if (!*str) return 1;
12753     return 0;
12754 }
12755
12756 void
12757 DisplayAnalysis()
12758 {
12759     char buf[MSG_SIZ];
12760     char lst[MSG_SIZ / 2];
12761     double nps;
12762     static char *xtra[] = { "", " (--)", " (++)" };
12763     int h, m, s, cs;
12764
12765     if (programStats.time == 0) {
12766         programStats.time = 1;
12767     }
12768
12769     if (programStats.got_only_move) {
12770         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12771     } else {
12772         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12773
12774         nps = (u64ToDouble(programStats.nodes) /
12775              ((double)programStats.time /100.0));
12776
12777         cs = programStats.time % 100;
12778         s = programStats.time / 100;
12779         h = (s / (60*60));
12780         s = s - h*60*60;
12781         m = (s/60);
12782         s = s - m*60;
12783
12784         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12785           if (programStats.move_name[0] != NULLCHAR) {
12786             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12787                     programStats.depth,
12788                     programStats.nr_moves-programStats.moves_left,
12789                     programStats.nr_moves, programStats.move_name,
12790                     ((float)programStats.score)/100.0, lst,
12791                     only_one_move(lst)?
12792                     xtra[programStats.got_fail] : "",
12793                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12794           } else {
12795             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12796                     programStats.depth,
12797                     programStats.nr_moves-programStats.moves_left,
12798                     programStats.nr_moves, ((float)programStats.score)/100.0,
12799                     lst,
12800                     only_one_move(lst)?
12801                     xtra[programStats.got_fail] : "",
12802                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12803           }
12804         } else {
12805             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12806                     programStats.depth,
12807                     ((float)programStats.score)/100.0,
12808                     lst,
12809                     only_one_move(lst)?
12810                     xtra[programStats.got_fail] : "",
12811                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12812         }
12813     }
12814     DisplayAnalysisText(buf);
12815 }
12816
12817 void
12818 DisplayComment(moveNumber, text)
12819      int moveNumber;
12820      char *text;
12821 {
12822     char title[MSG_SIZ];
12823     char buf[8000]; // comment can be long!
12824     int score, depth;
12825
12826     if( appData.autoDisplayComment ) {
12827         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12828             strcpy(title, "Comment");
12829         } else {
12830             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12831                     WhiteOnMove(moveNumber) ? " " : ".. ",
12832                     parseList[moveNumber]);
12833         }
12834         // [HGM] PV info: display PV info together with (or as) comment
12835         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12836             if(text == NULL) text = "";
12837             score = pvInfoList[moveNumber].score;
12838             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12839                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12840             text = buf;
12841         }
12842     } else title[0] = 0;
12843
12844     if (text != NULL)
12845         CommentPopUp(title, text);
12846 }
12847
12848 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12849  * might be busy thinking or pondering.  It can be omitted if your
12850  * gnuchess is configured to stop thinking immediately on any user
12851  * input.  However, that gnuchess feature depends on the FIONREAD
12852  * ioctl, which does not work properly on some flavors of Unix.
12853  */
12854 void
12855 Attention(cps)
12856      ChessProgramState *cps;
12857 {
12858 #if ATTENTION
12859     if (!cps->useSigint) return;
12860     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12861     switch (gameMode) {
12862       case MachinePlaysWhite:
12863       case MachinePlaysBlack:
12864       case TwoMachinesPlay:
12865       case IcsPlayingWhite:
12866       case IcsPlayingBlack:
12867       case AnalyzeMode:
12868       case AnalyzeFile:
12869         /* Skip if we know it isn't thinking */
12870         if (!cps->maybeThinking) return;
12871         if (appData.debugMode)
12872           fprintf(debugFP, "Interrupting %s\n", cps->which);
12873         InterruptChildProcess(cps->pr);
12874         cps->maybeThinking = FALSE;
12875         break;
12876       default:
12877         break;
12878     }
12879 #endif /*ATTENTION*/
12880 }
12881
12882 int
12883 CheckFlags()
12884 {
12885     if (whiteTimeRemaining <= 0) {
12886         if (!whiteFlag) {
12887             whiteFlag = TRUE;
12888             if (appData.icsActive) {
12889                 if (appData.autoCallFlag &&
12890                     gameMode == IcsPlayingBlack && !blackFlag) {
12891                   SendToICS(ics_prefix);
12892                   SendToICS("flag\n");
12893                 }
12894             } else {
12895                 if (blackFlag) {
12896                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12897                 } else {
12898                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12899                     if (appData.autoCallFlag) {
12900                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12901                         return TRUE;
12902                     }
12903                 }
12904             }
12905         }
12906     }
12907     if (blackTimeRemaining <= 0) {
12908         if (!blackFlag) {
12909             blackFlag = TRUE;
12910             if (appData.icsActive) {
12911                 if (appData.autoCallFlag &&
12912                     gameMode == IcsPlayingWhite && !whiteFlag) {
12913                   SendToICS(ics_prefix);
12914                   SendToICS("flag\n");
12915                 }
12916             } else {
12917                 if (whiteFlag) {
12918                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12919                 } else {
12920                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12921                     if (appData.autoCallFlag) {
12922                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12923                         return TRUE;
12924                     }
12925                 }
12926             }
12927         }
12928     }
12929     return FALSE;
12930 }
12931
12932 void
12933 CheckTimeControl()
12934 {
12935     if (!appData.clockMode || appData.icsActive ||
12936         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12937
12938     /*
12939      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12940      */
12941     if ( !WhiteOnMove(forwardMostMove) )
12942         /* White made time control */
12943         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12944         /* [HGM] time odds: correct new time quota for time odds! */
12945                                             / WhitePlayer()->timeOdds;
12946       else
12947         /* Black made time control */
12948         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12949                                             / WhitePlayer()->other->timeOdds;
12950 }
12951
12952 void
12953 DisplayBothClocks()
12954 {
12955     int wom = gameMode == EditPosition ?
12956       !blackPlaysFirst : WhiteOnMove(currentMove);
12957     DisplayWhiteClock(whiteTimeRemaining, wom);
12958     DisplayBlackClock(blackTimeRemaining, !wom);
12959 }
12960
12961
12962 /* Timekeeping seems to be a portability nightmare.  I think everyone
12963    has ftime(), but I'm really not sure, so I'm including some ifdefs
12964    to use other calls if you don't.  Clocks will be less accurate if
12965    you have neither ftime nor gettimeofday.
12966 */
12967
12968 /* VS 2008 requires the #include outside of the function */
12969 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12970 #include <sys/timeb.h>
12971 #endif
12972
12973 /* Get the current time as a TimeMark */
12974 void
12975 GetTimeMark(tm)
12976      TimeMark *tm;
12977 {
12978 #if HAVE_GETTIMEOFDAY
12979
12980     struct timeval timeVal;
12981     struct timezone timeZone;
12982
12983     gettimeofday(&timeVal, &timeZone);
12984     tm->sec = (long) timeVal.tv_sec;
12985     tm->ms = (int) (timeVal.tv_usec / 1000L);
12986
12987 #else /*!HAVE_GETTIMEOFDAY*/
12988 #if HAVE_FTIME
12989
12990 // include <sys/timeb.h> / moved to just above start of function
12991     struct timeb timeB;
12992
12993     ftime(&timeB);
12994     tm->sec = (long) timeB.time;
12995     tm->ms = (int) timeB.millitm;
12996
12997 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12998     tm->sec = (long) time(NULL);
12999     tm->ms = 0;
13000 #endif
13001 #endif
13002 }
13003
13004 /* Return the difference in milliseconds between two
13005    time marks.  We assume the difference will fit in a long!
13006 */
13007 long
13008 SubtractTimeMarks(tm2, tm1)
13009      TimeMark *tm2, *tm1;
13010 {
13011     return 1000L*(tm2->sec - tm1->sec) +
13012            (long) (tm2->ms - tm1->ms);
13013 }
13014
13015
13016 /*
13017  * Code to manage the game clocks.
13018  *
13019  * In tournament play, black starts the clock and then white makes a move.
13020  * We give the human user a slight advantage if he is playing white---the
13021  * clocks don't run until he makes his first move, so it takes zero time.
13022  * Also, we don't account for network lag, so we could get out of sync
13023  * with GNU Chess's clock -- but then, referees are always right.
13024  */
13025
13026 static TimeMark tickStartTM;
13027 static long intendedTickLength;
13028
13029 long
13030 NextTickLength(timeRemaining)
13031      long timeRemaining;
13032 {
13033     long nominalTickLength, nextTickLength;
13034
13035     if (timeRemaining > 0L && timeRemaining <= 10000L)
13036       nominalTickLength = 100L;
13037     else
13038       nominalTickLength = 1000L;
13039     nextTickLength = timeRemaining % nominalTickLength;
13040     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13041
13042     return nextTickLength;
13043 }
13044
13045 /* Adjust clock one minute up or down */
13046 void
13047 AdjustClock(Boolean which, int dir)
13048 {
13049     if(which) blackTimeRemaining += 60000*dir;
13050     else      whiteTimeRemaining += 60000*dir;
13051     DisplayBothClocks();
13052 }
13053
13054 /* Stop clocks and reset to a fresh time control */
13055 void
13056 ResetClocks()
13057 {
13058     (void) StopClockTimer();
13059     if (appData.icsActive) {
13060         whiteTimeRemaining = blackTimeRemaining = 0;
13061     } else { /* [HGM] correct new time quote for time odds */
13062         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13063         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13064     }
13065     if (whiteFlag || blackFlag) {
13066         DisplayTitle("");
13067         whiteFlag = blackFlag = FALSE;
13068     }
13069     DisplayBothClocks();
13070 }
13071
13072 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13073
13074 /* Decrement running clock by amount of time that has passed */
13075 void
13076 DecrementClocks()
13077 {
13078     long timeRemaining;
13079     long lastTickLength, fudge;
13080     TimeMark now;
13081
13082     if (!appData.clockMode) return;
13083     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13084
13085     GetTimeMark(&now);
13086
13087     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13088
13089     /* Fudge if we woke up a little too soon */
13090     fudge = intendedTickLength - lastTickLength;
13091     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13092
13093     if (WhiteOnMove(forwardMostMove)) {
13094         if(whiteNPS >= 0) lastTickLength = 0;
13095         timeRemaining = whiteTimeRemaining -= lastTickLength;
13096         DisplayWhiteClock(whiteTimeRemaining - fudge,
13097                           WhiteOnMove(currentMove));
13098     } else {
13099         if(blackNPS >= 0) lastTickLength = 0;
13100         timeRemaining = blackTimeRemaining -= lastTickLength;
13101         DisplayBlackClock(blackTimeRemaining - fudge,
13102                           !WhiteOnMove(currentMove));
13103     }
13104
13105     if (CheckFlags()) return;
13106
13107     tickStartTM = now;
13108     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13109     StartClockTimer(intendedTickLength);
13110
13111     /* if the time remaining has fallen below the alarm threshold, sound the
13112      * alarm. if the alarm has sounded and (due to a takeback or time control
13113      * with increment) the time remaining has increased to a level above the
13114      * threshold, reset the alarm so it can sound again.
13115      */
13116
13117     if (appData.icsActive && appData.icsAlarm) {
13118
13119         /* make sure we are dealing with the user's clock */
13120         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13121                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13122            )) return;
13123
13124         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13125             alarmSounded = FALSE;
13126         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13127             PlayAlarmSound();
13128             alarmSounded = TRUE;
13129         }
13130     }
13131 }
13132
13133
13134 /* A player has just moved, so stop the previously running
13135    clock and (if in clock mode) start the other one.
13136    We redisplay both clocks in case we're in ICS mode, because
13137    ICS gives us an update to both clocks after every move.
13138    Note that this routine is called *after* forwardMostMove
13139    is updated, so the last fractional tick must be subtracted
13140    from the color that is *not* on move now.
13141 */
13142 void
13143 SwitchClocks()
13144 {
13145     long lastTickLength;
13146     TimeMark now;
13147     int flagged = FALSE;
13148
13149     GetTimeMark(&now);
13150
13151     if (StopClockTimer() && appData.clockMode) {
13152         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13153         if (WhiteOnMove(forwardMostMove)) {
13154             if(blackNPS >= 0) lastTickLength = 0;
13155             blackTimeRemaining -= lastTickLength;
13156            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13157 //         if(pvInfoList[forwardMostMove-1].time == -1)
13158                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13159                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13160         } else {
13161            if(whiteNPS >= 0) lastTickLength = 0;
13162            whiteTimeRemaining -= lastTickLength;
13163            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13164 //         if(pvInfoList[forwardMostMove-1].time == -1)
13165                  pvInfoList[forwardMostMove-1].time =
13166                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13167         }
13168         flagged = CheckFlags();
13169     }
13170     CheckTimeControl();
13171
13172     if (flagged || !appData.clockMode) return;
13173
13174     switch (gameMode) {
13175       case MachinePlaysBlack:
13176       case MachinePlaysWhite:
13177       case BeginningOfGame:
13178         if (pausing) return;
13179         break;
13180
13181       case EditGame:
13182       case PlayFromGameFile:
13183       case IcsExamining:
13184         return;
13185
13186       default:
13187         break;
13188     }
13189
13190     tickStartTM = now;
13191     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13192       whiteTimeRemaining : blackTimeRemaining);
13193     StartClockTimer(intendedTickLength);
13194 }
13195
13196
13197 /* Stop both clocks */
13198 void
13199 StopClocks()
13200 {
13201     long lastTickLength;
13202     TimeMark now;
13203
13204     if (!StopClockTimer()) return;
13205     if (!appData.clockMode) return;
13206
13207     GetTimeMark(&now);
13208
13209     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13210     if (WhiteOnMove(forwardMostMove)) {
13211         if(whiteNPS >= 0) lastTickLength = 0;
13212         whiteTimeRemaining -= lastTickLength;
13213         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13214     } else {
13215         if(blackNPS >= 0) lastTickLength = 0;
13216         blackTimeRemaining -= lastTickLength;
13217         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13218     }
13219     CheckFlags();
13220 }
13221
13222 /* Start clock of player on move.  Time may have been reset, so
13223    if clock is already running, stop and restart it. */
13224 void
13225 StartClocks()
13226 {
13227     (void) StopClockTimer(); /* in case it was running already */
13228     DisplayBothClocks();
13229     if (CheckFlags()) return;
13230
13231     if (!appData.clockMode) return;
13232     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13233
13234     GetTimeMark(&tickStartTM);
13235     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13236       whiteTimeRemaining : blackTimeRemaining);
13237
13238    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13239     whiteNPS = blackNPS = -1;
13240     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13241        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13242         whiteNPS = first.nps;
13243     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13244        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13245         blackNPS = first.nps;
13246     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13247         whiteNPS = second.nps;
13248     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13249         blackNPS = second.nps;
13250     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13251
13252     StartClockTimer(intendedTickLength);
13253 }
13254
13255 char *
13256 TimeString(ms)
13257      long ms;
13258 {
13259     long second, minute, hour, day;
13260     char *sign = "";
13261     static char buf[32];
13262
13263     if (ms > 0 && ms <= 9900) {
13264       /* convert milliseconds to tenths, rounding up */
13265       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13266
13267       sprintf(buf, " %03.1f ", tenths/10.0);
13268       return buf;
13269     }
13270
13271     /* convert milliseconds to seconds, rounding up */
13272     /* use floating point to avoid strangeness of integer division
13273        with negative dividends on many machines */
13274     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13275
13276     if (second < 0) {
13277         sign = "-";
13278         second = -second;
13279     }
13280
13281     day = second / (60 * 60 * 24);
13282     second = second % (60 * 60 * 24);
13283     hour = second / (60 * 60);
13284     second = second % (60 * 60);
13285     minute = second / 60;
13286     second = second % 60;
13287
13288     if (day > 0)
13289       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13290               sign, day, hour, minute, second);
13291     else if (hour > 0)
13292       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13293     else
13294       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13295
13296     return buf;
13297 }
13298
13299
13300 /*
13301  * This is necessary because some C libraries aren't ANSI C compliant yet.
13302  */
13303 char *
13304 StrStr(string, match)
13305      char *string, *match;
13306 {
13307     int i, length;
13308
13309     length = strlen(match);
13310
13311     for (i = strlen(string) - length; i >= 0; i--, string++)
13312       if (!strncmp(match, string, length))
13313         return string;
13314
13315     return NULL;
13316 }
13317
13318 char *
13319 StrCaseStr(string, match)
13320      char *string, *match;
13321 {
13322     int i, j, length;
13323
13324     length = strlen(match);
13325
13326     for (i = strlen(string) - length; i >= 0; i--, string++) {
13327         for (j = 0; j < length; j++) {
13328             if (ToLower(match[j]) != ToLower(string[j]))
13329               break;
13330         }
13331         if (j == length) return string;
13332     }
13333
13334     return NULL;
13335 }
13336
13337 #ifndef _amigados
13338 int
13339 StrCaseCmp(s1, s2)
13340      char *s1, *s2;
13341 {
13342     char c1, c2;
13343
13344     for (;;) {
13345         c1 = ToLower(*s1++);
13346         c2 = ToLower(*s2++);
13347         if (c1 > c2) return 1;
13348         if (c1 < c2) return -1;
13349         if (c1 == NULLCHAR) return 0;
13350     }
13351 }
13352
13353
13354 int
13355 ToLower(c)
13356      int c;
13357 {
13358     return isupper(c) ? tolower(c) : c;
13359 }
13360
13361
13362 int
13363 ToUpper(c)
13364      int c;
13365 {
13366     return islower(c) ? toupper(c) : c;
13367 }
13368 #endif /* !_amigados    */
13369
13370 char *
13371 StrSave(s)
13372      char *s;
13373 {
13374     char *ret;
13375
13376     if ((ret = (char *) malloc(strlen(s) + 1))) {
13377         strcpy(ret, s);
13378     }
13379     return ret;
13380 }
13381
13382 char *
13383 StrSavePtr(s, savePtr)
13384      char *s, **savePtr;
13385 {
13386     if (*savePtr) {
13387         free(*savePtr);
13388     }
13389     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13390         strcpy(*savePtr, s);
13391     }
13392     return(*savePtr);
13393 }
13394
13395 char *
13396 PGNDate()
13397 {
13398     time_t clock;
13399     struct tm *tm;
13400     char buf[MSG_SIZ];
13401
13402     clock = time((time_t *)NULL);
13403     tm = localtime(&clock);
13404     sprintf(buf, "%04d.%02d.%02d",
13405             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13406     return StrSave(buf);
13407 }
13408
13409
13410 char *
13411 PositionToFEN(move, overrideCastling)
13412      int move;
13413      char *overrideCastling;
13414 {
13415     int i, j, fromX, fromY, toX, toY;
13416     int whiteToPlay;
13417     char buf[128];
13418     char *p, *q;
13419     int emptycount;
13420     ChessSquare piece;
13421
13422     whiteToPlay = (gameMode == EditPosition) ?
13423       !blackPlaysFirst : (move % 2 == 0);
13424     p = buf;
13425
13426     /* Piece placement data */
13427     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13428         emptycount = 0;
13429         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13430             if (boards[move][i][j] == EmptySquare) {
13431                 emptycount++;
13432             } else { ChessSquare piece = boards[move][i][j];
13433                 if (emptycount > 0) {
13434                     if(emptycount<10) /* [HGM] can be >= 10 */
13435                         *p++ = '0' + emptycount;
13436                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13437                     emptycount = 0;
13438                 }
13439                 if(PieceToChar(piece) == '+') {
13440                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13441                     *p++ = '+';
13442                     piece = (ChessSquare)(DEMOTED piece);
13443                 }
13444                 *p++ = PieceToChar(piece);
13445                 if(p[-1] == '~') {
13446                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13447                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13448                     *p++ = '~';
13449                 }
13450             }
13451         }
13452         if (emptycount > 0) {
13453             if(emptycount<10) /* [HGM] can be >= 10 */
13454                 *p++ = '0' + emptycount;
13455             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13456             emptycount = 0;
13457         }
13458         *p++ = '/';
13459     }
13460     *(p - 1) = ' ';
13461
13462     /* [HGM] print Crazyhouse or Shogi holdings */
13463     if( gameInfo.holdingsWidth ) {
13464         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13465         q = p;
13466         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13467             piece = boards[move][i][BOARD_WIDTH-1];
13468             if( piece != EmptySquare )
13469               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13470                   *p++ = PieceToChar(piece);
13471         }
13472         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13473             piece = boards[move][BOARD_HEIGHT-i-1][0];
13474             if( piece != EmptySquare )
13475               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13476                   *p++ = PieceToChar(piece);
13477         }
13478
13479         if( q == p ) *p++ = '-';
13480         *p++ = ']';
13481         *p++ = ' ';
13482     }
13483
13484     /* Active color */
13485     *p++ = whiteToPlay ? 'w' : 'b';
13486     *p++ = ' ';
13487
13488   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13489     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13490   } else {
13491   if(nrCastlingRights) {
13492      q = p;
13493      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13494        /* [HGM] write directly from rights */
13495            if(castlingRights[move][2] >= 0 &&
13496               castlingRights[move][0] >= 0   )
13497                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13498            if(castlingRights[move][2] >= 0 &&
13499               castlingRights[move][1] >= 0   )
13500                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13501            if(castlingRights[move][5] >= 0 &&
13502               castlingRights[move][3] >= 0   )
13503                 *p++ = castlingRights[move][3] + AAA;
13504            if(castlingRights[move][5] >= 0 &&
13505               castlingRights[move][4] >= 0   )
13506                 *p++ = castlingRights[move][4] + AAA;
13507      } else {
13508
13509         /* [HGM] write true castling rights */
13510         if( nrCastlingRights == 6 ) {
13511             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13512                castlingRights[move][2] >= 0  ) *p++ = 'K';
13513             if(castlingRights[move][1] == BOARD_LEFT &&
13514                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13515             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13516                castlingRights[move][5] >= 0  ) *p++ = 'k';
13517             if(castlingRights[move][4] == BOARD_LEFT &&
13518                castlingRights[move][5] >= 0  ) *p++ = 'q';
13519         }
13520      }
13521      if (q == p) *p++ = '-'; /* No castling rights */
13522      *p++ = ' ';
13523   }
13524
13525   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13526      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13527     /* En passant target square */
13528     if (move > backwardMostMove) {
13529         fromX = moveList[move - 1][0] - AAA;
13530         fromY = moveList[move - 1][1] - ONE;
13531         toX = moveList[move - 1][2] - AAA;
13532         toY = moveList[move - 1][3] - ONE;
13533         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13534             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13535             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13536             fromX == toX) {
13537             /* 2-square pawn move just happened */
13538             *p++ = toX + AAA;
13539             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13540         } else {
13541             *p++ = '-';
13542         }
13543     } else {
13544         *p++ = '-';
13545     }
13546     *p++ = ' ';
13547   }
13548   }
13549
13550     /* [HGM] find reversible plies */
13551     {   int i = 0, j=move;
13552
13553         if (appData.debugMode) { int k;
13554             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13555             for(k=backwardMostMove; k<=forwardMostMove; k++)
13556                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13557
13558         }
13559
13560         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13561         if( j == backwardMostMove ) i += initialRulePlies;
13562         sprintf(p, "%d ", i);
13563         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13564     }
13565     /* Fullmove number */
13566     sprintf(p, "%d", (move / 2) + 1);
13567
13568     return StrSave(buf);
13569 }
13570
13571 Boolean
13572 ParseFEN(board, blackPlaysFirst, fen)
13573     Board board;
13574      int *blackPlaysFirst;
13575      char *fen;
13576 {
13577     int i, j;
13578     char *p;
13579     int emptycount;
13580     ChessSquare piece;
13581
13582     p = fen;
13583
13584     /* [HGM] by default clear Crazyhouse holdings, if present */
13585     if(gameInfo.holdingsWidth) {
13586        for(i=0; i<BOARD_HEIGHT; i++) {
13587            board[i][0]             = EmptySquare; /* black holdings */
13588            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13589            board[i][1]             = (ChessSquare) 0; /* black counts */
13590            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13591        }
13592     }
13593
13594     /* Piece placement data */
13595     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13596         j = 0;
13597         for (;;) {
13598             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13599                 if (*p == '/') p++;
13600                 emptycount = gameInfo.boardWidth - j;
13601                 while (emptycount--)
13602                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13603                 break;
13604 #if(BOARD_SIZE >= 10)
13605             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13606                 p++; emptycount=10;
13607                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13608                 while (emptycount--)
13609                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13610 #endif
13611             } else if (isdigit(*p)) {
13612                 emptycount = *p++ - '0';
13613                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13614                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13615                 while (emptycount--)
13616                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13617             } else if (*p == '+' || isalpha(*p)) {
13618                 if (j >= gameInfo.boardWidth) return FALSE;
13619                 if(*p=='+') {
13620                     piece = CharToPiece(*++p);
13621                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13622                     piece = (ChessSquare) (PROMOTED piece ); p++;
13623                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13624                 } else piece = CharToPiece(*p++);
13625
13626                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13627                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13628                     piece = (ChessSquare) (PROMOTED piece);
13629                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13630                     p++;
13631                 }
13632                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13633             } else {
13634                 return FALSE;
13635             }
13636         }
13637     }
13638     while (*p == '/' || *p == ' ') p++;
13639
13640     /* [HGM] look for Crazyhouse holdings here */
13641     while(*p==' ') p++;
13642     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13643         if(*p == '[') p++;
13644         if(*p == '-' ) *p++; /* empty holdings */ else {
13645             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13646             /* if we would allow FEN reading to set board size, we would   */
13647             /* have to add holdings and shift the board read so far here   */
13648             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13649                 *p++;
13650                 if((int) piece >= (int) BlackPawn ) {
13651                     i = (int)piece - (int)BlackPawn;
13652                     i = PieceToNumber((ChessSquare)i);
13653                     if( i >= gameInfo.holdingsSize ) return FALSE;
13654                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13655                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13656                 } else {
13657                     i = (int)piece - (int)WhitePawn;
13658                     i = PieceToNumber((ChessSquare)i);
13659                     if( i >= gameInfo.holdingsSize ) return FALSE;
13660                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13661                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13662                 }
13663             }
13664         }
13665         if(*p == ']') *p++;
13666     }
13667
13668     while(*p == ' ') p++;
13669
13670     /* Active color */
13671     switch (*p++) {
13672       case 'w':
13673         *blackPlaysFirst = FALSE;
13674         break;
13675       case 'b':
13676         *blackPlaysFirst = TRUE;
13677         break;
13678       default:
13679         return FALSE;
13680     }
13681
13682     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13683     /* return the extra info in global variiables             */
13684
13685     /* set defaults in case FEN is incomplete */
13686     FENepStatus = EP_UNKNOWN;
13687     for(i=0; i<nrCastlingRights; i++ ) {
13688         FENcastlingRights[i] =
13689             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13690     }   /* assume possible unless obviously impossible */
13691     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13692     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13693     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13694     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13695     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13696     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13697     FENrulePlies = 0;
13698
13699     while(*p==' ') p++;
13700     if(nrCastlingRights) {
13701       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13702           /* castling indicator present, so default becomes no castlings */
13703           for(i=0; i<nrCastlingRights; i++ ) {
13704                  FENcastlingRights[i] = -1;
13705           }
13706       }
13707       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13708              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13709              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13710              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13711         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13712
13713         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13714             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13715             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13716         }
13717         switch(c) {
13718           case'K':
13719               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13720               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13721               FENcastlingRights[2] = whiteKingFile;
13722               break;
13723           case'Q':
13724               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13725               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13726               FENcastlingRights[2] = whiteKingFile;
13727               break;
13728           case'k':
13729               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13730               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13731               FENcastlingRights[5] = blackKingFile;
13732               break;
13733           case'q':
13734               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13735               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13736               FENcastlingRights[5] = blackKingFile;
13737           case '-':
13738               break;
13739           default: /* FRC castlings */
13740               if(c >= 'a') { /* black rights */
13741                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13742                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13743                   if(i == BOARD_RGHT) break;
13744                   FENcastlingRights[5] = i;
13745                   c -= AAA;
13746                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13747                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13748                   if(c > i)
13749                       FENcastlingRights[3] = c;
13750                   else
13751                       FENcastlingRights[4] = c;
13752               } else { /* white rights */
13753                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13754                     if(board[0][i] == WhiteKing) break;
13755                   if(i == BOARD_RGHT) break;
13756                   FENcastlingRights[2] = i;
13757                   c -= AAA - 'a' + 'A';
13758                   if(board[0][c] >= WhiteKing) break;
13759                   if(c > i)
13760                       FENcastlingRights[0] = c;
13761                   else
13762                       FENcastlingRights[1] = c;
13763               }
13764         }
13765       }
13766     if (appData.debugMode) {
13767         fprintf(debugFP, "FEN castling rights:");
13768         for(i=0; i<nrCastlingRights; i++)
13769         fprintf(debugFP, " %d", FENcastlingRights[i]);
13770         fprintf(debugFP, "\n");
13771     }
13772
13773       while(*p==' ') p++;
13774     }
13775
13776     /* read e.p. field in games that know e.p. capture */
13777     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13778        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13779       if(*p=='-') {
13780         p++; FENepStatus = EP_NONE;
13781       } else {
13782          char c = *p++ - AAA;
13783
13784          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13785          if(*p >= '0' && *p <='9') *p++;
13786          FENepStatus = c;
13787       }
13788     }
13789
13790
13791     if(sscanf(p, "%d", &i) == 1) {
13792         FENrulePlies = i; /* 50-move ply counter */
13793         /* (The move number is still ignored)    */
13794     }
13795
13796     return TRUE;
13797 }
13798
13799 void
13800 EditPositionPasteFEN(char *fen)
13801 {
13802   if (fen != NULL) {
13803     Board initial_position;
13804
13805     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13806       DisplayError(_("Bad FEN position in clipboard"), 0);
13807       return ;
13808     } else {
13809       int savedBlackPlaysFirst = blackPlaysFirst;
13810       EditPositionEvent();
13811       blackPlaysFirst = savedBlackPlaysFirst;
13812       CopyBoard(boards[0], initial_position);
13813           /* [HGM] copy FEN attributes as well */
13814           {   int i;
13815               initialRulePlies = FENrulePlies;
13816               epStatus[0] = FENepStatus;
13817               for( i=0; i<nrCastlingRights; i++ )
13818                   castlingRights[0][i] = FENcastlingRights[i];
13819           }
13820       EditPositionDone();
13821       DisplayBothClocks();
13822       DrawPosition(FALSE, boards[currentMove]);
13823     }
13824   }
13825 }